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