凌峰创科服务平台

HttpClient如何配置代理服务器?

HttpClient 是 Java 生态中最流行的 HTTP 客户端库之一,它提供了非常灵活和强大的代理配置功能,无论是简单的 HTTP 代理、需要身份验证的代理,还是 SOCKS 代理,HttpClient 都能很好地支持。

HttpClient如何配置代理服务器?-图1
(图片来源网络,侵删)

核心概念:RoutePlannerHttpClientContext

在深入代码之前,理解 HttpClient 中代理的两个核心组件很重要:

  1. RoutePlanner (路由规划器):这是 HttpClient 决定是否使用代理以及使用哪个代理的“大脑”,默认情况下,它会遵循标准的 Java 系统属性(如 http.proxyHost, https.proxyHost)以及目标服务器的响应(如果服务器返回 3xx 重定向或 407 Proxy Authentication Required)。
  2. HttpClientContext (HTTP客户端上下文):这是一个可变的对象,用于在单个请求执行过程中传递状态信息,包括路由信息、认证凭据、Cookie 等,当你需要为特定请求强制使用代理时,通常会在这个上下文中设置。

基本代理配置(HTTP/HTTPS 代理)

这是最常见的场景,假设你有一个代理服务器地址和端口。

使用 SystemDefaultRoutePlanner(推荐)

这是最简单的方法,它直接读取并使用 Java 的标准系统属性来配置代理,你不需要在代码中创建复杂的对象。

步骤:

HttpClient如何配置代理服务器?-图2
(图片来源网络,侵删)
  1. 在启动 Java 应用程序之前,设置系统属性。
  2. 创建 HttpClient 时,使用 SystemDefaultRoutePlanner

示例代码:

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.routing.SystemDefaultRoutePlanner;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.hc.core5.net.Proxy;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class SimpleProxyExample {
    public static void main(String[] args) throws IOException, URISyntaxException {
        // --- 方式 A: 通过 JVM 启动参数设置 ---
        // java -Dhttp.proxyHost=your_proxy_host -Dhttp.proxyPort=8080 -Dhttps.proxyHost=your_proxy_host -Dhttps.proxyPort=8080 YourClassName
        // --- 方式 B: 在代码中设置系统属性 (推荐用于测试) ---
        // System.setProperty("http.proxyHost", "your_proxy_host");
        // System.setProperty("http.proxyPort", "8080");
        // System.setProperty("https.proxyHost", "your_proxy_host");
        // System.setProperty("https.proxyPort", "8080");
        // 创建一个使用系统默认代理的路由规划器
        SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(ProxySelector.getDefault());
        // 使用这个路由规划器构建 HttpClient
        try (CloseHttpClient httpClient = HttpClients.custom()
                .setRoutePlanner(routePlanner)
                .build()) {
            // 创建一个 HTTP GET 请求
            // URIBuilder 可以帮助你安全地构建 URL
            URI uri = new URIBuilder("http://httpbin.org/get")
                    .addParameter("user-agent", "My-HttpClient-App")
                    .build();
            HttpGet httpGet = new HttpGet(uri);
            System.out.println("Executing request to: " + httpGet.getUri());
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                System.out.println("Response status: " + response.getCode());
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    String result = EntityUtils.toString(entity);
                    System.out.println("Response body: " + result);
                    EntityUtils.consume(entity); // 确保实体内容被完全消费,释放连接
                }
            }
        }
    }
}

如何运行:

  • 启动参数方式:编译后,在命令行运行:
    java -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=3128 -Dhttps.proxyHost=proxy.example.com -Dhttps.proxyPort=3128 YourClassName
  • 代码设置方式:直接取消注释 System.setProperty(...) 部分并填入你的代理信息。

需要身份验证的代理

如果代理服务器需要用户名和密码,你需要使用 ProxyAuthenticationStrategyCredentialsProvider

核心组件:

