Return 文件 PHP Slim REST API 从 Guzzle 调用到另一个 REST API
Return file in PHP Slim REST API from a Guzzle call to another REST API
更新
这似乎在某种程度上与输出时流的读取有关。 Slim用来输出body的函数是这样的,其中$body实现了StreamInterface,$this->responseChunkSize为4096:
$amountToRead = $body->getSize();
while ($amountToRead > 0 && !$body->eof()) {
$length = min($this->responseChunkSize, $amountToRead);
$data = $body->read($length);
echo $data;
$amountToRead -= strlen($data);
if (connection_status() !== CONNECTION_NORMAL) {
break;
}
}
似乎 $body->eof() 调用(它只是 PHP 的 feof() 函数的包装器)正在返回 true,即使尚未读取完整文件。不知道为什么会这样。我还验证了如果我只是对文件执行 fopen() 并从中创建 Stream,然后 运行 相同的代码,则不会发生这种情况。只有当流是通过 Guzzle 进行的外部 REST API 调用的产物时才会发生。
原版Post
我有一个使用 Slim (v4.4) 构建的服务,它使用 returns 一个文件的 Guzzle (v6.5.3) 调用外部 REST API。这是 Windows 中的 运行ning,网络服务器是 IIS/FastCGI(我知道,不寻常)。 PHP 版本是 7.3.10。从 Slim 到外部 REST API 的调用可以很好地检索文件,但是当我的应用程序调用该服务时,一些文件会损坏,根据我在文件大小中看到的情况,似乎有些数据会丢失。从服务到外部 REST 的调用 API 相当简单:
$file_response = $guzzleClient->request('GET', "{$base_url}/docs/{$file_id}", [
'headers' => [
'Authorization' => "token {$token}"
]
]);
以上调用工作正常并且 returns 文件正确,我可以将其显示到屏幕或使用 Guzzle 中的 'sink' 选项保存到文件,它工作正常。但是当我尝试调用包装该调用的服务时,它失败了。我尝试了几件事。首先,我只是按原样返回响应,因为它无论如何都符合所需的接口。我的 Slim 路线是这样的:
$app->group('/files', function (Group $group) {
$group->get('/{file_id}', GetFileAction::class);
});
GetFileAction class 有一个这样的方法:
public function __invoke(Request $request, Response $response, $args): Response {
...Guzzle request returning $file_response here...
return $file_response;
}
我的应用也在使用 Guzzle 调用服务,调用如下所示:
$guzzleClient->request(
'GET',
"{$base_url}/files/{$file_id}",
[
'auth' => [$username, $password],
'sink' => $file_path
]
);
我想知道在 Slim 中返回 Guzzle 响应是否会导致一些意外结果,所以我尝试在服务中返回这个:
return $response->withBody(new \Slim\Psr7\Stream($file_response->getBody()->detach()));
同样的结果。显然,如果 运行 遇到这个完全相同的问题的人可以提供帮助,那就太好了,但如果没有一些关于如何尝试调试流处理的指示可能会有所帮助。
我已经确认这与 feof() 函数返回 true 的奇怪问题有关,即使它没有读取完整文件。我想出的解决方案涉及创建一个与默认 Slim 4 不同的响应发射器(大部分相同)并覆盖 emitBody 函数,使其不依赖于 feof()。我是这样做的:
$length = min($this->responseChunkSizeCopy, $amountToRead);
while ($amountToRead > 0 && ($data = $body->read($length)) !== false) {
echo $data;
$amountToRead -= $length;
$length = min($this->responseChunkSizeCopy, $amountToRead);
if (connection_status() !== CONNECTION_NORMAL) {
break;
}
}
到目前为止,根据我的测试,它运行良好。我不知道为什么 feof() 没有按预期工作,也没有真正找到任何似乎专门解决它的东西。也许这是 Windows 特定的事情,并且由于 PHP 在 Windows 上不太常见,所以这并不常见。但是将此解决方案留在这里以防它可以帮助某人。
我正在尝试实现类似的目标——使用 Slim 通过 Guzzle 客户端代理传入请求并将其转发到另一个服务——并且在返回 Guzzle 响应时遇到了类似的问题。
在我的例子中,问题是其他服务在响应中错误地返回了 Transfer-Encoding: chunked
header。
您的情况可能有所不同,但解决方案是在返回的响应中用正确的 Content-Length
header 替换它:
return $response
->withoutHeader('Transfer-Encoding')
->withHeader('Content-Length', $response->getBody()->getSize());
更新
这似乎在某种程度上与输出时流的读取有关。 Slim用来输出body的函数是这样的,其中$body实现了StreamInterface,$this->responseChunkSize为4096:
$amountToRead = $body->getSize();
while ($amountToRead > 0 && !$body->eof()) {
$length = min($this->responseChunkSize, $amountToRead);
$data = $body->read($length);
echo $data;
$amountToRead -= strlen($data);
if (connection_status() !== CONNECTION_NORMAL) {
break;
}
}
似乎 $body->eof() 调用(它只是 PHP 的 feof() 函数的包装器)正在返回 true,即使尚未读取完整文件。不知道为什么会这样。我还验证了如果我只是对文件执行 fopen() 并从中创建 Stream,然后 运行 相同的代码,则不会发生这种情况。只有当流是通过 Guzzle 进行的外部 REST API 调用的产物时才会发生。
原版Post
我有一个使用 Slim (v4.4) 构建的服务,它使用 returns 一个文件的 Guzzle (v6.5.3) 调用外部 REST API。这是 Windows 中的 运行ning,网络服务器是 IIS/FastCGI(我知道,不寻常)。 PHP 版本是 7.3.10。从 Slim 到外部 REST API 的调用可以很好地检索文件,但是当我的应用程序调用该服务时,一些文件会损坏,根据我在文件大小中看到的情况,似乎有些数据会丢失。从服务到外部 REST 的调用 API 相当简单:
$file_response = $guzzleClient->request('GET', "{$base_url}/docs/{$file_id}", [
'headers' => [
'Authorization' => "token {$token}"
]
]);
以上调用工作正常并且 returns 文件正确,我可以将其显示到屏幕或使用 Guzzle 中的 'sink' 选项保存到文件,它工作正常。但是当我尝试调用包装该调用的服务时,它失败了。我尝试了几件事。首先,我只是按原样返回响应,因为它无论如何都符合所需的接口。我的 Slim 路线是这样的:
$app->group('/files', function (Group $group) {
$group->get('/{file_id}', GetFileAction::class);
});
GetFileAction class 有一个这样的方法:
public function __invoke(Request $request, Response $response, $args): Response {
...Guzzle request returning $file_response here...
return $file_response;
}
我的应用也在使用 Guzzle 调用服务,调用如下所示:
$guzzleClient->request(
'GET',
"{$base_url}/files/{$file_id}",
[
'auth' => [$username, $password],
'sink' => $file_path
]
);
我想知道在 Slim 中返回 Guzzle 响应是否会导致一些意外结果,所以我尝试在服务中返回这个:
return $response->withBody(new \Slim\Psr7\Stream($file_response->getBody()->detach()));
同样的结果。显然,如果 运行 遇到这个完全相同的问题的人可以提供帮助,那就太好了,但如果没有一些关于如何尝试调试流处理的指示可能会有所帮助。
我已经确认这与 feof() 函数返回 true 的奇怪问题有关,即使它没有读取完整文件。我想出的解决方案涉及创建一个与默认 Slim 4 不同的响应发射器(大部分相同)并覆盖 emitBody 函数,使其不依赖于 feof()。我是这样做的:
$length = min($this->responseChunkSizeCopy, $amountToRead);
while ($amountToRead > 0 && ($data = $body->read($length)) !== false) {
echo $data;
$amountToRead -= $length;
$length = min($this->responseChunkSizeCopy, $amountToRead);
if (connection_status() !== CONNECTION_NORMAL) {
break;
}
}
到目前为止,根据我的测试,它运行良好。我不知道为什么 feof() 没有按预期工作,也没有真正找到任何似乎专门解决它的东西。也许这是 Windows 特定的事情,并且由于 PHP 在 Windows 上不太常见,所以这并不常见。但是将此解决方案留在这里以防它可以帮助某人。
我正在尝试实现类似的目标——使用 Slim 通过 Guzzle 客户端代理传入请求并将其转发到另一个服务——并且在返回 Guzzle 响应时遇到了类似的问题。
在我的例子中,问题是其他服务在响应中错误地返回了 Transfer-Encoding: chunked
header。
您的情况可能有所不同,但解决方案是在返回的响应中用正确的 Content-Length
header 替换它:
return $response
->withoutHeader('Transfer-Encoding')
->withHeader('Content-Length', $response->getBody()->getSize());