GuzzleHttp PSR-7 消息实现库深入指南:流、静态 API 与 URI 工具全解析
2026/9/16 23:52:27 网站建设 项目流程

GuzzleHttp PSR-7 消息实现库深入指南:流、静态 API 与 URI 工具全解析

【免费下载链接】mailcow-dockerizedmailcow: dockerized - 🐮 + 🐋 = 💕项目地址: https://gitcode.com/GitHub_Trending/ma/mailcow-dockerized

本文以 mailcow-dockerized 项目内置的第三方依赖guzzlehttp/psr7为对象,完整讲解其 PSR-7 消息实现、十余种流(Stream)实现与装饰器、Message/Header/Query/Utils/MimeType静态 API,以及基于 RFC 3986 的 URI 解析、归一化与比较工具。读完本文,你将掌握如何用Utils::streamFor()快速构造流、如何用CachingStream/LimitStream等装饰器包装现有流、如何用Message::parseMessage()解析原始 HTTP 报文、如何用UriResolver/UriNormalizer处理相对引用,并能顺畅迁移到 2.x 静态 API 的写法。

该库以 vendor 形式存放在 data/web/inc/lib/vendor/guzzlehttp/psr7 目录下,是 mailcow Web 界面(data/web/inc/lib)中 Guzzle HTTP 客户端生态的基础依赖,仓库锁定的版本为 2.4.5(见 composer.lock 中guzzlehttp/psr7条目)。

一、库定位与安装

本仓库是一个完整的 PSR-7 HTTP 消息实现,同时附带多个流装饰器(stream decorators)以及查询字符串解析等实用功能。所谓 PSR-7,是 PHP-FIG 制定的 HTTP 消息接口规范,定义了RequestInterfaceResponseInterfaceStreamInterfaceUriInterface等标准接口,让不同 HTTP 客户端、框架之间可以互换消息对象。

在 mailcow 仓库中,它通过 Composer 依赖树被引入,与guzzlehttp/guzzleguzzlehttp/promises共同构成完整的 HTTP 客户端栈(见 vendor/guzzlehttp 目录)。安装方式为标准 Composer 命令:

composer require guzzlehttp/psr7

从 composer.json 可以看到其依赖约束:

  • PHP^7.2.5 || ^8.0
  • psr/http-factory: ^1.0(PSR-17 工厂接口)
  • psr/http-message: ^1.0(PSR-7 消息接口)
  • ralouphie/getallheaders: ^3.0(跨平台获取全部请求头)

同时它向 Composer 生态声明providepsr/http-factory-implementationpsr/http-message-implementation,即本库本身就是这两个 PSR 接口的实现提供方。

二、Stream 实现体系

PSR-7 的StreamInterface是消息体的抽象。本包提供了一系列流实现与装饰器,覆盖内存缓冲、文件延迟打开、限流读取、多重流拼接等典型场景。所有类都位于GuzzleHttp\Psr7命名空间,源码集中在 src 目录。

2.1 AppendStream:顺序拼接多个流

GuzzleHttp\Psr7\AppendStream依次从多个流中读取数据,适合把"前缀 + 主体 + 后缀"组合成一个连续流而不必在内存中拼接字符串:

use GuzzleHttp\Psr7; $a = Psr7\Utils::streamFor('abc, '); $b = Psr7\Utils::streamFor('123.'); $composed = new Psr7\AppendStream([$a, $b]); $composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); echo $composed; // abc, 123. Above all listen to me.

其实现见 AppendStream.php:内部维护一个流队列与当前游标,read()时若当前流耗尽则自动切换到下一个流,eof()在所有流都耗尽后才返回 true。

2.2 BufferStream:带高水位线的可写缓冲流

GuzzleHttp\Psr7\BufferStream提供可写可读的内存缓冲。它的独特之处是暴露hwm(high water mark,高水位线)元数据:当缓冲区超过设定的水位时,write()开始返回false,以此向下游写入方发出"请放慢速度"的信号,实现背压(backpressure)语义:

use GuzzleHttp\Psr7; // 当缓冲区超过 1024 字节时,write() 将开始返回 false $buffer = new Psr7\BufferStream(1024);

实现见 BufferStream.php,getMetadata('hwm')可查询配置的高水位值。

2.3 CachingStream:让不可定位的流支持 seek

