001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.client; 029 030import java.io.IOException; 031import java.net.Socket; 032 033import org.apache.http.ConnectionReuseStrategy; 034import org.apache.http.HttpEntity; 035import org.apache.http.HttpException; 036import org.apache.http.HttpHost; 037import org.apache.http.HttpRequest; 038import org.apache.http.HttpResponse; 039import org.apache.http.HttpVersion; 040import org.apache.http.auth.AUTH; 041import org.apache.http.auth.AuthSchemeRegistry; 042import org.apache.http.auth.AuthScope; 043import org.apache.http.auth.AuthState; 044import org.apache.http.auth.Credentials; 045import org.apache.http.client.config.AuthSchemes; 046import org.apache.http.client.config.RequestConfig; 047import org.apache.http.client.params.HttpClientParamConfig; 048import org.apache.http.client.protocol.HttpClientContext; 049import org.apache.http.client.protocol.RequestClientConnControl; 050import org.apache.http.config.ConnectionConfig; 051import org.apache.http.conn.HttpConnectionFactory; 052import org.apache.http.conn.ManagedHttpClientConnection; 053import org.apache.http.conn.routing.HttpRoute; 054import org.apache.http.conn.routing.RouteInfo.LayerType; 055import org.apache.http.conn.routing.RouteInfo.TunnelType; 056import org.apache.http.entity.BufferedHttpEntity; 057import org.apache.http.impl.DefaultConnectionReuseStrategy; 058import org.apache.http.impl.auth.BasicSchemeFactory; 059import org.apache.http.impl.auth.DigestSchemeFactory; 060import org.apache.http.impl.auth.HttpAuthenticator; 061import org.apache.http.impl.auth.KerberosSchemeFactory; 062import org.apache.http.impl.auth.NTLMSchemeFactory; 063import org.apache.http.impl.auth.SPNegoSchemeFactory; 064import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory; 065import org.apache.http.impl.execchain.TunnelRefusedException; 066import org.apache.http.message.BasicHttpRequest; 067import org.apache.http.params.BasicHttpParams; 068import org.apache.http.params.HttpParamConfig; 069import org.apache.http.params.HttpParams; 070import org.apache.http.protocol.BasicHttpContext; 071import org.apache.http.protocol.HttpContext; 072import org.apache.http.protocol.HttpCoreContext; 073import org.apache.http.protocol.HttpProcessor; 074import org.apache.http.protocol.HttpRequestExecutor; 075import org.apache.http.protocol.ImmutableHttpProcessor; 076import org.apache.http.protocol.RequestTargetHost; 077import org.apache.http.protocol.RequestUserAgent; 078import org.apache.http.util.Args; 079import org.apache.http.util.EntityUtils; 080 081/** 082 * ProxyClient can be used to establish a tunnel via an HTTP proxy. 083 */ 084@SuppressWarnings("deprecation") 085public class ProxyClient { 086 087 private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; 088 private final ConnectionConfig connectionConfig; 089 private final RequestConfig requestConfig; 090 private final HttpProcessor httpProcessor; 091 private final HttpRequestExecutor requestExec; 092 private final ProxyAuthenticationStrategy proxyAuthStrategy; 093 private final HttpAuthenticator authenticator; 094 private final AuthState proxyAuthState; 095 private final AuthSchemeRegistry authSchemeRegistry; 096 private final ConnectionReuseStrategy reuseStrategy; 097 098 /** 099 * @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}