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 HttpHost host = target;
144 if (host.getPort() <= 0) {
145 host = new HttpHost(host.getSchemeName(), host.getHostName(), 80);
146 }
147 final HttpRoute route = new HttpRoute(
148 host,
149 null,
150 proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN);
151
152 final ManagedHttpClientConnection conn = this.connFactory.createConnection(null);
153 final HttpContext context = new BasicHttpContext();
154 ClassicHttpResponse response;
155
156 final ClassicHttpRequest connect = new BasicClassicHttpRequest(Method.CONNECT, proxy, host.toHostString());
157
158 final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
159 credsProvider.setCredentials(new AuthScope(proxy), credentials);
160
161
162 context.setAttribute(HttpCoreContext.HTTP_REQUEST, connect);
163 context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
164 context.setAttribute(HttpClientContext.CREDS_PROVIDER, credsProvider);
165 context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
166 context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.requestConfig);
167
168 this.requestExec.preProcess(connect, this.httpProcessor, context);
169
170 for (;;) {
171 if (!conn.isOpen()) {
172 final Socket socket = new Socket(proxy.getHostName(), proxy.getPort());
173 conn.bind(socket);
174 }
175
176 this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, this.proxyAuthExchange, context);
177
178 response = this.requestExec.execute(connect, conn, context);
179
180 final int status = response.getCode();
181 if (status < 200) {
182 throw new HttpException("Unexpected response to CONNECT request: " + response);
183 }
184 if (this.authenticator.isChallenged(proxy, ChallengeType.PROXY, response, this.proxyAuthExchange, context)) {
185 if (this.authenticator.updateAuthState(proxy, ChallengeType.PROXY, response,
186 this.proxyAuthStrategy, this.proxyAuthExchange, context)) {
187
188 if (this.reuseStrategy.keepAlive(connect, response, context)) {
189
190 final HttpEntity entity = response.getEntity();
191 EntityUtils.consume(entity);
192 } else {
193 conn.close();
194 }
195
196 connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
197 } else {
198 break;
199 }
200 } else {
201 break;
202 }
203 }
204
205 final int status = response.getCode();
206
207 if (status > 299) {
208
209
210 final HttpEntity entity = response.getEntity();
211 final String responseMessage = entity != null ? EntityUtils.toString(entity) : null;
212 conn.close();
213 throw new TunnelRefusedException("CONNECT refused by proxy: " + new StatusLine(response), responseMessage);
214 }
215 return conn.getSocket();
216 }
217
218 }