GuzzleHttp\Psr7\CachingStream解决"非 seekable 流需要回退重读"的问题,典型场景是重定向(redirect)后需要把请求体 rewind 重新发送。它把已读过的字节缓存在 PHP 临时流中:先缓存在内存,超出阈值后落盘,从而在seek()回退时直接读缓存而无需重新请求远端:

use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); $stream = new Psr7\CachingStream($original); $stream->read(1024); echo $stream->tell(); // 1024 $stream->seek(0); echo $stream->tell(); // 0

实现见 CachingStream.php,内部使用php://temp流存储已读数据。

2.4 DroppingStream:超出容量即丢弃写入

GuzzleHttp\Psr7\DroppingStream是一个"限流漏斗"装饰器:当底层流已写满设定大小后,后续write()的数据被静默丢弃,适用于固定大小的循环缓冲区等场景:

use GuzzleHttp\Psr7; // 创建空流 $stream = Psr7\Utils::streamFor(); // 当流超过 10 字节时开始丢弃新写入的数据 $dropping = new Psr7\DroppingStream($stream, 10); $dropping->write('01234567890123456789'); echo $stream; // 0123456789

实现见 DroppingStream.php。

2.5 FnStream:用函数表组合流

GuzzleHttp\Psr7\FnStream允许用"函数哈希表"来组合出一个流实现,无需为简单的扩展点创建具体类,非常便于测试与快速扩展:

use GuzzleHttp\Psr7; $stream = Psr7\Utils::streamFor('hi'); $fnStream = Psr7\FnStream::decorate($stream, [ 'rewind' => function () use ($stream) { echo 'About to rewind - '; $stream->rewind(); echo 'rewound!'; } ]); $fnStream->rewind(); // Outputs: About to rewind - rewound!

实现见 FnStream.php。

2.6 InflateStream:透明解压 zlib / gzip 内容

GuzzleHttp\Psr7\InflateStream使用 PHP 的zlib.inflate过滤器,将 zlib(HTTP deflate,RFC 1950)或 gzip(RFC 1952)压缩的内容流式解压。实现原理:先把输入流转换为 PHP 流资源,追加zlib.inflate过滤器,再转换回 Guzzle 流对象供调用方使用。当服务器返回Content-Encoding: deflate/gzip的响应体时非常有用。实现见 InflateStream.php。

2.7 LazyOpenStream:延迟打开文件

GuzzleHttp\Psr7\LazyOpenStream直到第一次真正的 IO 操作(读或写)发生时才打开文件,避免"构造了流却从未使用"时白白占用文件句柄:

use GuzzleHttp\Psr7; $stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); // 此时文件尚未被打开…… echo $stream->read(10); // 直到真正读取时才打开文件并读取

实现见 LazyOpenStream.php,__construct仅保存路径与模式,createStream()在首次 IO 时惰性触发。

2.8 LimitStream:读取流的子区间

GuzzleHttp\Psr7\LimitStream用于读取既有流的子集或切片,例如把大文件拆成小块分片上传(如 S3 multipart upload API):

use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); echo $original->getSize(); // >>> 1048576 // 将 body 限制为 1024 字节,并从字节 2048 开始读取 $stream = new Psr7\LimitStream($original, 1024, 2048); echo $stream->getSize(); // >>> 1024 echo $stream->tell(); // >>> 0

实现见 LimitStream.php,构造参数依次为(原流, 最大长度, 偏移量)。

2.9 MultipartStream:流式 multipart/form-data

GuzzleHttp\Psr7\MultipartStream读取时返回符合 multipart 规范的字节流,用于构造multipart/form-data请求体,实现见 MultipartStream.php。它内部也借助了其他流(如 LimitStream)来按元素切片,从而做到"整体流式、不必把整个表单载入内存"。

2.10 NoSeekStream:禁掉 seek 能力的包装流

GuzzleHttp\Psr7\NoSeekStream包装一个流并禁止 seek。对只允许顺序读写的下游逻辑,这种包装可以强制约束使用方式:

use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor('foo'); $noSeek = new Psr7\NoSeekStream($original); echo $noSeek->read(3); // foo var_export($noSeek->isSeekable()); // false $noSeek->seek(0); var_export($noSeek->read(3)); // NULL

实现见 NoSeekStream.php:isSeekable()恒为 false,seek()RuntimeException(示例中捕获后 read 返回 NULL 是异常被吞掉后的演示效果)。

