You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何fopen摘要认证请求仅约1/50能正常返回结果?

问题:PHP fopen实现摘要认证时响应内容偶尔无法获取

摘要认证流程分为两步:先发起请求收到401 Unauthorized响应,从响应头提取nonce等参数,计算摘要后再发起带认证头的请求。使用PHP的fopen实现该流程时,出现异常:首次运行时$response无输出,仅约每50次刷新页面才会出现正常响应内容;但用file_get_contents()可以正常实现功能,需要排查解决fopen的问题。

实现代码

# first query
$resource = fopen($url, 'r');
p($http_response_header);

# get nonce
preg_match('/nonce="([^"]+)"/', $http_response_header[1], $matches);
$nonce = $matches[1];

# calculate request for digest
$digest = calculateDigest($username, $password, $realm, $nonce, $uri, $method, $qop, $nc, $cnonce);

// HTTP header settings
$options = [
    'http' => [
        'method' => 'GET',
        'header' => "Authorization: Digest username=\"{$username}\", realm=\"{$realm}\", nonce=\"{$nonce}\", uri=\"{$uri}\", response=\"{$digest}\", opaque=\"{$opaque}\", qop={$qop}, nc={$nc}, cnonce=\"{$cnonce}\"",
    ],
];

# second request
$resource = fopen($url, 'r', false, stream_context_create($options));
p($http_response_header);

// Read data from the file (URL)
while (!feof($resource)) {
    $response = fgets($resource);
}

// Closing the resource
fclose($resource);

p($response);

首次运行输出

Warning: fopen(http://myip/cgi-bin/mediaFileFind.cgi?action=factory.create): Failed to open stream: HTTP request failed! HTTP/1.1 401 Unauthorized in C:\OSPanel\domains\cam.local\uploader.php on line 54
Array
(
    [0] => HTTP/1.1 401 Unauthorized
    [1] => WWW-Authenticate: Digest realm="Login to bd72fbb2e0734a3cb18d4cb40504cdf0", qop="auth", nonce="2142598378", opaque="1a067f2162e6693bed2d111d38af7bee229a316f"
    [2] => Connection: close
    [3] => CONTENT-LENGTH: 0
)
Array
(
    [0] => HTTP/1.1 200 OK
    [1] => X-XSS-Protection: 1;mode=block
    [2] => X-Frame-Options: SAMEORIGIN
    [3] => Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval'
    [4] => Strict-Transport-Security: max-age=604800; includeSubDomains
    [5] => Content-type: text/plain;charset=utf-8
    [6] => CONNECTION: close
    [7] => CONTENT-LENGTH: 19
)

此时$response无输出。

偶尔正常的输出

...
    [5] => Content-type: text/plain;charset=utf-8
    [6] => CONNECTION: close
    [7] => CONTENT-LENGTH: 19
)
result=2134173032

问题排查与解决方案

1. 禁用持久化连接

fopen默认尝试复用HTTP连接,但第一次请求返回Connection: close,第二次请求可能因连接复用异常导致内容读取失败。在流上下文里明确禁用持久化:

$options = [
    'http' => [
        'method' => 'GET',
        'header' => "Authorization: Digest username=\"{$username}\", realm=\"{$realm}\", nonce=\"{$nonce}\", uri=\"{$uri}\", response=\"{$digest}\", opaque=\"{$opaque}\", qop={$qop}, nc={$nc}, cnonce=\"{$cnonce}\"",
        'persistent' => false,
        'protocol_version' => '1.1'
    ],
];

2. 修复内容读取逻辑

原代码中$response会被fgets反复覆盖,且未处理读取失败的情况,改为累积读取内容并校验流有效性:

$response = '';
if ($resource !== false) {
    while (!feof($resource)) {
        $chunk = fgets($resource);
        if ($chunk !== false) {
            $response .= $chunk;
        }
    }
    fclose($resource);
}
p($response);

3. 抑制首次请求的警告

首次fopen触发的401警告不影响流程,可通过@抑制避免干扰:

$resource = @fopen($url, 'r');

4. 确保摘要参数完全匹配

从第一次响应头中完整提取realm和opaque,避免参数不匹配导致服务器返回空内容:

// 提取realm
preg_match('/realm="([^"]+)"/', $http_response_header[1], $realm_matches);
$realm = $realm_matches[1];
// 提取opaque
preg_match('/opaque="([^"]+)"/', $http_response_header[1], $opaque_matches);
$opaque = $opaque_matches[1];

内容的提问来源于stack exchange,提问作者accountnujen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:49:52