跳到主要内容
NextProxyNextProxy文档

Java 与 PHP

Java 的 HttpClient、OkHttp、Apache HttpClient,以及 PHP 的 cURL 和 Guzzle 接入方式。

两种语言各有一个共同的坑:Java 的 HttpClient 默认不会把凭据发给代理,PHP 的 CURLOPT_PROXY 需要显式指定代理类型。下面都处理好了。

Java 11+ HttpClient

标准库自带,但代理认证需要一个 Authenticator,而且要打开一个系统属性:

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 NextProxyExample {

    private static final String GATEWAY_HOST = "GATEWAY_HOST";
    private static final int GATEWAY_PORT = 58971;
    private static final String PROXY_USER =
            "USERNAME-country-US-session-job1-time-30";
    private static final String PROXY_PASS = "PASSWORD";

    public static void main(String[] args) throws Exception {
        // 没有这一行,JDK 会拒绝对代理发送 Basic 凭据,表现为持续 407
        System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");

        HttpClient client = HttpClient.newBuilder()
                .proxy(ProxySelector.of(
                        new InetSocketAddress(GATEWAY_HOST, GATEWAY_PORT)))
                .authenticator(new Authenticator() {
                    @Override
                    protected PasswordAuthentication getPasswordAuthentication() {
                        return new PasswordAuthentication(
                                PROXY_USER, PROXY_PASS.toCharArray());
                    }
                })
                .connectTimeout(Duration.ofSeconds(15))
                .build();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(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());
    }
}

OkHttp

比标准库省事得多,凭据用 Authenticator 直接给:

java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;

import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class OkHttpExample {

    static OkHttpClient buildClient(String proxyUser, String proxyPass) {
        Proxy proxy = new Proxy(Proxy.Type.HTTP,
                new InetSocketAddress("GATEWAY_HOST", 58971));

        return new OkHttpClient.Builder()
                .proxy(proxy)
                .proxyAuthenticator((route, response) -> {
                    // 已经带过凭据还是 401/407 就别再重试,避免死循环
                    if (response.request().header("Proxy-Authorization") != null) {
                        return null;
                    }
                    return response.request().newBuilder()
                            .header("Proxy-Authorization",
                                    Credentials.basic(proxyUser, proxyPass))
                            .build();
                })
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();
    }

    public static void main(String[] args) throws IOException {
        OkHttpClient client = buildClient(
                "USERNAME-country-US-session-job1-time-30", "PASSWORD");

        Request request = new Request.Builder()
                .url("https://api.ipify.org?format=json")
                .build();

        try (Response response = client.newCall(request).execute()) {
            System.out.println(response.code() + " " + response.body().string());
        }
    }
}

OkHttp 的 SOCKS5

java
Proxy proxy = new Proxy(Proxy.Type.SOCKS,
        new InetSocketAddress("GATEWAY_HOST", 58971));

// SOCKS5 的凭据走 java.net.Authenticator,不是 proxyAuthenticator
java.net.Authenticator.setDefault(new java.net.Authenticator() {
    @Override
    protected java.net.PasswordAuthentication getPasswordAuthentication() {
        return new java.net.PasswordAuthentication(
                "USERNAME-country-US", "PASSWORD".toCharArray());
    }
});

OkHttpClient client = new OkHttpClient.Builder().proxy(proxy).build();

轮换 IP

OkHttp 的连接池会复用连接。想让每次都换 IP,要么每次新建客户端,要么调用 evictAll()

java
OkHttpClient client = buildClient("USERNAME-country-US", "PASSWORD");

for (int i = 0; i < 5; i++) {
    Request request = new Request.Builder()
            .url("https://api.ipify.org")
            .build();

    try (Response response = client.newCall(request).execute()) {
        System.out.println(response.body().string());
    }

    // 清空连接池,下一次会建新连接从而重新选路
    client.connectionPool().evictAll();
}

并发 + 每任务独立会话

java
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class ConcurrentExample {

    public static void main(String[] args) throws Exception {
        try (ExecutorService pool = Executors.newFixedThreadPool(8)) {
            List<?> futures = IntStream.range(0, 8)
                    .mapToObj(id -> pool.submit(() -> {
                        // session 值只允许 [A-Za-z0-9.-],用点号不用下划线
                        String user = "USERNAME-country-US-session-task."
                                + id + "-time-30";
                        OkHttpClient client =
                                OkHttpExample.buildClient(user, "PASSWORD");

                        Request request = new Request.Builder()
                                .url("https://api.ipify.org")
                                .build();
                        try (Response response =
                                     client.newCall(request).execute()) {
                            return id + " -> " + response.body().string();
                        }
                    }))
                    .toList();

            for (Object future : futures) {
                System.out.println(((java.util.concurrent.Future<?>) future).get());
            }
        }
    }
}

Apache HttpClient 5

java
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.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.entity.EntityUtils;

public class ApacheExample {