2.11 PumpStream:由回调驱动的只读流

GuzzleHttp\Psr7\PumpStream提供只读流,数据由 PHP callable 动态"泵"出。调用 callable 时,PumpStream 会把本次请求读取的字节数传给它;callable 可以忽略该数值、返回更少或更多的字节。多返回的数据会在内部缓冲,直到被后续read()排空。callable 在无更多数据时必须返回false,这是流结束的信号。实现见 PumpStream.php。

三、编写自定义流装饰器:StreamDecoratorTrait

得益于GuzzleHttp\Psr7\StreamDecoratorTrait,创建流装饰器非常简单:该 trait 提供了对Psr\Http\Message\StreamInterface全部方法的默认实现——全部转发(proxy)到底层流。开发者只需use该 trait 并覆盖需要定制的方法即可。

例如,希望在流读到最后一个字节时调用某个函数,只需重写read()

use Psr\Http\Message\StreamInterface; use GuzzleHttp\Psr7\StreamDecoratorTrait; class EofCallbackStream implements StreamInterface { use StreamDecoratorTrait; private $callback; private $stream; public function __construct(StreamInterface $stream, callable $cb) { $this->stream = $stream; $this->callback = $cb; } public function read($length) { $result = $this->stream->read($length); // 到达 EOF 时触发回调 if ($this->eof()) { call_user_func($this->callback); } return $result; } }

该装饰器可套在任何既有流上使用:

use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor('foo'); $eofStream = new EofCallbackStream($original, function () { echo 'EOF!'; }); $eofStream->read(2); $eofStream->read(1); // echoes "EOF!" $eofStream->seek(0); $eofStream->read(3); // echoes "EOF!"

trait 实现位于 StreamDecoratorTrait.php,本包内CachingStreamDroppingStreamLimitStreamNoSeekStream等装饰器正是这一模式的官方范例。

四、StreamWrapper:PSR-7 流与 PHP 流资源互转

如果需要把 PSR-7 流当作 PHP 原生流资源使用(例如传给只接受 resource 的 PHP 函数),可以使用GuzzleHttp\Psr7\StreamWrappergetResource()方法:

use GuzzleHttp\Psr7\StreamWrapper; $stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); $resource = StreamWrapper::getResource($stream); echo fread($resource, 6); // outputs hello!

实现见 StreamWrapper.php,它通过注册guzzlehttp-psr7-stream流协议包装器实现双向桥接,也提供getResource()之外的流包装/解包能力。

五、静态 API 全览

自 1.7.0 起,库引入了GuzzleHttp\Psr7命名空间下的静态方法体系,用于规避全局函数在"全局副本与本地副本共存"时的命名冲突问题。以下按类逐一说明。

5.1Message:HTTP 消息级操作

源码见 Message.php。

Message::toString(MessageInterface $message): string— 返回 HTTP 消息的完整字符串表示:

$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); echo GuzzleHttp\Psr7\Message::toString($request);

Message::bodySummary(MessageInterface $message, int $truncateAt = 120): string|null— 获取消息体的简短摘要;当响应不可打印(如二进制体)时返回null。默认截断长度为 120 字符。

Message::rewindBody(MessageInterface $message): void— 尝试回绕消息体,失败时抛异常。只有当tell()返回值不为 0(即当前位置不在开头)时才会真正执行 rewind。

Message::parseMessage(string $message): array— 将 HTTP 消息字符串解析为关联数组,包含三个键:

  • "start-line":消息起始行(请求行或状态行);
  • "headers":键为头部名、值为字符串数组的关联数组;
  • "body":消息体字符串。

Message::parseRequestUri(string $path, array $headers): string— 根据请求消息构造 URI。

Message::parseRequest(string $message): Request— 将请求消息字符串解析为Request对象。

Message::parseResponse(string $message): Response— 将响应消息字符串解析为Response对象。

5.2Header:头部解析工具

源码见 Header.php。

Header::parse(string|array $header): array— 把包含;分隔数据的头部值数组解析为"键值对"关联数组的数组。当某个参数只有键没有值时,会注入一个空字符串''作为其值。

Header::splitList(string|string[] $header): string[]— 把定义为逗号分隔列表的 HTTP 头拆分为单个值:

$knownEtags = Header::splitList($request->getHeader('if-none-match'));

典型适用头部包括acceptcache-controlif-none-match等。