HttpClient如何配置代理服务器?-图3
(图片来源网络,侵删)
  • CredentialsProvider:用于存储用户名和密码凭据。
  • ProxyAuthenticationStrategy:一个认证策略,当收到 407 Proxy Authentication Required 响应时,它会自动从 CredentialsProvider 中查找凭据并重试请求。

示例代码:

import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.CloseHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.routing.SystemDefaultRoutePlanner;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.hc.core5.net.Proxy;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class AuthenticatedProxyExample {
    public static void main(String[] args) throws IOException, URISyntaxException {
        String proxyHost = "your_proxy_host";
        int proxyPort = 8080;
        String proxyUser = "your_username";
        String proxyPass = "your_password";
        // 1. 创建凭据提供者
        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        // 设置代理作用域和凭据
        credentialsProvider.setCredentials(
                new AuthScope(proxyHost, proxyPort),
                new UsernamePasswordCredentials(proxyUser, proxyPass.toCharArray()));
        // 2. 创建路由规划器
        SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(ProxySelector.getDefault());
        // 3. 创建 HttpClient 并配置凭据提供者和认证策略
        try (CloseHttpClient httpClient = HttpClients.custom()
                .setDefaultCredentialsProvider(credentialsProvider)
                .setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy())
                .setRoutePlanner(routePlanner)
                .build()) {
            URI uri = new URIBuilder("http://httpbin.org/get").build();
            org.apache.hc.client5.http.classic.methods.HttpGet httpGet = new org.apache.hc.client5.http.classic.methods.HttpGet(uri);
            System.out.println("Executing request through authenticated proxy...");
            try (org.apache.hc.client5.http.impl.classic.CloseableHttpResponse response = httpClient.execute(httpGet)) {
                System.out.println("Response status: " + response.getCode());
                // ... 处理响应
            }
        }
    }
}

为单个请求设置代理

如果你不希望全局设置代理,而是希望只为某一个或几个特定的请求使用代理,可以使用 HttpClientContext

核心组件:

  • HttpRoute:表示一个从客户端到目标服务器的完整路由,包括是否经过代理。
  • RequestConfig:用于配置单个请求的执行参数,比如超时、连接请求、以及最重要的——代理。

示例代码:

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.hc.core5.net.Proxy;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class PerRequestProxyExample {
    public static void main(String[] args) throws IOException, URISyntaxException {
        String proxyHost = "your_proxy_host";
        int proxyPort = 8080;
        try (CloseHttpClient httpClient = HttpClients.createDefault()) {
            URI targetUri = new URIBuilder("http://httpbin.org/ip").build();
            HttpGet httpGet = new HttpGet(targetUri);
            // 1. 创建一个上下文对象
            HttpClientContext context = HttpClientContext.create();
            // 2. 创建一个代理地址
            HttpHost proxy = new HttpHost(proxyHost, proxyPort);
            // 3. 创建请求配置,并设置代理
            org.apache.hc.client5.http.config.RequestConfig config = org.apache.hc.client5.http.config.RequestConfig.custom()
                    .setProxy(proxy)
                    .build();
            // 4. 将配置应用到请求上下文中
            context.setRequestConfig(config);
            System.out.println("Executing request with a specific proxy...");
            // 5. 使用这个上下文执行请求
            try (CloseableHttpResponse response = httpClient.execute(httpGet, context)) {
                System.out.println("Response status: " + response.getCode());
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    String result = EntityUtils.toString(entity);
                    System.out.println("Response body: " + result);
                    EntityUtils.consume(entity);
                }
            }
        }
    }
}

这种方式非常灵活,适合在复杂的业务场景中动态选择代理。


SOCKS 代理支持

HttpClient 本身不直接实现 SOCKS 协议,但它可以与 Java 的 java.net.Proxy 集成,从 Java 7 开始,Proxy 类支持 SOCKS。

步骤:

  1. 创建一个 java.net.Proxy 对象,类型为 Proxy.Type.SOCKS
  2. 创建一个实现 org.apache.hc.client5.http.routing.HttpRoutePlanner 接口的自定义路由规划器。
  3. 在路由规划器的 determineRoute 方法中,返回一个通过 HttpHost.from(Proxy) 创建的路由。

