ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

GuzzleHttp PSR-7 消息实现库深入指南:流、静态 API 与 URI 工具全解析 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 消息接口规范定义了RequestInterface、ResponseInterface、StreamInterface、UriInterface等标准接口让不同 HTTP 客户端、框架之间可以互换消息对象。在 mailcow 仓库中它通过 Composer 依赖树被引入与guzzlehttp/guzzle、guzzlehttp/promises共同构成完整的 HTTP 客户端栈见 vendor/guzzlehttp 目录。安装方式为标准 Composer 命令composer require guzzlehttp/psr7从 composer.json 可以看到其依赖约束PHP^7.2.5 || ^8.0psr/http-factory: ^1.0PSR-17 工厂接口psr/http-message: ^1.0PSR-7 消息接口ralouphie/getallheaders: ^3.0跨平台获取全部请求头同时它向 Composer 生态声明provide了psr/http-factory-implementation与psr/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提供可写可读的内存缓冲。它的独特之处是暴露hwmhigh water mark高水位线元数据当缓冲区超过设定的水位时write()开始返回false以此向下游写入方发出请放慢速度的信号实现背压backpressure语义use GuzzleHttp\Psr7; // 当缓冲区超过 1024 字节时write() 将开始返回 false $buffer new Psr7\BufferStream(1024);实现见 BufferStream.phpgetMetadata(hwm)可查询配置的高水位值。2.3 CachingStream让不可定位的流支持 seekGuzzleHttp\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过滤器将 zlibHTTP deflateRFC 1950或 gzipRFC 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 APIuse 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-dataGuzzleHttp\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.phpisSeekable()恒为 falseseek()抛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本包内CachingStream、DroppingStream、LimitStream、NoSeekStream等装饰器正是这一模式的官方范例。四、StreamWrapperPSR-7 流与 PHP 流资源互转如果需要把 PSR-7 流当作 PHP 原生流资源使用例如传给只接受 resource 的 PHP 函数可以使用GuzzleHttp\Psr7\StreamWrapper的getResource()方法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.1MessageHTTP 消息级操作源码见 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));典型适用头部包括accept、cache-control、if-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]1foo[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(iterablestring $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— 把流内容复制为字符串。实现中以 1MB1048576 字节为读取块避免小缓冲导致的多次系统调用。Utils::hash(StreamInterface $stream, string $algo, bool $rawOutput false): string— 计算流的滚动哈希基于 PHPhash_init系列函数。源码实现Utils.php会先记录当前tell()位置必要时 rewind 到开头逐块hash_update后hash_final最后seek回原位置——因此不会破坏调用方的读取位置。Utils::modifyRequest(RequestInterface $request, array $changes): RequestInterface— 克隆并修改请求减少为一次小改动克隆多次消息的开销。支持以下变更键变更键类型作用methodstring修改 HTTP 方法set_headersarray设置给定头部remove_headersarray移除给定头部bodymixed设置请求体uriUriInterface设置 URIquerystring设置 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::toStringuri_forUtils::uriForstream_forUtils::streamForparse_headerHeader::parsenormalize_headerHeader::normalizemodify_requestUtils::modifyRequestrewind_bodyMessage::rewindBodytry_fopenUtils::tryFopencopy_to_stringUtils::copyToStringcopy_to_streamUtils::copyToStreamhashUtils::hashreadlineUtils::readLineparse_requestMessage::parseRequestparse_responseMessage::parseResponseparse_queryQuery::parsebuild_queryQuery::buildmimetype_from_filenameMimeType::fromFilenamemimetype_from_extensionMimeType::fromExtension_parse_messageMessage::parseMessage_parse_request_uriMessage::parseRequestUriget_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\UriUri::isAbsolute(UriInterface $uri): bool— 是否为绝对 URI即带有 schemeUri::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。与给定键完全匹配的既有查询项会被移除并替换为新键值对value为null时只设置键不带值如key而非keyvalue。Uri::withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface— 与withQueryValue()行为相同但接受key value关联数组批量设置。Uri::withoutQueryValue(UriInterface $uri, $key): UriInterface— 创建移除指定查询键的新 URI。7.3 跨源检测UriComparatorGuzzleHttp\Psr7\UriComparator提供判断修改后的 URL 是否应被视为跨源cross-origin的方法UriComparator::isCrossOrigin(UriInterface $original, UriInterface $modified): bool— 判断修改后的 URL 相对原始 URL 是否跨源。这在实现安全跳转、CORS 逻辑时很有价值。源码见 UriComparator.php。7.4 引用解析UriResolverGuzzleHttp\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 归一化与比较UriNormalizerGuzzleHttp\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%b1b→http://example.org/a%C2%B1bDECODE_UNRESERVED_CHARACTERS解码非保留字符的百分号编码ALPHA、DIGIT、-、.、_、~http://example.org/%7Eusern%61me/→http://example.org/~username/CONVERT_EMPTY_PATHhttp/https URI 的空路径转为/http://example.org→http://example.org/REMOVE_DEFAULT_HOST移除 scheme 的默认 host仅filescheme 定义默认 hostlocalhostfile://localhost/myfile→file:///myfileREMOVE_DEFAULT_PORT移除 scheme 的默认端口http://example.org:80/→http://example.org/REMOVE_DOT_SEGMENTS移除多余的 dot 段相对路径引用中的不移除以免改变语义http://example.org/../a/b/../c/./d.html→http://example.org/a/c/d.htmlREMOVE_DUPLICATE_SLASHES相邻多个斜杠合并为一个%2F编码斜杠不处理http://example.org//foo///bar.html→http://example.org/foo/bar.htmlSORT_QUERY_PARAMETERS查询参数按键名按字母序排序可能改变语义不安全?langenarticlefred→?articlefredlangenUriNormalizer::isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations self::PRESERVING_NORMALIZATIONS): bool— 判断两个 URI 是否等价。比较前会先按给定位掩码自动归一化。该方法也接受相对 URI 引用并返回其等价性——前提是它们针对同一基准 URI 解析若基准不同相对引用的等价判断没有意义。八、版本支持策略版本状态PHP 版本1.x仅安全修复5.4,8.12.x最新^7.2.5 \|\| ^8.0mailcow 仓库锁定的 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/guzzle、guzzlehttp/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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进