后端【免费下载链接】psr7PSR-7 HTTP message library项目地址https://gitcode.com/gh_mirrors/ps/psr7点击查看免费下载PSR-7 规定 HTTP 消息的请求体与响应体统一以StreamInterface流的形式存在而guzzlehttp/psr7通过GuzzleHttp\Psr7\Utils静态工具类提供了一套创建流、复制流、计算流哈希、逐行读取以及安全打开/读取流资源的开箱即用方法。本文围绕 docs/stream-helpers.md 展开逐方法讲解签名、语义、限制与底层实现并结合 src/Utils.php 与 tests/UtilsTest.php 中的源码与测试用例做深入验证。读完本文你将能够熟练运用这 7 个工具方法处理日常 HTTP 消息体并理解其背后的超时检测与错误处理机制。流的具体实现与装饰器AppendStream、BufferStream、CachingStream等请参见 Streams and DecoratorsPSR-17 工厂的流式能力请参见 PSR-17 Factories。概览Utils 流工具全家桶所有方法均为Utils类的public static方法位于命名空间GuzzleHttp\Psr7类定义见 src/Utils.phpfinal class Utils构造函数为private纯静态工具类。Utils同时也是一个“口袋类”还承载了 URI 解析、请求修改、大小写无感比较等工具本文只聚焦与流相关的 7 个方法方法签名用途copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen -1): int把一个流的内容复制进另一个流返回复制字节数copyToString(StreamInterface $stream, int $maxLen -1): string把流内容读入字符串返回读取结果hash(StreamInterface $stream, string $algo, bool $rawOutput false): string基于hash_init系列函数计算流的滚动哈希readLine(StreamInterface $stream, ?int $maxLength null): string逐行读取可限制最大缓冲长度streamFor(resource\|string\|null\|StreamInterface\|callable\|\Iterator\|\Stringable $resource , array $options []): StreamInterface按输入类型创建流对象tryFopen(string $filename, string $mode): resource安全打开文件资源失败时抛异常而非警告tryGetContents(resource $stream): string安全读取流资源全部内容失败时抛异常而非警告其中前四个方法copyToStream、copyToString、hash、readLine在每次读取无进展时都会借助内部类StreamTimeout见 src/StreamTimeout.php检测 PHP 风格的超时元数据若检测到超时则抛出GuzzleHttp\Psr7\Exception\TimeoutException定义见 src/Exception/TimeoutException.php继承自RuntimeException。流的创建Utils::streamForstreamFor()是最常用的入口它根据传入的$resource类型返回对应的StreamInterface实现。其核心实现逻辑位于 src/Utils.php可归纳为如下分派规则Psr\Http\Message\StreamInterface原样返回不做任何包装源码中instanceof StreamInterface分支直接return $resource。string写入php://temp流并创建Stream对象。字符串始终被当作字面量正文处理即使它恰好是一个可调用函数的名字例如Utils::streamFor(strlen)返回的仍是内容为strlen的字符串流而不是 PumpStream这一点有专门测试testFactoryTreatsCallableStringAsStringBody佐证见 tests/UtilsTest.php。resource包装成Stream对象保留当前指针位置测试testKeepsPositionOfResource验证了先fseek到 10 再streamFor后tell()仍为 10。特殊的php://input会被读入php://temp后重新包装以规避该流在 PHP 中的怪异行为。Iterator包装为只读的PumpStream。每次读取时从迭代器取数据填充内部缓冲直到满足请求的读取长度。产出值会被转换为字符串分片字符串、整数、有限浮点数、布尔值、null与可字符串化对象均可转换非有限浮点数与其它值在读取时抛出UnexpectedValueException字符串化为空串的值会被跳过并继续推进迭代器。带__toString()的对象先转成字符串再按字符串创建流。null以及省略参数的默认值返回一个内容为空的php://temp流。callable可调用数组、闭包、可调用对象在无更早的资源/对象规则命中时包装为只读的PumpStream。调用时传入“建议读取字节数”可返回更少或更多字节必须返回非空字符串来提供数据返回false或null表示数据结束多余字节会被缓冲供后续读取使用。其它类型例如整数、浮点数、布尔值直接抛出InvalidArgumentException。$options是关联数组支持两个键metadata自定义元数据数组可通过getMetadata()读取size流的已知大小非负整数会交给PumpStream等实现通过getSize()返回。PumpStream的options处理见 src/PumpStream.phpsize经Integers::assertOptionalNonNegativeSize校验见 src/Integers.php非法值抛InvalidArgumentException。官方文档给出的典型示例$stream GuzzleHttp\Psr7\Utils::streamFor(foo); $stream GuzzleHttp\Psr7\Utils::streamFor(fopen(/path/to/file, r)); $generator function ($bytes) { for ($i 0; $i $bytes; $i) { yield ; } }; $stream GuzzleHttp\Psr7\Utils::streamFor($generator(100));调用可调用数组与可调用对象的测试见 tests/UtilsTest.php它们都断言结果为PumpStream且内容读取正确。更多关于流创建、元数据与装饰器行为的内容可参考 Streams and Decorators。流间复制Utils::copyToStreamcopyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen -1): int从源流读取数据写入目标流直到读完$maxLen个字节-1表示读完整条流返回实际复制字节数。实现位于 src/Utils.php关键行为分块读取内部按 8192 字节的缓冲区读取避免一次性把大流装入内存。测试testCopyToStreamReadsInChunksInsteadOfAllInMemory验证了复制 16394 字节时按 8192、8192、10 分三次读取见 tests/UtilsTest.php。短写重试私有方法writeAll()循环写入直到数据全部落盘如果目标流某次只写入了部分字节返回小于请求长度会继续写剩余部分。测试testCopyToStreamRetriesShortWrites中把每次write限制为 1 字节最终仍完整复制 6 字节并恰好写入 6 次。无进展即抛错目标流若返回 0 作为背压或丢弃信号——例如BufferStream达到高水位线write在缓冲不小于hwm时返回 0见 src/BufferStream.php或DroppingStream已满见 src/DroppingStream.php——writeAll()会抛出RuntimeException(Unable to write to stream)。测试testCopyToStreamThrowsWhenDestinationBufferStreamReachesHighWaterMark与testCopyToStreamThrowsWhenDestinationDroppingStreamIsFull分别验证了这两种场景。因此做全量复制时应使用普通可写流文件流或php://temp而不是背压型流。$maxLen边界0或小于-1的负数表示复制 0 字节见测试testCopyToStreamWithZeroMaxLenCopiesNothing、testCopyToStreamWithNegativeMaxLenOtherThanMinusOneCopiesNothing源流比$maxLen短时返回实际字节数testCopyToStreamReturnsActualBytesWhenSourceShorterThanMaxLen。超时检测读取与写入两侧都可能触发TimeoutException——源流读取超时抛“Unable to read from stream: timed out”目标流写入超时抛“Unable to write to stream: timed out”对应测试见 tests/UtilsTest.php。整数安全字节计数使用Integers::addsrc/Integers.php累加超过PHP_INT_MAX时抛OverflowException。因此文档明确提醒在 32 位 PHP 上无界复制超过PHP_INT_MAX字节的量无法用int返回值表达64 位 PHP 不受影响。读成字符串Utils::copyToStringcopyToString(StreamInterface $stream, int $maxLen -1): string把流内容读入字符串并返回。实现见 src/Utils.php$maxLen -1时循环读取直到eof()每次读取上限 1048576 字节1 MiB并追加到缓冲字符串读到空串即停止。指定$maxLen时最多读取$maxLen字节循环条件同时检查eof()与$len $maxLen。读取无进展且检测到超时元数据时抛TimeoutException。基本行为测试见 tests/UtilsTest.php对foobaz先整体读取返回foobazseek 回 0 后两次copyToString($s, 3)分别得到foo与baz再读返回空串。testCopyToStringThrowsWhenReadTimesOut则用FnStream模拟eof() false、read()返回空串、getMetadata(timed_out) true的场景断言抛出TimeoutException且消息为“Unable to read from stream: timed out”。滚动哈希Utils::hashhash(StreamInterface $stream, string $algo, bool $rawOutput false): string读取整条流计算哈希基于 PHP 的hash_init/hash_update/hash_final函数支持md5、crc32、sha256等任意 PHP 支持的算法。实现见 src/Utils.php需要注意的语义先记住当前位置若tell()大于 0 则先rewind()读完后再seek()回到原位置。因此哈希计算不会破坏调用方的读取位置——测试testCalculatesHashSeeksToOriginalPosition验证了先seek(4)再哈希计算后tell()仍为 4。分块更新每次以 1 MiB 为单位hash_update避免大流占用过多内存。对不可 seek 的流抛错如果流不可回绕例如NoSeekStreamhash()会因rewind/seek失败抛出RuntimeException测试testCalculatesHashThrowsWhenSeekFails。空流返回空值哈希读到空串即停止返回hash_final结果例如对空流求 md5 返回md5()测试testCalculatesHashStopsWhenReadReturnsEmptyString。超时读取无进展且检测到超时时抛TimeoutException消息为“Unable to calculate stream hash: timed out”。典型用法$stream GuzzleHttp\Psr7\Utils::streamFor(foobazbar); echo GuzzleHttp\Psr7\Utils::hash($stream, md5); // 等价于 md5(foobazbar)逐行读取Utils::readLinereadLine(StreamInterface $stream, ?int $maxLength null): string从流中读取一行直到遇到换行符\n或达到最大缓冲长度返回的字符串包含换行符本身。实现见 src/Utils.php每次read($stream, 1)读取 1 个字节并追加到缓冲遇到\n或$size $maxLength - 1时中断——注意文档语义是“最大缓冲长度$maxLength”实际返回的字节数为$maxLength - 1含换行符时恰好占满上限。例如readLine($s, 4)对12345\n返回123剩余的45\n由下一次调用读取测试见 tests/UtilsTest.phpEOF 时返回已缓冲内容缓冲为空则返回空串读取无进展且检测到超时时抛TimeoutException消息为“Unable to read line from stream: timed out”。完整行为测试见 tests/UtilsTest.php覆盖了逐行读取foo\nbaz\nbar、EOF 返回空串、模拟read()先返回h再返回等场景。安全打开文件Utils::tryFopentryFopen(string $filename, string $mode): resource与 PHP 原生fopen()等价区别在于失败时抛出异常而不是产生警告。实现见 src/Utils.php通过set_error_handler()注册临时错误处理器捕获fopen()触发的警告并构造RuntimeException随后restore_error_handler()恢复同时用try/catch兜底 PHP 8 可能直接抛出的\Throwable并附带$e作为异常链异常消息格式为Unable to open {filename} using mode {mode}: {errstr}文件名与模式经DiagnosticValue::escape转义见 src/DiagnosticValue.php防止换行等控制字符破坏日志。测试验证了三种失败场景都会抛出RuntimeException而非警告文件不存在、文件名含换行符异常消息中转义为\x0A、空文件名见 tests/UtilsTest.php。安全读取内容Utils::tryGetContentstryGetContents(resource $stream): string是stream_get_contents()的安全版本失败时抛出异常而非警告。实现见 src/Utils.php注册临时错误处理器捕获警告同样经DiagnosticValue::escape转义后包装为RuntimeException若stream_get_contents()返回false进一步用StreamTimeout::isResourceReadTimedOut()检查stream_get_meta_data()[timed_out]且feof()为假判断是否超时超时则抛TimeoutException否则抛RuntimeException即使成功读到了内容只要资源超时元数据为真也会抛TimeoutException部分读取后超时的情况。相关测试见 tests/UtilsTest.php覆盖了只写模式打开导致读取失败抛RuntimeException、已关闭资源读取失败、stream_get_contents返回false且资源超时抛TimeoutException、部分读取后超时抛TimeoutException等场景。超时检测机制TimeoutException 背后的原理文档反复强调“当 PHP 风格超时元数据可被检测到时抛出TimeoutException”其判定逻辑集中在内部类StreamTimeout见 src/StreamTimeout.php读取超时isReadTimedOut()检查getMetadata(timed_out) true且!eof()——即流报告超时但还没到文件末尾isResourceReadTimedOut()对原生资源使用stream_get_meta_data()[timed_out]与feof()做等价判断。写入超时isWriteTimedOut()只检查getMetadata(timed_out) true。探测失败被容忍getMetadata()、eof()探测本身抛异常或返回非布尔值时超时检测返回false不会掩盖原始的读取/写入异常。测试testCopyToStringIgnoresMetadataProbeFailure、testCopyToStringIgnoresNonBooleanTimedOutMetadata、testCopyToStringPreservesReadExceptionWhenMetadataProbeFails都验证了这一容错设计。这套机制把“读不出数据的普通 EOF”与“读卡住了”区分开来普通读取返回空串且无超时元数据时方法正常停止只有检测到timed_out元数据时才抛TimeoutException让调用方明确区分正常结束与超时失败。实战建议与边界提醒综合文档与源码使用这几个工具方法时有几点值得注意全量复制请使用常规可写流BufferStream达到高水位时write返回 0与DroppingStream已满时返回 0作为copyToStream的目标会触发RuntimeException需要完整复制到文件、php://temp等普通流。copyToString与readLine都按字节边界工作适合处理 ASCII 文本处理多字节字符时需注意按字节截断的可能性。hash()会重绕流只适合可 seek 的流普通文件流、php://temp流均可对PumpStream、NoSeekStream等不可 seek 流会抛RuntimeException。32 位 PHP 的计数上限copyToStream返回int无界复制超过PHP_INT_MAX字节在 32 位平台无法表达必要时改用$maxLen限长或直接使用 64 位环境。区分 EOF 与超时所有读取类方法都把“空串 timed_out元数据”当作超时处理捕获TimeoutException可以精确处理网络流卡死而不会误伤正常读完的情况。这些工具方法的测试覆盖位于 tests/UtilsTest.php流相关用例约从第 65 行到第 861 行需要了解流实现与装饰器的读者可继续阅读 Streams and Decorators需要了解消息工厂流式能力的读者可阅读 PSR-17 Factories消息整体处理可参考 Message Helpers。赞分享后端【免费下载链接】psr7PSR-7 HTTP message library项目地址https://gitcode.com/gh_mirrors/ps/psr7点击查看免费下载相关推荐PSR-7 流与装饰器全解析GuzzleHttp\Psr7 的流创建、内置装饰器与 PHP 资源桥接指南PSR 7 流与装饰器全解析GuzzleHttp\Psr7 的流创建、内置装饰器与 PHP 资源桥接指南 PSR 7 规范将 HTTP 请求与响应的 body后端PSR-7 项目 MIME 类型表再生成实战从 mime-db 到 GuzzleHttp\Psr7\MimeType 的完整流程PSR 7 项目 MIME 类型表再生成实战从 mime db 到 GuzzleHttp\Psr7\MimeType 的完整流程 本篇指南以 hack/REA后端复杂链表的复制哈希与拆分法双解完全指南复杂链表的复制哈希与拆分法双解完全指南 开篇你还在为随机指针复制头疼吗 当你在LeetCode上遇到「复杂链表复制」问题时是否曾因 random 指针的示例工程上一篇RegExr设计系统详解组件库与视觉一致性维护下一篇EvoMaster智能测试生成进化算法驱动的企业级API测试自动化解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考