Header::normalize(string|array $header): array(已废弃)— 把可能含逗号分隔值的头部数组转换为无逗号分隔值的数组。注意:此方法已被废弃,官方推荐改用 API 更干净、文档更完善的Header::splitList()

5.3Query:查询字符串解析与构建

源码见 Query.php。

Query::parse(string $str, int|bool $urlEncoding = true): array— 将查询字符串解析为关联数组。同一键出现多个值时,该键的值会合并为数组。此方法不会把 PHP 风格的嵌套数组解析为多维关联数组(例如foo[a]=1&foo[b]=2会被解析为['foo[a]' => '1', 'foo[b]' => '2'])。

Query::build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string— 由键值对数组构建查询字符串。可以直接用parse()的返回值反哺build()。与http_build_query()不同,此方法在遇到数组值时不会修改传入的键。

5.4Utils:流与请求通用工具

源码见 Utils.php,这是最常用的工具类。

Utils::caselessRemove(iterable<string> $keys, array $data): array— 从数据中按键名大小写不敏感地移除指定项。例如移除Host头部时,无论数据中是host还是HOST都会被命中。

Utils::copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void— 把源流内容复制到目标流,直到读满指定字节数。实现中以 8192 字节为缓冲块循环读写;maxLen-1时复制整个流。

Utils::copyToString(StreamInterface $stream, int $maxLen = -1): string— 把流内容复制为字符串。实现中以 1MB(1048576 字节)为读取块,避免小缓冲导致的多次系统调用。

Utils::hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string— 计算流的滚动哈希,基于 PHPhash_init系列函数。源码实现(Utils.php)会先记录当前tell()位置,必要时 rewind 到开头,逐块hash_updatehash_final,最后seek回原位置——因此不会破坏调用方的读取位置

Utils::modifyRequest(RequestInterface $request, array $changes): RequestInterface— 克隆并修改请求,减少"为一次小改动克隆多次消息"的开销。支持以下变更键:

变更键类型作用
methodstring修改 HTTP 方法
set_headersarray设置给定头部
remove_headersarray移除给定头部
bodymixed设置请求体
uriUriInterface设置 URI
querystring设置 URI 的查询字符串
versionstring设置协议版本

源码实现(Utils.php)有一个值得注意的细节:当changes中提供新uri且带 host 时,会自动同步Host头;若端口不是该 scheme 的标准端口(http 为 80、https 为 443),还会把端口拼进Host头。

Utils::readLine(StreamInterface $stream, int $maxLength = null): string— 从流中读取一行,最多不超过最大缓冲长度。

Utils::streamFor(...)— 根据输入类型创建新流,是构造流的最常用入口。签名:

public static function streamFor( resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = [] ): StreamInterface

$options支持两个键:

  • metadata:自定义元数据数组;
  • size:流的预期大小。

$resource各类型的处理规则:

  • Psr\Http\Message\StreamInterface:原样返回;
  • string:创建以该字符串为内容的流对象;
  • resource:创建包装该 PHP 流资源的流对象;
  • Iterator:创建只读流包装该可迭代对象。每次读取时,迭代器数据先填充缓冲区直到满足请求的读取大小;后续读取优先消费缓冲区,耗尽后再对底层迭代器调用next,直到迭代完毕;
  • __toString()的对象:先强转为字符串再创建流;
  • NULL:返回空流对象;
  • callable:创建只读流,调用时传入"建议读取字节数";callable 可返回任意字节数,但无更多数据时必须返回false。流对象会持续调用 callable 直到凑够请求的字节数,多余字节会缓冲供后续读取使用。

代码示例:

$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));

Utils::tryFopen(string $filename, string $mode): resource— 安全地按文件名打开 PHP 流资源。普通fopen失败时只会发出 PHP warning,此方法通过临时错误处理器把失败转换为异常抛出,便于在严格错误模式下安全编程。

Utils::tryGetContents(resource $stream): string— 安全地读取流全部内容。同理,stream_get_contents失败时会把 warning 转为异常。

Utils::uriFor(string|UriInterface $uri): UriInterface— 为给定值返回UriInterface。已传UriInterface时原样返回,传字符串时构造Uri对象。

5.5MimeType:文件名与 MIME 类型映射

源码见 MimeType.php。

  • MimeType::fromFilename(string $filename): string|null— 通过文件扩展名判断 MIME 类型;
  • MimeType::fromExtension(string $extension): string|null— 把文件扩展名映射为 MIME 类型。

