blob: c7d2e63c12c998c09fa88eebc36270bdcd0c8ed1 [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.sync;
import java.io.IOException;
import java.net.Socket;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.RouteInfo.LayerType;
import org.apache.hc.client5.http.RouteInfo.TunnelType;
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
import org.apache.hc.client5.http.auth.AuthExchange;
import org.apache.hc.client5.http.auth.AuthSchemeProvider;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.ChallengeType;
import org.apache.hc.client5.http.auth.Credentials;
import org.apache.hc.client5.http.config.AuthSchemes;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory;
import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory;
import org.apache.hc.client5.http.impl.auth.SPNegoSchemeFactory;
import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
import org.apache.hc.client5.http.impl.protocol.DefaultAuthenticationStrategy;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.protocol.RequestClientConnControl;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ConnectionReuseStrategy;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.config.CharCodingConfig;
import org.apache.hc.core5.http.config.H1Config;
import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.StatusLine;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.protocol.HttpCoreContext;
import org.apache.hc.core5.http.protocol.HttpProcessor;
import org.apache.hc.core5.http.protocol.RequestTargetHost;
import org.apache.hc.core5.http.protocol.RequestUserAgent;
import org.apache.hc.core5.util.Args;
/**
* ProxyClient can be used to establish a tunnel via an HTTP proxy.
*/
public class ProxyClient {
private final HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
private final RequestConfig requestConfig;
private final HttpProcessor httpProcessor;
private final HttpRequestExecutor requestExec;
private final AuthenticationStrategy proxyAuthStrategy;
private final HttpAuthenticator authenticator;
private final AuthExchange proxyAuthExchange;
private final Lookup<AuthSchemeProvider> authSchemeRegistry;
private final ConnectionReuseStrategy reuseStrategy;
/**
* @since 5.0
*/
public ProxyClient(
final HttpConnectionFactory<ManagedHttpClientConnection> connFactory,
final H1Config h1Config,
final CharCodingConfig charCodingConfig,
final RequestConfig requestConfig) {
super();
this.connFactory = connFactory != null ? connFactory : new ManagedHttpClientConnectionFactory(h1Config, charCodingConfig, null, null);
this.requestConfig = requestConfig != null ? requestConfig : RequestConfig.DEFAULT;
this.httpProcessor = new DefaultHttpProcessor(
new RequestTargetHost(), new RequestClientConnControl(), new RequestUserAgent());
this.requestExec = new HttpRequestExecutor();
this.proxyAuthStrategy = new DefaultAuthenticationStrategy();
this.authenticator = new HttpAuthenticator();
this.proxyAuthExchange = new AuthExchange();
this.authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.BASIC, new BasicSchemeFactory())
.register(AuthSchemes.DIGEST, new DigestSchemeFactory())
.register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true))
.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true))
.build();
this.reuseStrategy = new DefaultConnectionReuseStrategy();
}
/**
* @since 4.3
*/
public ProxyClient(final RequestConfig requestConfig) {
this(null, null, null, requestConfig);
}
public ProxyClient() {
this(null, null, null, null);
}
public Socket tunnel(
final HttpHost proxy,
final HttpHost target,
final Credentials credentials) throws IOException, HttpException {
Args.notNull(proxy, "Proxy host");
Args.notNull(target, "Target host");
Args.notNull(credentials, "Credentials");
HttpHost host = target;
if (host.getPort() <= 0) {
host = new HttpHost(host.getHostName(), 80, host.getSchemeName());
}
final HttpRoute route = new HttpRoute(
host,
this.requestConfig.getLocalAddress(),
proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN);
final ManagedHttpClientConnection conn = this.connFactory.createConnection(null);
final HttpContext context = new BasicHttpContext();
ClassicHttpResponse response;
final ClassicHttpRequest connect = new BasicClassicHttpRequest("CONNECT", host.toHostString());
final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(proxy), credentials);
// Populate the execution context
context.setAttribute(HttpCoreContext.HTTP_REQUEST, connect);
context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
context.setAttribute(HttpClientContext.CREDS_PROVIDER, credsProvider);
context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.requestConfig);
this.requestExec.preProcess(connect, this.httpProcessor, context);
for (;;) {
if (!conn.isOpen()) {
final Socket socket = new Socket(proxy.getHostName(), proxy.getPort());
conn.bind(socket);
}
this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, this.proxyAuthExchange, context);
response = this.requestExec.execute(connect, conn, context);
final int status = response.getCode();
if (status < 200) {
throw new HttpException("Unexpected response to CONNECT request: " + response);
}
if (this.authenticator.isChallenged(proxy, ChallengeType.PROXY, response, this.proxyAuthExchange, context)) {
if (this.authenticator.prepareAuthResponse(proxy, ChallengeType.PROXY, response,
this.proxyAuthStrategy, this.proxyAuthExchange, context)) {
// Retry request
if (this.reuseStrategy.keepAlive(connect, response, context)) {
// Consume response content
final HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
} else {
conn.close();
}
// discard previous auth header
connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
} else {
break;
}
} else {
break;
}
}
final int status = response.getCode();
if (status > 299) {
// Buffer response content
final HttpEntity entity = response.getEntity();
final String responseMessage = entity != null ? EntityUtils.toString(entity) : null;
conn.close();
throw new TunnelRefusedException("CONNECT refused by proxy: " + new StatusLine(response), responseMessage);
}
return conn.getSocket();
}
}