如何使用Guzzle通过PUT请求分块写入请求体?
如何用Guzzle分块写入PUT请求的请求体
问题描述
我想使用Guzzle发起PUT请求并在请求体中发送数据,请问怎么像读取响应体那样分块写入请求体?
参考的响应体读取代码
while (!$body->eof()) { $data = $body_origin->read(1024*1024); }
我尝试的方案
$dest_stream = Psr7\Utils::streamFor(); $response_dest = $client_destination->request('PUT', $url_dest, [ 'stream' => true, 'body' => $dest_stream, 'read_timeout' => 100, 'Content-Type' => 'application/octet-stream', 'headers' => [ 'Content-Length' => '0' ] ]); while (!$data_to_send->eof()) { $data = $data_to_send->read(1024*1024); $dest_stream->write($data); }
解决方案
你当前的写法存在逻辑问题:调用request()方法后,Guzzle会立即发送请求,这时候再往$dest_stream里写入数据已经晚了,请求已完成,流内的内容不会被发送。
正确的思路是让Guzzle在发送请求时按需读取数据源,把分块读取$data_to_send的逻辑绑定到请求体流的读取操作上。下面是两种可行的实现方式:
方式一:使用可回调的流(推荐)
利用Psr7\Utils::streamFor()接收回调函数,Guzzle在需要请求体数据时会自动调用该回调获取数据:
use GuzzleHttp\Psr7\Utils; // $data_to_send为你的数据源流(如本地文件流、远程响应流等) $request_body = Utils::streamFor(function () use ($data_to_send) { if (!$data_to_send->eof()) { return $data_to_send->read(1024 * 1024); // 每次读取1MB } return false; // 返回false表示数据读取完毕 }); $response_dest = $client_destination->request('PUT', $url_dest, [ 'read_timeout' => 100, 'Content-Type' => 'application/octet-stream', // 无需手动设置Content-Length,Guzzle会自动处理: // 若数据源是可查找的流,自动计算长度;否则使用Transfer-Encoding: chunked分块传输 ]);
方式二:自定义PSR-7流
如果需要更精细的控制,可以实现自定义PSR-7流类,将分块读取逻辑封装在read()方法中:
use Psr\Http\Message\StreamInterface; class ChunkedRequestBody implements StreamInterface { private $source; private $chunkSize; public function __construct(StreamInterface $source, int $chunkSize = 1048576) { $this->source = $source; $this->chunkSize = $chunkSize; } // 核心实现:分块读取数据源 public function read($length) { return $this->source->read($this->chunkSize); } // 以下方法直接委托给数据源流实现 public function eof() { return $this->source->eof(); } public function getContents() { return $this->source->getContents(); } public function close() { return $this->source->close(); } public function detach() { return $this->source->detach(); } public function getSize() { return $this->source->getSize(); } public function tell() { return $this->source->tell(); } public function isSeekable() { return $this->source->isSeekable(); } public function seek($offset, $whence = SEEK_SET) { return $this->source->seek($offset, $whence); } public function rewind() { return $this->source->rewind(); } public function isWritable() { return false; } public function write($string) { throw new \RuntimeException('Cannot write to this stream'); } public function isReadable() { return true; } public function getMetadata($key = null) { return $this->source->getMetadata($key); } } // 使用自定义流 $request_body = new ChunkedRequestBody($data_to_send); $response_dest = $client_destination->request('PUT', $url_dest, [ 'read_timeout' => 100, 'Content-Type' => 'application/octet-stream', ]);
注意事项
- 不要手动设置
Content-Length: 0,这会告知服务器请求体为空,后续写入的数据不会生效。 stream => true参数用于流式读取响应体,和请求体无关,无需设置。
内容的提问来源于stack exchange,提问作者Danielzt
相关产品推荐
相关产品推荐