    public static void main(String[] args) throws Exception {
        HttpHost proxy = new HttpHost("http", "GATEWAY_HOST", 58971);

        BasicCredentialsProvider credentials = new BasicCredentialsProvider();
        credentials.setCredentials(
                new AuthScope(proxy),
                new UsernamePasswordCredentials(
                        "USERNAME-country-US-session-job1-time-30",
                        "PASSWORD".toCharArray()));

        try (CloseableHttpClient client = HttpClients.custom()
                .setProxy(proxy)
                .setDefaultCredentialsProvider(credentials)
                .build()) {

            HttpGet request = new HttpGet("https://api.ipify.org?format=json");
            client.execute(request, response -> {
                System.out.println(response.getCode() + " "
                        + EntityUtils.toString(response.getEntity()));
                return null;
            });
        }
    }
}

PHP 的 cURL 扩展

php
<?php

declare(strict_types=1);

function proxyRequest(string $url, string $proxyUser, string $proxyPass): string
{
    $handle = curl_init($url);

    curl_setopt_array($handle, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_PROXY          => 'GATEWAY_HOST:58971',
        // 必须显式指定类型,否则 libcurl 的默认值可能不是你想的那个
        CURLOPT_PROXYTYPE      => CURLPROXY_HTTP,
        CURLOPT_PROXYUSERPWD   => $proxyUser . ':' . $proxyPass,
        CURLOPT_CONNECTTIMEOUT => 15,
        CURLOPT_TIMEOUT        => 30,
    ]);

    $body = curl_exec($handle);
    if ($body === false) {
        $error = curl_error($handle);
        curl_close($handle);
        throw new RuntimeException('请求失败: ' . $error);
    }

    $status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
    curl_close($handle);

    if ($status !== 200) {
        throw new RuntimeException('目标返回 HTTP ' . $status);
    }

    return (string) $body;
}

echo proxyRequest(
    'https://api.ipify.org?format=json',
    'USERNAME-country-US-session-job1-time-30',
    'PASSWORD'
), PHP_EOL;

PHP 的 SOCKS5

php
curl_setopt_array($handle, [
    CURLOPT_PROXY        => 'GATEWAY_HOST:58971',
    // 让代理解析域名,等价于 socks5h://
    CURLOPT_PROXYTYPE    => CURLPROXY_SOCKS5_HOSTNAME,
    CURLOPT_PROXYUSERPWD => 'USERNAME-country-US:PASSWORD',
]);

Guzzle

shell
composer require guzzlehttp/guzzle
php
<?php

declare(strict_types=1);

require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

$proxyUser = 'USERNAME-country-US-session-job1-time-30';
$proxyPass = 'PASSWORD';
$proxy = sprintf('http://%s:%s@GATEWAY_HOST:58971',
    rawurlencode($proxyUser), rawurlencode($proxyPass));

$client = new Client([
    // http 和 https 都指到同一个明文代理,网关没有 TLS 入站
    'proxy'           => ['http' => $proxy, 'https' => $proxy],
    'timeout'         => 30,
    'connect_timeout' => 15,
    'http_errors'     => false,
]);

try {
    $response = $client->get('https://api.ipify.org?format=json');
    echo $response->getStatusCode(), ' ', $response->getBody(), PHP_EOL;
} catch (RequestException $exception) {
    echo '请求失败: ', $exception->getMessage(), PHP_EOL;
}

Guzzle 并发

php
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;

$password = 'PASSWORD';
$requests = static function () use ($password): Generator {
    for ($i = 0; $i < 8; $i++) {
        // session 值只允许 [A-Za-z0-9.-]
        $user = sprintf('USERNAME-country-US-session-task.%d-time-30', $i);
        $proxy = sprintf('http://%s:%s@GATEWAY_HOST:58971',
            rawurlencode($user), rawurlencode($password));

        yield new Request('GET', 'https://api.ipify.org') => [
            'proxy' => ['http' => $proxy, 'https' => $proxy],
        ];
    }
};

// Guzzle 的 Pool 不支持 per-request 换代理,所以按会话分批建客户端
for ($i = 0; $i < 8; $i++) {
    $user = sprintf('USERNAME-country-US-session-task.%d-time-30', $i);
    $proxy = sprintf('http://%s:%s@GATEWAY_HOST:58971',
        rawurlencode($user), rawurlencode($password));

    $client = new Client(['proxy' => ['http' => $proxy, 'https' => $proxy]]);
    echo $i, ' -> ', $client->get('https://api.ipify.org')->getBody(), PHP_EOL;
}

排错清单

语言现象原因
JavaHTTPS 一直 407,Authenticator 没被调用没设 jdk.http.auth.tunneling.disabledSchemes=""
JavaSOCKS5 凭据串了全局 Authenticator 是进程级的,改用 HTTP 协议
JavaIP 一直不变OkHttp 连接池复用,需要 evictAll()
PHP代理没生效没设 CURLOPT_PROXYTYPE
PHP凭据被截断密码放 URL 里没 rawurlencode
通用400invalid_proxy_parameters:参数格式、顺序或依赖错误
通用402traffic_exhausted / account_quota_exhausted / quota_exhausted:流量或额度不足
通用403account_disabled / account_expired / access_denied / target_denied:账号或策略拒绝
通用429connection_limit:降低并发和建连频率
通用502出口连接失败
通用503暂无可用线路或服务暂不可用
通用504建立代理连接超时

HTTP / CONNECT 失败可读取 X-NextProxy-Error 与 JSON 的 error.code;目标网站返回的状态和 CONNECT 握手错误需分别处理。完整映射见 错误码对照

这篇解决你的问题了吗?