六、从函数 API 升级到静态 API

静态 API 于 1.7.0 引入,用于解决包在全局副本与本地副本之间函数冲突的问题;函数 API 已在 2.0.0 中移除。官方提供迁移对照表:

原函数替代方法
strMessage::toString
uri_forUtils::uriFor
stream_forUtils::streamFor
parse_headerHeader::parse
normalize_headerHeader::normalize
modify_requestUtils::modifyRequest
rewind_bodyMessage::rewindBody
try_fopenUtils::tryFopen
copy_to_stringUtils::copyToString
copy_to_streamUtils::copyToStream
hashUtils::hash
readlineUtils::readLine
parse_requestMessage::parseRequest
parse_responseMessage::parseResponse
parse_queryQuery::parse
build_queryQuery::build
mimetype_from_filenameMimeType::fromFilename
mimetype_from_extensionMimeType::fromExtension
_parse_messageMessage::parseMessage
_parse_request_uriMessage::parseRequestUri
get_message_body_summaryMessage::bodySummary
_caseless_removeUtils::caselessRemove

七、附加 URI 方法

除了标准的Psr\Http\Message\UriInterface实现类GuzzleHttp\Psr7\Uri(源码见 Uri.php),本库还以静态方法形式提供了大量 URI 增强工具。

7.1 URI 类型判定

UriInterface实例要么是绝对 URI(有 scheme),要么是相对引用(relative reference)。根据 RFC 3986 Section 4.2,相对引用又分为几类:

  • 网络路径引用(network-path):如//example.com/path
  • 绝对路径引用(absolute-path):如/path
  • 相对路径引用(relative-path):如subpath

对应判定方法(均位于GuzzleHttp\Psr7\Uri):

  • Uri::isAbsolute(UriInterface $uri): bool— 是否为绝对 URI,即带有 scheme;
  • Uri::isNetworkPathReference(UriInterface $uri): bool— 是否以双斜杠//开头的相对引用;
  • Uri::isAbsolutePathReference(UriInterface $uri): bool— 是否以单斜杠/开头的相对引用;
  • Uri::isRelativePathReference(UriInterface $uri): bool— 是否不以斜杠开头的相对引用;
  • Uri::isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool— 是否为同文档引用。所谓同文档引用,指除 fragment 外与基准 URI 完全相同的 URI。未给基准 URI 时,只有空 URI 引用(除 fragment 外)才算同文档引用。

7.2 URI 组件操作

  • Uri::isDefaultPort(UriInterface $uri): bool— 判断 URI 是否使用了当前 scheme 的默认端口。getPort()可能返回 null(表示默认端口),此方法可不依赖具体实现地判断端口是否为标准端口。
  • Uri::composeComponents($scheme, $authority, $path, $query, $fragment): string— 按 RFC 3986 Section 5.3 由各组件组合出 URI 引用字符串。通常无需手动调用,它被__toString()间接使用。
  • Uri::fromParts(array $parts): UriInterface— 由parse_url返回的组件哈希创建 URI 对象。
  • Uri::withQueryValue(UriInterface $uri, $key, $value): UriInterface— 创建带指定查询字符串值的新 URI。与给定键完全匹配的既有查询项会被移除并替换为新键值对;valuenull时只设置键不带值,如"key"而非"key=value"
  • Uri::withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface— 与withQueryValue()行为相同,但接受key => value关联数组批量设置。
  • Uri::withoutQueryValue(UriInterface $uri, $key): UriInterface— 创建移除指定查询键的新 URI。

7.3 跨源检测:UriComparator

GuzzleHttp\Psr7\UriComparator提供判断修改后的 URL 是否应被视为跨源(cross-origin)的方法:

  • UriComparator::isCrossOrigin(UriInterface $original, UriInterface $modified): bool— 判断修改后的 URL 相对原始 URL 是否跨源。这在实现安全跳转、CORS 逻辑时很有价值。源码见 UriComparator.php。

7.4 引用解析:UriResolver

