1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.client;
20
21 import java.io.IOException;
22 import java.net.URI;
23 import java.util.Map;
24 import java.util.concurrent.TimeUnit;
25
26 import org.eclipse.jetty.client.api.Connection;
27 import org.eclipse.jetty.client.api.Request;
28 import org.eclipse.jetty.client.api.Response;
29 import org.eclipse.jetty.client.api.Result;
30 import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
31 import org.eclipse.jetty.http.HttpHeader;
32 import org.eclipse.jetty.http.HttpMethod;
33 import org.eclipse.jetty.http.HttpScheme;
34 import org.eclipse.jetty.io.ClientConnectionFactory;
35 import org.eclipse.jetty.io.EndPoint;
36 import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
37 import org.eclipse.jetty.util.Promise;
38 import org.eclipse.jetty.util.log.Log;
39 import org.eclipse.jetty.util.log.Logger;
40 import org.eclipse.jetty.util.ssl.SslContextFactory;
41
42 public class HttpProxy extends ProxyConfiguration.Proxy
43 {
44 public HttpProxy(String host, int port)
45 {
46 this(new Origin.Address(host, port), false);
47 }
48
49 public HttpProxy(Origin.Address address, boolean secure)
50 {
51 super(address, secure);
52 }
53
54 @Override
55 public ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory)
56 {
57 return new HttpProxyClientConnectionFactory(connectionFactory);
58 }
59
60 @Override
61 public URI getURI()
62 {
63 String scheme = isSecure() ? HttpScheme.HTTPS.asString() : HttpScheme.HTTP.asString();
64 return URI.create(new Origin(scheme, getAddress()).asString());
65 }
66
67 public static class HttpProxyClientConnectionFactory implements ClientConnectionFactory
68 {
69 private static final Logger LOG = Log.getLogger(HttpProxyClientConnectionFactory.class);
70 private final ClientConnectionFactory connectionFactory;
71
72 public HttpProxyClientConnectionFactory(ClientConnectionFactory connectionFactory)
73 {
74 this.connectionFactory = connectionFactory;
75 }
76
77 @Override
78 public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
79 {
80 @SuppressWarnings("unchecked")
81 Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
82 final ProxyPromise proxyPromise = new ProxyPromise(endPoint, promise, context);
83
84 context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, proxyPromise);
85 return connectionFactory.newConnection(endPoint, context);
86 }
87
88
89
90
91
92
93
94 private class ProxyPromise implements Promise<Connection>
95 {
96 private final EndPoint endPoint;
97 private final Promise<Connection> promise;
98 private final Map<String, Object> context;
99
100 private ProxyPromise(EndPoint endPoint, Promise<Connection> promise, Map<String, Object> context)
101 {
102 this.endPoint = endPoint;
103 this.promise = promise;
104 this.context = context;
105 }
106
107 @Override
108 public void succeeded(Connection connection)
109 {
110 HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
111 if (HttpScheme.HTTPS.is(destination.getScheme()))
112 {
113 SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory();
114 if (sslContextFactory != null)
115 {
116 tunnel(destination, connection);
117 }
118 else
119 {
120 String message = String.format("Cannot perform requests over SSL, no %s in %s",
121 SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
122 promise.failed(new IllegalStateException(message));
123 }
124 }
125 else
126 {
127 promise.succeeded(connection);
128 }
129 }
130
131 @Override
132 public void failed(Throwable x)
133 {
134 promise.failed(x);
135 }
136
137 private void tunnel(HttpDestination destination, final Connection connection)
138 {
139 String target = destination.getOrigin().getAddress().asString();
140 Origin.Address proxyAddress = destination.getConnectAddress();
141 HttpClient httpClient = destination.getHttpClient();
142 Request connect = httpClient.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
143 .scheme(HttpScheme.HTTP.asString())
144 .method(HttpMethod.CONNECT)
145 .path(target)
146 .header(HttpHeader.HOST, target)
147 .timeout(httpClient.getConnectTimeout(), TimeUnit.MILLISECONDS);
148
149 connection.send(connect, new Response.CompleteListener()
150 {
151 @Override
152 public void onComplete(Result result)
153 {
154 if (result.isFailed())
155 {
156 tunnelFailed(result.getFailure());
157 }
158 else
159 {
160 Response response = result.getResponse();
161 if (response.getStatus() == 200)
162 {
163 tunnelSucceeded();
164 }
165 else
166 {
167 tunnelFailed(new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
168 }
169 }
170 }
171 });
172 }
173
174 private void tunnelSucceeded()
175 {
176 try
177 {
178
179 context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
180 HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
181 HttpClient client = destination.getHttpClient();
182 ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
183 HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection();
184 org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
185 Helper.replaceConnection(oldConnection, newConnection);
186
187
188 oldConnection.softClose();
189 if (LOG.isDebugEnabled())
190 LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
191 }
192 catch (Throwable x)
193 {
194 tunnelFailed(x);
195 }
196 }
197
198 private void tunnelFailed(Throwable failure)
199 {
200 endPoint.close();
201 failed(failure);
202 }
203 }
204 }
205 }