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