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