示例代码:

import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.impl.classic.CloseHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.hc.core5.net.Proxy;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URISyntaxException;
public class SocksProxyExample {
    public static void main(String[] args) throws IOException, URISyntaxException {
        String socksHost = "127.0.0.1";
        int socksPort = 1080;
        // 1. 创建一个 SOCKS 代理选择器
        java.net.Proxy socksProxy = new java.net.Proxy(java.net.Proxy.Type.SOCKS, new InetSocketAddress(socksHost, socksPort));
        ProxySelector proxySelector = ProxySelector.of(null); // 清空默认代理
        proxySelector = new CustomProxySelector(proxySelector, socksProxy);
        // 2. 创建使用该代理选择器的路由规划器
        HttpRoutePlanner routePlanner = new SystemDefaultRoutePlanner(proxySelector);
        // 3. 构建 HttpClient
        try (CloseHttpClient httpClient = HttpClients.custom()
                .setRoutePlanner(routePlanner)
                .build()) {
            URI uri = new URIBuilder("http://httpbin.org/get").build();
            org.apache.hc.client5.http.classic.methods.HttpGet httpGet = new org.apache.hc.client5.http.classic.methods.HttpGet(uri);
            System.out.println("Executing request through SOCKS proxy...");
            try (org.apache.hc.client5.http.impl.classic.CloseableHttpResponse response = httpClient.execute(httpGet)) {
                System.out.println("Response status: " + response.getCode());
                // ... 处理响应
            }
        }
    }
    // 一个简单的 ProxySelector,它总是返回我们配置的 SOCKS 代理
    static class CustomProxySelector extends ProxySelector {
        private final ProxySelector defaultSelector;
        private final Proxy proxy;
        CustomProxySelector(ProxySelector defaultSelector, Proxy proxy) {
            this.defaultSelector = defaultSelector;
            this.proxy = proxy;
        }
        @Override
        public java.util.List<java.net.Proxy> select(URI uri) {
            // 总是返回 SOCKS 代理
            return java.util.Collections.singletonList(proxy);
        }
        @Override
        public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
            if (defaultSelector != null) {
                defaultSelector.connectFailed(uri, sa, ioe);
            }
        }
    }
}

注意:SOCKS 代理通常是无状态的,不处理 HTTP 认证,SOCKS 代理需要认证,你需要使用支持 SOCKS 认证的库(如 Netty 的 SocksAuthRequest)并结合 HttpClient 的自定义拦截器来实现,这会更复杂。


总结与最佳实践

场景 推荐方法 关键组件
全局、简单的 HTTP/HTTPS 代理 SystemDefaultRoutePlanner + JVM 系统属性 System.setProperty(), SystemDefaultRoutePlanner
全局、需要认证的代理 CredentialsProvider + ProxyAuthenticationStrategy BasicCredentialsProvider, ProxyAuthenticationStrategy
为单个请求设置代理 RequestConfig + HttpClientContext RequestConfig.custom().setProxy(), HttpClientContext
SOCKS 代理 自定义 ProxySelector + SystemDefaultRoutePlanner java.net.Proxy, 自定义 ProxySelector

最佳实践建议:

  1. 优先使用 RequestConfig:除非你确实需要全局代理,否则为单个请求设置代理是更清晰、更不易出错的做法。
  2. 管理连接池:始终使用 try-with-resources 来管理 HttpClientCloseableHttpResponse,确保资源被正确释放,在生产环境中,HttpClient 实例应该是单例的,不要为每个请求都创建一个新的。
  3. 处理异常:网络请求充满了不确定性,务必用 try-catch 块处理可能发生的 IOException 和其他异常。
  4. 消耗响应实体:在处理完 HttpEntity 后,调用 EntityUtils.consume(entity) 或使用 InputStream 流式处理,以确保连接可以被正确地返回到连接池中。
分享:
扫描分享到社交APP
上一篇
下一篇