接入指南
Java 代理设置:HttpClient 账号密码认证
JDK 自带的 HttpClient 通过 ProxySelector 获取代理,再由 Authenticator 回应代理的登录质询。有一个 JDK 默认值会挡路:HTTPS 隧道默认禁用 Basic 认证,所以第一次运行会得到 407,改一个系统属性才能通过。下面的示例都在 Temurin JDK 25 上运行过。
开始之前
- JDK 11 或更高版本。HttpClient 和用 java 命令直接运行单个源文件都是 Java 11 引入的;示例在 25 上测试。
- 一个空文件夹和一个终端,不需要 Maven 或 Gradle 项目。
- 控制台服务页面上列出的 HOST、PORT、USERNAME 和 PASSWORD。
你的连接信息
登录控制台,打开你购买的服务。服务页面上会显示该服务的主机、端口、用户名和密码。ProxyPanda 没有一个统一的固定地址,所以每次都请从那里复制。指南中的代码使用下面这些占位符,请逐一替换成你自己的值。
- HOST
- 该服务的代理地址,按服务页面上显示的原样填写。
- PORT
- 要连接的端口。请和主机一起复制,因为不同服务的端口可能不同。
- USERNAME
- 你的代理登录名。它与你登录控制台所用的邮箱是分开的。
- PASSWORD
- 请完整复制。住宅代理在控制台中选择的选项会附加在密码末尾,凭记忆手动输入的密码会丢失这些选项。
把你用来连接的 IP 地址加入服务的白名单,就可以不用用户名和密码。这时代码里只需要 HOST 和 PORT。
示例都使用 HTTP,它可以通过加密隧道访问 https 网站。SOCKS5 同样可用:住宅代理的端口两种都接受,ISP 和数据中心代理在控制台里切换协议。
步骤
检查 JDK 并新建文件夹
java -version 会显示负责编译和运行文件的版本。每个示例都是完整的源文件:保存到这个文件夹,用 java 加文件名启动。
terminal java -version mkdir proxycheck && cd proxycheck创建带代理和 Authenticator 的客户端
ProxySelector.of 把所有请求发往同一个代理地址。Authenticator 只在请求方类型为 PROXY 时交出登录信息,网站要求输入密码时拿不到代理的密码。connectTimeout 限制连上代理的时间,请求上的 timeout 限制等待响应的总时长。
CheckIp.java import java.net.Authenticator; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; public class CheckIp { public static void main(String[] args) throws Exception { HttpClient client = HttpClient.newBuilder() .proxy(ProxySelector.of(new InetSocketAddress("HOST", PORT))) .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() != RequestorType.PROXY) return null; return new PasswordAuthentication("USERNAME", "PASSWORD".toCharArray()); } }) .connectTimeout(Duration.ofSeconds(10)) .build(); HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.ipify.org?format=json")) .timeout(Duration.ofSeconds(30)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode() + " " + response.body()); } }允许隧道使用 Basic 认证后运行
JDK 的 conf/net.properties 把 jdk.http.auth.tunneling.disabledSchemes 设为 Basic,客户端因此不肯在建立 HTTPS 隧道的 CONNECT 请求里附上用户名和密码。在命令行里清空这个列表,登录信息就能发出,程序会打印 200 和 api.ipify.org 返回的 JSON。不加这个参数时,同一个文件打印的是 407 null。
terminal java -Djdk.http.auth.tunneling.disabledSchemes="" CheckIp.java或者在代码里清空这个属性
无法修改启动命令时,把 System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "") 写成 main 的第一行。后面的轮换示例就是这样做的。直接修改 conf/net.properties 里的那一行在测试中也有效,但这个 JDK 运行的所有程序都会受影响。
把请求分散到多个静态 IP
ISP 和数据中心服务给你的是一组固定地址。把它们写进 proxies.txt,每行一个 HOST:PORT:USERNAME:PASSWORD,然后用 java Rotate.java 启动。程序为每一行建一个 HttpClient,各自带一个 Authenticator,第 i 个请求交给第 i 除以列表长度取余对应的客户端。某个代理出错时会打印异常,循环继续往下走。
Rotate.java import java.net.Authenticator; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.ArrayList; import java.util.List; public class Rotate { static HttpClient clientFor(String line) { String[] p = line.trim().split(":", 4); return HttpClient.newBuilder() .proxy(ProxySelector.of(new InetSocketAddress(p[0], Integer.parseInt(p[1])))) .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() != RequestorType.PROXY) return null; return new PasswordAuthentication(p[2], p[3].toCharArray()); } }) .connectTimeout(Duration.ofSeconds(10)) .build(); } public static void main(String[] args) throws Exception { System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); List<HttpClient> clients = new ArrayList<>(); for (String line : Files.readAllLines(Path.of("proxies.txt"))) { if (!line.isBlank()) clients.add(clientFor(line)); } for (int i = 0; i < 6; i++) { HttpClient client = clients.get(i % clients.size()); HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.ipify.org")) .timeout(Duration.ofSeconds(30)) .build(); try { System.out.println(i + " " + client.send(request, HttpResponse.BodyHandlers.ofString()).body()); } catch (Exception e) { System.out.println(i + " " + e); } } } }
轮换 IP 与粘性 IP
HttpClient 会在请求之间保持隧道。测试中,同一个客户端的三次请求只经过一次 CONNECT,复用的隧道出口地址不变。这正适合“粘性 IP”以及 ISP、数据中心 IP。
住宅代理选“随机 IP”时,新隧道才会带来新地址。在 JDK 25 上加 -Djdk.httpclient.keepalive.timeout=0 启动后,三次请求各自建立了一条新隧道。每个请求新建一个 HttpClient 也能达到同样效果,只是每次都要重新握手。
HttpClient 创建后不可变,可以发送任意多个请求,所以启动时为每个代理建一个,之后共用即可。
常见错误与解决办法
程序打印 407 null
代理要求登录,而客户端没有提供,因为隧道仍然禁用 Basic。把 -Djdk.http.auth.tunneling.disabledSchemes="" 放在文件名前面:放在 CheckIp.java 后面时,它会作为参数传给 main,不起作用。
收到几次 407 之后抛出 IOException
Authenticator 已经回应,但代理一直拒绝它发送的登录信息。在测试代理上,客户端尝试了四次后抛出异常,JDK 25 给出的原因是一个看不出头绪的 NullPointerException。从服务页面重新粘贴 USERNAME 和 PASSWORD。
没有附带信息的 ConnectException
HOST 和 PORT 上没有任何程序接受连接。把两者和服务页面对照一遍,再到控制台确认服务仍然有效。
HttpConnectTimeoutException: HTTP connect timed out
连接或隧道没能按时建立。如果是响应太慢,请求超时过后抛出的会是 HttpTimeoutException。住宅出口是家庭网络,可能有延迟,给它们多一些时间,并只重试失败的网址。
其他接入指南
有不明白的词? 术语表用大白话解释。
卡在某一步了?
把命令和报错贴到 Discord,记得先删掉密码。那里的人大多遇到过这些错误。