经由 HTTP 代理的 HTTPS 请求可能先收到一个接受 CONNECT 的响应,随后才收到隧道内网站返回的响应。初次配置路由可参考 Guzzle 的按请求代理选项。第一个响应里的某个头,未必会出现在最终响应对象上。

若要用自选的 HTTP 端点复现,请从HTTP 代理列表中挑一个候选并确认它能接受 HTTPS 隧道。在 Guzzle 7.10 的 cURL 处理器路径下,把每次 header 回调保存为独立的块:

$blocks = [];
$client = new \GuzzleHttp\Client([
    'handler' => \GuzzleHttp\HandlerStack::create(
        new \GuzzleHttp\Handler\CurlHandler()
    ),
]);

$response = $client->get('https://www.example.com/', [
    'proxy' => 'http://proxy.example:8080',
    'curl' => [CURLOPT_SUPPRESS_CONNECT_HEADERS => false],
    'on_headers' => function (
        \Psr\Http\Message\ResponseInterface $part
    ) use (&$blocks) {
        $blocks[] = [
            'status' => $part->getStatusCode(),
            'reason' => $part->getReasonPhrase(),
            'headers' => $part->getHeaders(),
        ];
    },
]);

cURL 扩展必须提供 CURLOPT_SUPPRESS_CONNECT_HEADERS,该选项由 libcurl 7.54.0 引入。将其设为 false 后,CONNECT 响应头才会进入 cURL 的 header 回调。Guzzle 7.10 的处理器会为每个已完成的响应头块构建一个响应,并在该边界调用 on_headers。

请保持块的顺序。认证质询、重定向和信息类响应都可能产生额外的块;不要把全部取值压进一个数组,也不要仅凭状态码是 200 就给响应定性。

返回的响应代表已完成请求的响应,而 blocks 保留了回调收集到的观测。若隧道或 TLS 协商失败,请在调用方应用里处理抛出的 transfer 异常;此时可能根本不存在最终的网站响应。

该行为与上述 cURL 处理器绑定。自定义处理器或 stream 处理器没有义务暴露同样的代理握手回调。把捕获到的代理响应头当作诊断数据,并避免不必要地留存与账户相关的取值。

相关文章