View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl.client;
29  
30  import java.io.IOException;
31  import java.net.Socket;
32  
33  import org.apache.http.ConnectionReuseStrategy;
34  import org.apache.http.HttpEntity;
35  import org.apache.http.HttpException;
36  import org.apache.http.HttpHost;
37  import org.apache.http.HttpRequest;
38  import org.apache.http.HttpResponse;
39  import org.apache.http.HttpVersion;
40  import org.apache.http.auth.AUTH;
41  import org.apache.http.auth.AuthSchemeRegistry;
42  import org.apache.http.auth.AuthScope;
43  import org.apache.http.auth.AuthState;
44  import org.apache.http.auth.Credentials;
45  import org.apache.http.client.config.AuthSchemes;
46  import org.apache.http.client.config.RequestConfig;
47  import org.apache.http.client.params.HttpClientParamConfig;
48  import org.apache.http.client.protocol.HttpClientContext;
49  import org.apache.http.client.protocol.RequestClientConnControl;
50  import org.apache.http.config.ConnectionConfig;
51  import org.apache.http.conn.HttpConnectionFactory;
52  import org.apache.http.conn.ManagedHttpClientConnection;
53  import org.apache.http.conn.routing.HttpRoute;
54  import org.apache.http.conn.routing.RouteInfo.LayerType;
55  import org.apache.http.conn.routing.RouteInfo.TunnelType;
56  import org.apache.http.entity.BufferedHttpEntity;
57  import org.apache.http.impl.DefaultConnectionReuseStrategy;
58  import org.apache.http.impl.auth.BasicSchemeFactory;
59  import org.apache.http.impl.auth.DigestSchemeFactory;
60  import org.apache.http.impl.auth.HttpAuthenticator;
61  import org.apache.http.impl.auth.KerberosSchemeFactory;
62  import org.apache.http.impl.auth.NTLMSchemeFactory;
63  import org.apache.http.impl.auth.SPNegoSchemeFactory;
64  import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
65  import org.apache.http.impl.execchain.TunnelRefusedException;
66  import org.apache.http.message.BasicHttpRequest;
67  import org.apache.http.params.BasicHttpParams;
68  import org.apache.http.params.HttpParamConfig;
69  import org.apache.http.params.HttpParams;
70  import org.apache.http.protocol.BasicHttpContext;
71  import org.apache.http.protocol.HttpContext;
72  import org.apache.http.protocol.HttpCoreContext;
73  import org.apache.http.protocol.HttpProcessor;
74  import org.apache.http.protocol.HttpRequestExecutor;
75  import org.apache.http.protocol.ImmutableHttpProcessor;
76  import org.apache.http.protocol.RequestTargetHost;
77  import org.apache.http.protocol.RequestUserAgent;
78  import org.apache.http.util.Args;
79  import org.apache.http.util.EntityUtils;
80  
81  /**
82   * ProxyClient can be used to establish a tunnel via an HTTP proxy.
83   */
84  @SuppressWarnings("deprecation")
85  public class ProxyClient {
86  
87      private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory;
88      private final ConnectionConfig connectionConfig;
89      private final RequestConfig requestConfig;
90      private final HttpProcessor httpProcessor;
91      private final HttpRequestExecutor requestExec;
92      private final ProxyAuthenticationStrategy proxyAuthStrategy;
93      private final HttpAuthenticator authenticator;
94      private final AuthState proxyAuthState;
95      private final AuthSchemeRegistry authSchemeRegistry;
96      private final ConnectionReuseStrategy reuseStrategy;
97  
98      /**
99       * @since 4.3
100      */
101     public ProxyClient(
102             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
103             final ConnectionConfig connectionConfig,
104             final RequestConfig requestConfig) {
105         super();
106         this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
107         this.connectionConfig = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT;
108         this.requestConfig = requestConfig != null ? requestConfig : RequestConfig.DEFAULT;
109         this.httpProcessor = new ImmutableHttpProcessor(
110                 new RequestTargetHost(), new RequestClientConnControl(), new RequestUserAgent());
111         this.requestExec = new HttpRequestExecutor();
112         this.proxyAuthStrategy = new ProxyAuthenticationStrategy();
113         this.authenticator = new HttpAuthenticator();
114         this.proxyAuthState = new AuthState();
115         this.authSchemeRegistry = new AuthSchemeRegistry();
116         this.authSchemeRegistry.register(AuthSchemes.BASIC, new BasicSchemeFactory());
117         this.authSchemeRegistry.register(AuthSchemes.DIGEST, new DigestSchemeFactory());
118         this.authSchemeRegistry.register(AuthSchemes.NTLM, new NTLMSchemeFactory());
119         this.authSchemeRegistry.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory());
120         this.authSchemeRegistry.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory());
121         this.reuseStrategy = new DefaultConnectionReuseStrategy();
122     }
123 
124     /**
125      * @deprecated (4.3) use {@link ProxyClient#ProxyClient(HttpConnectionFactory, ConnectionConfig, RequestConfig)}
126      */
127     @Deprecated
128     public ProxyClient(final HttpParams params) {
129         this(null,
130                 HttpParamConfig.getConnectionConfig(params),
131                 HttpClientParamConfig.getRequestConfig(params));
132     }
133 
134     /**
135      * @since 4.3
136      */
137     public ProxyClient(final RequestConfig requestConfig) {
138         this(null, null, requestConfig);
139     }
140 
141     public ProxyClient() {
142         this(null, null, null);
143     }
144 
145     /**
146      * @deprecated (4.3) do not use.
147      */
148     @Deprecated
149     public HttpParams getParams() {
150         return new BasicHttpParams();
151     }
152 
153     /**
154      * @deprecated (4.3) do not use.
155      */
156     @Deprecated
157     public AuthSchemeRegistry getAuthSchemeRegistry() {
158         return this.authSchemeRegistry;
159     }
160 
161     public Socket tunnel(
162             final HttpHost proxy,
163             final HttpHost target,
164             final Credentials credentials) throws IOException, HttpException {
165         Args.notNull(proxy, "Proxy host");
166         Args.notNull(target, "Target host");
167         Args.notNull(credentials, "Credentials");
168         HttpHost host = target;
169         if (host.getPort() <= 0) {
170             host = new HttpHost(host.getHostName(), 80, host.getSchemeName());
171         }
172         final HttpRoute route = new HttpRoute(
173                 host,
174                 this.requestConfig.getLocalAddress(),
175                 proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN);
176 
177         final ManagedHttpClientConnection conn = this.connFactory.create(
178                 route, this.connectionConfig);
179         final HttpContext context = new BasicHttpContext();
180         HttpResponse response;
181 
182         final HttpRequest connect = new BasicHttpRequest(
183                 "CONNECT", host.toHostString(), HttpVersion.HTTP_1_1);
184 
185         final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
186         credsProvider.setCredentials(new AuthScope(proxy), credentials);
187 
188         // Populate the execution context
189         context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);
190         context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
191         context.setAttribute(HttpCoreContext.HTTP_REQUEST, connect);
192         context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
193         context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, this.proxyAuthState);
194         context.setAttribute(HttpClientContext.CREDS_PROVIDER, credsProvider);
195         context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
196         context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.requestConfig);
197 
198         this.requestExec.preProcess(connect, this.httpProcessor, context);
199 
200         for (;;) {
201             if (!conn.isOpen()) {
202                 final Socket socket = new Socket(proxy.getHostName(), proxy.getPort());
203                 conn.bind(socket);
204             }
205 
206             this.authenticator.generateAuthResponse(connect, this.proxyAuthState, context);
207 
208             response = this.requestExec.execute(connect, conn, context);
209 
210             final int status = response.getStatusLine().getStatusCode();
211             if (status < 200) {
212                 throw new HttpException("Unexpected response to CONNECT request: " +
213                         response.getStatusLine());
214             }
215             if (this.authenticator.isAuthenticationRequested(proxy, response,
216                     this.proxyAuthStrategy, this.proxyAuthState, context)) {
217                 if (this.authenticator.handleAuthChallenge(proxy, response,
218                         this.proxyAuthStrategy, this.proxyAuthState, context)) {
219                     // Retry request
220                     if (this.reuseStrategy.keepAlive(response, context)) {
221                         // Consume response content
222                         final HttpEntity entity = response.getEntity();
223                         EntityUtils.consume(entity);
224                     } else {
225                         conn.close();
226                     }
227                     // discard previous auth header
228                     connect.removeHeaders(AUTH.PROXY_AUTH_RESP);
229                 } else {
230                     break;
231                 }
232             } else {
233                 break;
234             }
235         }
236 
237         final int status = response.getStatusLine().getStatusCode();
238 
239         if (status > 299) {
240 
241             // Buffer response content
242             final HttpEntity entity = response.getEntity();
243             if (entity != null) {
244                 response.setEntity(new BufferedHttpEntity(entity));
245             }
246 
247             conn.close();
248             throw new TunnelRefusedException("CONNECT refused by proxy: " +
249                     response.getStatusLine(), response);
250         }
251         return conn.getSocket();
252     }
253 
254 }