GuzzleHttp\Psr7\UriResolver提供按 RFC 3986 Section 5 在基准 URI 上下文中解析 URI 引用的方法——这也是浏览器根据当前请求 URI 解析网页内链接时所做的运算。源码见 UriResolver.php。

  • UriResolver::resolve(UriInterface $base, UriInterface $rel): UriInterface— 把相对 URI 转换为基于基准 URI 解析出的新 URI。
  • UriResolver::removeDotSegments(string $path): string— 按 RFC 3986 Section 5.2.4 从路径中移除点段(...)。
  • UriResolver::relativize(UriInterface $base, UriInterface $target): UriInterface— 返回目标 URI 相对基准 URI 的相对引用,是resolve()的逆操作,满足恒等式(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))。典型用途:以当前请求 URI 为基准,在文档中生成相对链接以减小文档体积,或制作可独立下载的文档归档:
$base = new Uri('http://example.com/a/b/'); echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.

7.5 归一化与比较:UriNormalizer

GuzzleHttp\Psr7\UriNormalizer提供按 RFC 3986 Section 6 归一化与比较 URI 的方法。源码见 UriNormalizer.php。

UriNormalizer::normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface— 返回归一化后的 URI。PSR-7 的UriInterface已保证 scheme 与 host 小写化,此方法通过$flags位掩码追加可选归一化:

常量行为示例
PRESERVING_NORMALIZATIONS默认值,仅包含保持语义的归一化
CAPITALIZE_PERCENT_ENCODING百分号编码三元组内字母统一大写http://example.org/a%c2%b1bhttp://example.org/a%C2%B1b
DECODE_UNRESERVED_CHARACTERS解码非保留字符的百分号编码(ALPHA、DIGIT、-._~http://example.org/%7Eusern%61me/http://example.org/~username/
CONVERT_EMPTY_PATHhttp/https URI 的空路径转为/http://example.orghttp://example.org/
REMOVE_DEFAULT_HOST移除 scheme 的默认 host(仅filescheme 定义默认 hostlocalhostfile://localhost/myfilefile:///myfile
REMOVE_DEFAULT_PORT移除 scheme 的默认端口http://example.org:80/http://example.org/
REMOVE_DOT_SEGMENTS移除多余的 dot 段(相对路径引用中的不移除,以免改变语义)http://example.org/../a/b/../c/./d.htmlhttp://example.org/a/c/d.html
REMOVE_DUPLICATE_SLASHES相邻多个斜杠合并为一个(%2F编码斜杠不处理)http://example.org//foo///bar.htmlhttp://example.org/foo/bar.html
SORT_QUERY_PARAMETERS查询参数按键名按字母序排序(可能改变语义,不安全)?lang=en&article=fred?article=fred&lang=en

UriNormalizer::isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool— 判断两个 URI 是否等价。比较前会先按给定位掩码自动归一化。该方法也接受相对 URI 引用并返回其等价性——前提是它们针对同一基准 URI 解析;若基准不同,相对引用的等价判断没有意义。

八、版本支持策略

版本状态PHP 版本
1.x仅安全修复>=5.4,<8.1
2.x最新^7.2.5 \|\| ^8.0

mailcow 仓库锁定的 2.4.5 属于 2.x 主线,其 Composer 约束与上方 2.x 的 PHP 版本要求一致(见 composer.json 与 composer.lock)。

九、安全与许可

若在本包中发现安全漏洞,请通过邮件联系 Tidelift 的安全邮箱,所有漏洞会被及时处理;在修复方案公布前,请勿公开披露安全问题。

本库采用 MIT License 发布(许可文件见 LICENSE),可以在商业项目中自由使用与分发。此外,Guzzle 维护者通过 Tidelift Subscription 为开源依赖提供商业支持与维护服务。

十、小结:在本仓库中的定位

  • 依赖层级guzzlehttp/psr7位于 data/web/inc/lib/vendor/guzzlehttp/psr7,与guzzlehttp/guzzleguzzlehttp/promises并列,是 mailcow Web 界面 HTTP 客户端栈的地基;
  • 核心价值:为 Guzzle 客户端提供符合 PSR-7 标准的消息对象、可组合的流抽象与 URI 处理工具,其Utils::streamFor()Message::parseMessage()UriNormalizer::normalize()等方法也适合在非 Guzzle 的 PHP 项目中独立使用;
  • 版本:仓库锁定 2.4.5,采用 2.x 静态 API(函数 API 已移除),升级 1.x 代码时请参考第六节迁移对照表。

【免费下载链接】mailcow-dockerizedmailcow: dockerized - 🐮 + 🐋 = 💕项目地址: https://gitcode.com/GitHub_Trending/ma/mailcow-dockerized

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询