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