1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.client5.http.impl.classic;
29
30 import java.io.IOException;
31 import java.net.Socket;
32
33 import org.apache.hc.client5.http.AuthenticationStrategy;
34 import org.apache.hc.client5.http.HttpRoute;
35 import org.apache.hc.client5.http.RouteInfo.LayerType;
36 import org.apache.hc.client5.http.RouteInfo.TunnelType;
37 import org.apache.hc.client5.http.auth.AuthExchange;
38 import org.apache.hc.client5.http.auth.AuthSchemeFactory;
39 import org.apache.hc.client5.http.auth.AuthScope;
40 import org.apache.hc.client5.http.auth.ChallengeType;
41 import org.apache.hc.client5.http.auth.Credentials;
42 import org.apache.hc.client5.http.auth.StandardAuthScheme;
43 import org.apache.hc.client5.http.config.RequestConfig;
44 import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
45 import org.apache.hc.client5.http.impl.DefaultClientConnectionReuseStrategy;
46 import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
47 import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
48 import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
49 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
50 import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
51 import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
52 import org.apache.hc.client5.http.protocol.HttpClientContext;
53 import org.apache.hc.client5.http.protocol.RequestClientConnControl;
54 import org.apache.hc.core5.http.ClassicHttpRequest;
55 import org.apache.hc.core5.http.ClassicHttpResponse;
56 import org.apache.hc.core5.http.ConnectionReuseStrategy;
57 import org.apache.hc.core5.http.HttpEntity;
58 import org.apache.hc.core5.http.HttpException;
59 import org.apache.hc.core5.http.HttpHeaders;
60 import org.apache.hc.core5.http.HttpHost;
61 import org.apache.hc.core5.http.Method;
62 import org.apache.hc.core5.http.config.CharCodingConfig;
63 import org.apache.hc.core5.http.config.Http1Config;
64 import org.apache.hc.core5.http.config.Lookup;
65 import org.apache.hc.core5.http.config.RegistryBuilder;
66 import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
67 import org.apache.hc.core5.http.io.HttpConnectionFactory;
68 import org.apache.hc.core5.http.io.entity.EntityUtils;
69 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
70 import org.apache.hc.core5.http.message.StatusLine;
71 import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
72 import org.apache.hc.core5.http.protocol.HttpProcessor;
73 import org.apache.hc.core5.http.protocol.RequestTargetHost;
74 import org.apache.hc.core5.http.protocol.RequestUserAgent;
75 import org.apache.hc.core5.util.Args;
76
77
78
79
80 public class ProxyClient {
81
82 private final HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
83 private final RequestConfig requestConfig;
84 private final HttpProcessor httpProcessor;
85 private final HttpRequestExecutor requestExec;
86 private final AuthenticationStrategy proxyAuthStrategy;
87 private final HttpAuthenticator authenticator;
88 private final AuthExchange proxyAuthExchange;
89 private final Lookup<AuthSchemeFactory> authSchemeRegistry;
90 private final ConnectionReuseStrategy reuseStrategy;
91
92
93
94
95 public ProxyClient(
96 final HttpConnectionFactory<ManagedHttpClientConnection> connFactory,
97 final Http1Config h1Config,
98 final CharCodingConfig charCodingConfig,
99 final RequestConfig requestConfig) {
100 super();
101 this.connFactory = connFactory != null
102 ? connFactory
103 : ManagedHttpClientConnectionFactory.builder()
104 .http1Config(h1Config)
105 .charCodingConfig(charCodingConfig)
106 .build();
107 this.requestConfig = requestConfig != null ? requestConfig : RequestConfig.DEFAULT;
108 this.httpProcessor = new DefaultHttpProcessor(
109 new RequestTargetHost(), new RequestClientConnControl(), new RequestUserAgent());
110 this.requestExec = new HttpRequestExecutor();
111 this.proxyAuthStrategy = new DefaultAuthenticationStrategy();
112 this.authenticator = new HttpAuthenticator();
113 this.proxyAuthExchange = new AuthExchange();
114 this.authSchemeRegistry = RegistryBuilder.<AuthSchemeFactory>create()
115 .register(StandardAuthScheme.BASIC, BasicSchemeFactory.INSTANCE)
116 .register(StandardAuthScheme.DIGEST, DigestSchemeFactory.INSTANCE)
117 .build();
118 this.reuseStrategy = DefaultClientConnectionReuseStrategy.INSTANCE;
119 }
120
121
122
123
124 public ProxyClient(final RequestConfig requestConfig) {
125 this(null, null, null, requestConfig);
126 }
127
128 public ProxyClient() {
129 this(null, null, null, null);
130 }
131
132 public Socket tunnel(
133 final HttpHost proxy,
134 final HttpHost target,
135 final Credentials credentials) throws IOException, HttpException {
136 Args.notNull(proxy, "Proxy host");
137 Args.notNull(target, "Target host");
138 Args.notNull(credentials, "Credentials");
139 Args.check(target.getPort() > 0, "A valid port number must be provided for the tunnel CONNECT request.");
140
141 final HttpRoute route = new HttpRoute(
142 target,
143 null,
144 proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN);
145
146 final ManagedHttpClientConnection conn = this.connFactory.createConnection(null);
147 final HttpClientContext context = HttpClientContext.create();
148 ClassicHttpResponse response;
149
150 final ClassicHttpRequest connect = new BasicClassicHttpRequest(Method.CONNECT, proxy, target.toHostString());
151
152 final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
153 credsProvider.setCredentials(new AuthScope(proxy), credentials);
154
155
156 context.setRequest(connect);
157 context.setRoute(route);
158 context.setCredentialsProvider(credsProvider);
159 context.setAuthSchemeRegistry(this.authSchemeRegistry);
160 context.setRequestConfig(this.requestConfig);
161
162 this.requestExec.preProcess(connect, this.httpProcessor, context);
163
164 for (;;) {
165 if (!conn.isOpen()) {
166 final Socket socket = new Socket(proxy.getHostName(), proxy.getPort());
167 conn.bind(socket);
168 }
169
170 this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, this.proxyAuthExchange, context);
171
172 response = this.requestExec.execute(connect, conn, context);
173
174 final int status = response.getCode();
175 if (status < 200) {
176 throw new HttpException("Unexpected response to CONNECT request: " + response);
177 }
178 if (this.authenticator.isChallenged(proxy, ChallengeType.PROXY, response, this.proxyAuthExchange, context)) {
179 if (this.authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
180 this.proxyAuthStrategy, this.proxyAuthExchange, context)) {
181
182 if (this.reuseStrategy.keepAlive(connect, response, context)) {
183
184 final HttpEntity entity = response.getEntity();
185 EntityUtils.consume(entity);
186 } else {
187 conn.close();
188 }
189
190 connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
191 } else {
192 break;
193 }
194 } else {
195 break;
196 }
197 }
198
199 final int status = response.getCode();
200
201 if (status > 299) {
202 EntityUtils.consume(response.getEntity());
203 conn.close();
204 throw new HttpException("Tunnel refused: " + new StatusLine(response));
205 }
206 return conn.getSocket();
207 }
208
209 }