ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

深入掌握 PSR-7:基于 OpenCart 内置 psr/http-message 的 HTTP 消息与流式操作实战

深入掌握 PSR-7:基于 OpenCart 内置 psr/http-message 的 HTTP 消息与流式操作实战 电商后端【免费下载链接】opencartA free shopping cart system. OpenCart is an open source PHP-based online e-commerce solution.项目地址https://gitcode.com/gh_mirrors/op/opencart点击查看免费下载PSR-7 定义了 PHP 生态中 HTTP 消息请求与响应的统一接口契约是 Guzzle、AWS SDK 等主流 HTTP 客户端实现互操作的基础。本文以 OpenCart 仓库内置的psr/http-message包位于upload/system/storage/vendor/psr/http-message为核心完整讲解 HTTP 头的增删改查、消息体的读写与流指针操作并结合仓库内的接口源码与 Guzzle 实现帮助你写出符合标准、可复用的 HTTP 消息处理代码。读完本文你将掌握 PSR-7 的不可变对象语义、Header 与 Stream 的全部操作手法以及常见的指针陷阱与规避方案。一、PSR-7 是什么HTTP 消息的标准接口契约PSR-7PHP Standard Recommendation 7定义了一套描述 HTTP 消息的接口。正如upload/system/storage/vendor/psr/http-message/docs/PSR7-Usage.md开头所述所有符合 PSR-7 的应用程序都遵守这些接口它们被创建出来是为了在中间件middleware实现之间建立统一标准。无论底层实现是哪个库只要实现这些接口行为就应该一致。该包的核心继承关系如下摘自原文档的说明RequestInterface、ServerRequestInterface、ResponseInterface都继承自MessageInterface因为请求Request与响应Response本质上都是 HTTP 消息HTTP Messages。 使用ServerRequestInterface时RequestInterface与Psr\Http\Message\MessageInterface的方法都被视为可用。在 OpenCart 仓库中该包以 Composer 依赖形式存在完整文件结构为接口定义upload/system/storage/vendor/psr/http-message/src/7 个接口文件文档upload/system/storage/vendor/psr/http-message/docs/PSR7-Interfaces.md方法速查、PSR7-Usage.md用法指南包说明upload/system/storage/vendor/psr/http-message/README.md需要特别强调的是README 中的原话psr/http-message本身并不是一个 HTTP 消息实现它只是描述 HTTP 消息的接口规范。真正提供实现的是guzzlehttp/psr7这类实现包——本仓库中就内置了完整的 Guzzle 实现upload/system/storage/vendor/guzzlehttp/psr7/src/包含Message.php、Request.php、ServerRequest.php、Response.php、Stream.php、Uri.php、UploadedFile.php等。而 OpenCart 的 Composer 自动加载器也把这些命名空间映射到位upload/system/storage/vendor/composer/autoload_psr4.php中Psr\Http\Message\命名空间同时指向psr/http-factory/src与psr/http-message/srcupload/system/storage/vendor/composer/installed.json中记录了psr/http-message的安装信息install-path 为../psr/http-message。1.1 七大接口一览PSR-7 规范一共定义了 7 个接口upload/system/storage/vendor/psr/http-message/docs/PSR7-Interfaces.md给出的对照表如下接口名职责描述Psr\Http\Message\MessageInterface一条 HTTP 消息的抽象表示请求与响应共有的部分Psr\Http\Message\RequestInterface客户端发出的请求outgoing, client-side requestPsr\Http\Message\ServerRequestInterface服务器接收到的请求incoming, server-side requestPsr\Http\Message\ResponseInterface服务器发出的响应outgoing, server-side responsePsr\Http\Message\StreamInterface描述一条数据流data streamPsr\Http\Message\UriInterface表示 URI 的值对象value objectPsr\Http\Message\UploadedFileInterface通过 HTTP 请求上传的文件的值对象1.2 不可变性Immutability原则这是 PSR-7 最核心的语义源码 docblock 中写得很明确upload/system/storage/vendor/psr/http-message/src/MessageInterface.phpMessages are considered immutable; all methods that might change state MUST be implemented such that they retain the internal state of the current message and return an instance that contains the changed state.即消息是不可变的。所有可能改变状态的方法如withHeader、withBody、withStatus都必须保留当前消息的内部状态并返回一个包含新状态的新实例而不是修改原对象。因此所有with*方法的返回值都必须被接收$response $response-withHeader(...)这一点在实际编码中是最容易踩的坑。二、接口方法速查表CheatsheetPSR7-Interfaces.md明确说明其用途是帮助在使用 PSR-7 时快速查找方法。为方便读者查阅以下完整保留各接口的方法清单。2.1MessageInterface方法请求与响应共有的方法方法名说明备注getProtocolVersion()获取 HTTP 协议版本如 1.0 或 1.1withProtocolVersion($version)返回设置新协议版本后的新消息实例getHeaders()获取全部 HTTP 头键为头名值为字符串数组保留原始大小写hasHeader($name)检查指定头是否存在头名大小写不敏感getHeader($name)获取单个头的值数组不存在时返回空数组getHeaderLine($name)获取单个头以逗号拼接的字符串不存在时返回空字符串withHeader($name, $value)返回设置/替换指定头后的新实例若原消息中已有该头则整体替换其值withAddedHeader($name, $value)返回向指定头追加值后的新实例头已存在则追加不存在则新建withoutHeader($name)返回移除指定头后的新实例头名大小写不敏感getBody()获取消息体返回实现StreamInterface的对象withBody(StreamInterface $body)返回设置新消息体后的新实例2.2RequestInterface方法包含MessageInterface全部方法另加方法名说明备注getRequestTarget()获取请求目标origin-form / absolute-form / authority-form / asterisk-formRFC 7230withRequestTarget($requestTarget)返回设置请求目标后的新实例getMethod()获取 HTTP 方法GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACERFC 7231及 PATCHRFC 5789withMethod($method)返回设置方法后的新实例getUri()获取 URI 实例withUri(UriInterface $uri, $preserveHost false)返回设置 URI 后的新实例2.3ServerRequestInterface方法包含RequestInterface全部方法另加方法名说明备注getServerParams()获取服务器参数通常来源于$_SERVERgetCookieParams()获取客户端发送的 Cookie通常来源于$_COOKIEwithCookieParams(array $cookies)返回设置 Cookie 后的新请求实例withQueryParams(array $query)返回设置查询字符串参数后的新实例getUploadedFiles()获取规范化后的文件上传数据withUploadedFiles(array $uploadedFiles)返回设置上传文件后的新实例getParsedBody()获取请求体中的参数withParsedBody($data)返回设置请求体参数后的新实例getAttributes()获取从请求派生的属性getAttribute($name, $default null)获取单个派生属性withAttribute($name, $value)返回设置派生属性后的新实例withoutAttribute($name)返回移除派生属性后的新实例2.4ResponseInterface方法包含MessageInterface全部方法另加方法名说明getStatusCode()获取响应状态码withStatus($code, $reasonPhrase )返回设置状态码可带原因短语后的新实例getReasonPhrase()获取状态码对应的原因短语2.5StreamInterface方法方法名说明__toString()从流头到尾读取全部数据为字符串close()关闭流及底层资源detach()将底层资源与流分离getSize()获取流大小未知则返回 nulltell()获取文件读写指针当前位置eof()是否已到流末尾isSeekable()流是否可定位seek($offset, $whence SEEK_SET)将指针定位到指定位置rewind()将指针定位到流开头等价于seek(0)isWritable()流是否可写write($string)向流写入数据返回写入字节数isReadable()流是否可读read($length)从流读取至多$length字节getContents()返回流中剩余内容组成的字符串getMetadata($key null)获取流元数据键与stream_get_meta_data()一致2.6UriInterface方法方法名说明getScheme()/withScheme($scheme)获取 / 设置 URI 协议schemegetAuthority()获取 authority 组件userinfo host portgetUserInfo()/withUserInfo($user, $password null)获取 / 设置用户信息getHost()/withHost($host)获取 / 设置主机getPort()/withPort($port)获取 / 设置端口getPath()/withPath($path)获取 / 设置路径getQuery()/withQuery($query)获取 / 设置查询字符串getFragment()/withFragment($fragment)获取 / 设置片段__toString()返回 URI 引用的字符串表示2.7UploadedFileInterface方法方法名说明getStream()获取表示上传文件的流moveTo($targetPath)将上传文件移动到新位置getSize()获取文件大小getError()获取与上传文件相关的错误码getClientFilename()获取客户端发送的文件名getClientMediaType()获取客户端发送的媒体类型三、HTTP 头的操作实战原文档docs/PSR7-Usage.md中的示例均假设$request是Psr\Http\Message\RequestInterface的对象$response是实现响应接口的对象原文档此处笔误写成RequestInterface实际应为ResponseInterface详见文末勘误。运行这些示例至少需要一个 PSR-7 实现包例如 zendframework/zend-diactoros、guzzlehttp/psr7、slim/slim 等所有实现的行为应一致。本仓库内置的就是guzzlehttp/psr7。3.1 向响应添加响应头$response-withHeader(My-Custom-Header, My Custom Message);注意withHeader返回新实例请务必接收返回值$response $response-withHeader(...)否则原响应对象不会有任何变化。3.2 向已有头追加值$response-withAddedHeader(My-Custom-Header, The second message);withAddedHeader与withHeader的区别在于若头已存在withAddedHeader保留旧值并追加新值而withHeader会整体替换旧值。二者都会返回新实例接口源码MessageInterface.php中对应 docblock 明确标注了这一点。3.3 检查头是否存在$request-hasHeader(My-Custom-Header); // 返回 false $response-hasHeader(My-Custom-Header); // 返回 true注意My-Custom-Header只被添加到了 Response 中因此对$request查询返回false对$response查询返回true。头名匹配是大小写不敏感的hasHeader的 docblock 明确要求使用 case-insensitive 比较。3.4 获取头值的逗号拼接字符串同样适用于请求// 从请求头中取值 $request-getHeaderLine(Content-Type); // 返回: text/html; charsetUTF-8 // 从响应头中取值 $response-getHeaderLine(My-Custom-Header); // 返回: My Custom Message; The second messagegetHeaderLine()将同一个头的所有值用逗号拼接成一个字符串。不过MessageInterface.php的 docblock 也提醒并非所有头都适合用逗号拼接表示例如 Cookie、Set-Cookie 等这类头应使用getHeader()自行选择分隔符拼接。3.5 获取头值的数组形式同样适用于请求// 从请求头中取值 $request-getHeader(Content-Type); // 返回: [text/html, charsetUTF-8] // 从响应头中取值 $response-getHeader(My-Custom-Header); // 返回: [My Custom Message, The second message]getHeader()返回字符串数组保留了每个值原本的形态如果头不存在则返回空数组。注意与getHeaderLine()的返回类型差异一个是string[]一个是string。3.6 从头中移除响应头// 从 Request 中移除头例如移除已废弃的 Content-MD5 头 $request-withoutHeader(Content-MD5); // 从 Response 中移除头 // 效果浏览器将无法获知流的长度 // 浏览器会一直下载到流结束为止 $response-withoutHeader(Content-Length);移除Content-Length会让响应变成未知长度的分块/流式输出浏览器会持续读取直到流结束——这在流式响应场景下是一种常见的主动行为。3.7 头操作底层语义小结源码依据结合upload/system/storage/vendor/psr/http-message/src/MessageInterface.php的实现约定头名匹配一律大小写不敏感但getHeaders()与withHeader()会保留最初指定的大小写即写时保留、读时忽略大小写withHeader($name, $value)的$value支持string|string[]非法头名或非法值会抛出\InvalidArgumentException所有with*/without*方法都必须维持不可变性并返回新实例。四、HTTP 消息体Body / Stream的操作实战使用 PSR-7 处理消息体有两种实现方式原文档对此有明确推荐。4.1 方式一单独取出 Body 再操作这种方式让 body 的处理更容易理解在需要反复调用 body 方法时非常有用只需调用一次getBody()。这种方式还可以避免$response-write()这类误用。$body $response-getBody(); // 对 body 进行操作例如 read、write、seek // ... // 用可能被替换过的新 body 替换旧 body $response-withBody($body); // 上面这条语句是可选的因为我们操作的是对象 // 在这种情况下新body 与旧body 是同一个 // $body 变量与 $request 中的值相同只是传入了引用这里withBody($body)之所以可选是因为 PHP 中对象默认按引用传递你通过$body对流的修改会直接反映在$response内部持有的同一个流对象上。因此只有在确实想替换成另一个流对象时withBody()才是必须的。4.2 方式二直接在 Response 上操作这种方式在只做少量操作时比较方便因为不需要$request-getBody()这行语句。$response-getBody()-write(hello);4.3 获取消息体内容下面的代码片段获取流的内容。注意流指针的语义流必须被 rewind回卷到开头。如果之前向流中写入过内容直接调用getContents()会忽略已写入的内容——因为写入后流指针停在最后一个字符位置\0意味着已到流末尾。$body $response-getBody(); $body-rewind(); // 或者 $body-seek(0); $bodyText $body-getContents();注意如果在getContents()之前调用了$body-seek(1)那么第一个字符会被跳过因为起始指针被设为1而不是0。这就是为什么推荐使用$body-rewind()。结合StreamInterface.php源码 docblock 可进一步理解getContents()返回的是流中剩余的内容Returns the remaining contents in a string它从当前指针位置读到流末尾而__toString()则相反会先尝试 seek 到流开头再从头读到尾。4.4 向 Body 追加内容$response-getBody()-write(Hello); // 直接写入 $body $request-getBody(); // 这是一个 StreamInterface 对象 $body-write(xxxxx);write()的行为遵循底层 PHP 流如fwrite的语义如果指针当前不在流末尾则从指针当前位置覆盖写入如果指针在末尾则表现为追加。五、流指针Stream Pointer的进阶语义与前置内容前置prepend操作对流来说与追加完全不同必须先读出原内容再从头写入前置部分 原内容。下面的示例解释流的这一行为。// 假设我们的 response 初始为空 $body $response-getBody(); // 写入字符串 abcd $body-write(abcd); // 将指针定位到流开头 $body-seek(0); // 写入 ef $body-write(ef); // 此时流的内容是 efcd关键点seek(0)之后write(ef)是覆盖式写入从位置 0 开始覆盖两个字符ab因此结果是efcdcd保留而不是efabcd。这正是流的写入位置由指针决定的直观体现。5.1 通过单独重写实现前置// 假设我们的 response body 流中只包含: abcd $body $response-getBody(); $body-rewind(); $contents $body-getContents(); // abcd // 将流指针定位到开头 $body-rewind(); $body-write(ef); // 流内容变为 efcd $body-write($contents); // 流内容变为 efabcd注意getContents()在读取时会移动流指针因此如果缺少第二次rewind()流会变成abcdefabcd——因为write()在未被rewind()或seek(0)前置的情况下会从当前指针位置流末尾继续追加写入。5.2 先把内容拼成字符串再整体写回$body $response-getBody(); $body-rewind(); $contents $body-getContents(); // efabcd $contents ef . $contents; $body-rewind(); $body-write($contents);这种方式只做一次write()逻辑更清晰读出全部内容 → 在字符串层面拼接前置部分 → 回卷指针 → 整体覆盖写回。5.3 为什么推荐rewind()而非seek(0)两种写法效果等价rewind()内部就是seek(0)见StreamInterface.phpdocblock但rewind()语义更明确、不易出错——你不需要记住seek()的偏移量与SEEK_SET等whence参数。seek($offset, $whence SEEK_SET)的三个whence取值与 PHP 内置fseek()完全一致SEEK_SET绝对偏移、SEEK_CUR相对当前偏移、SEEK_END相对流末尾偏移。六、仓库内参考实现guzzlehttp/psr7 与配套 PSR 包psr/http-message只是接口契约要真正运行上面所有示例必须有实现。OpenCart 仓库中随附了完整的 PSR-7 实现与配套接口可直接查看源码加深理解实现包upload/system/storage/vendor/guzzlehttp/psr7/src/其中Request.php、ServerRequest.php、Response.php分别实现对应接口Stream.php是流的核心实现对 PHP 底层 stream 资源进行封装MessageTrait.php承载头操作与协议版本等公共逻辑Uri.php、UploadedFile.php对应 URI 与上传文件接口。例如AppendStream、BufferStream、LimitStream、CachingStream、InflateStream等类都直接implements StreamInterface可用于构造复合流场景。工厂接口upload/system/storage/vendor/psr/http-factory/src/定义了RequestFactoryInterface、ResponseFactoryInterface、ServerRequestFactoryInterface、StreamFactoryInterface、UploadedFileFactoryInterface、UriFactoryInterface用于在不依赖具体实现类的情况下创建 PSR-7 对象依赖注入友好。HTTP 客户端接口upload/system/storage/vendor/psr/http-client/src/定义了ClientInterfacesendRequest()及异常接口是 PSR-18 的 HTTP 客户端契约。主要消费方仓库内guzzlehttp/guzzle与aws/aws-sdk-php都依赖这些 PSR 接口例如upload/system/storage/vendor/aws/aws-sdk-php/composer.json中声明了psr/http-message: ^1.0 || ^2.0。从依赖结构看可以推断 OpenCart 扩展开发中调用 AWS 云服务或外部 REST API 时正是通过这些 PSR-7 接口与 Guzzle 实现完成 HTTP 交互。若想在本地验证自动加载与接口可用性可执行php -r require upload/system/vendor.php; var_dump(interface_exists(Psr\\Http\\Message\\ResponseInterface));返回bool(true)即表示 PSR-7 接口已随 Composer 自动加载就绪upload/system/vendor.php是仓库的 Composer 引导文件。七、易错点速查写给快速上手的开发者忘记接收with*返回值不可变性意味着$response-withHeader(...)不会修改$response必须写$response $response-withHeader(...)。getContents()前忘记rewind()读取前指针不在开头会丢掉前面的内容或得到空字符串。write()前不定位指针指针在末尾则追加、在中间则覆盖结果取决于调用历史——必要时先rewind()或seek(0)。用getHeaderLine()拼接不适合逗号拼接的头Cookie、Set-Cookie 等头应使用getHeader()自行处理。混淆withHeader与withAddedHeader前者整体替换后者追加保留旧值。八、原文档勘误说明原文档docs/PSR7-Usage.md中有两处笔误阅读时请注意前置假设部分写着$responseis an object implementingPsr\Http\Message\RequestInterface根据上下文与接口继承关系响应应实现ResponseInterface此处应为ResponseInterfacePrepend to body 示例中$repsonse-getBody()为response的拼写错误。结语与延伸阅读PSR-7 的接口契约 不可变语义 流指针模型是理解现代 PHP HTTP 客户端与中间件体系的地基。本文覆盖了原文档docs/PSR7-Usage.md的全部示例头操作、体操作、追加/前置、指针陷阱并补齐了配套文档docs/PSR7-Interfaces.md的完整方法速查与接口源码依据。仓库内可供继续深挖的关键文件用法指南upload/system/storage/vendor/psr/http-message/docs/PSR7-Usage.md接口速查upload/system/storage/vendor/psr/http-message/docs/PSR7-Interfaces.md核心接口源码MessageInterface.php、StreamInterface.php参考实现guzzlehttp/psr7 源码目录自动加载映射composer/autoload_psr4.php赞分享电商后端【免费下载链接】opencartA free shopping cart system. OpenCart is an open source PHP-based online e-commerce solution.项目地址https://gitcode.com/gh_mirrors/op/opencart点击查看免费下载相关推荐ShowDoc 中的 PSR-7 HTTP 消息实战psr/http-message 消息头与流式消息体操作指南ShowDoc 中的 PSR 7 HTTP 消息实战psr/http message 消息头与流式消息体操作指南 导读 本文基于 ShowDoc 仓库内 ps文档知识库后端前端技术深度解析Electrobun 如何用 Bun 和 Zig 重塑桌面应用性能边界技术深度解析Electrobun 如何用 Bun 和 Zig 重塑桌面应用性能边界 在当今桌面应用开发领域性能与开发效率的平衡始终是一个技术挑战。传统 El桌面应用跨平台BiliBiliToolPro完整部署指南5分钟快速搭建B站自动化任务助手BiliBiliToolPro完整部署指南5分钟快速搭建B站自动化任务助手 BiliBiliToolPro是一款功能强大的B站自动任务工具能够帮助用户自动完后端任务调度工作流自动化上一篇终极番茄钟计时器免费开源工具让你告别拖延症下一篇终极RKNN模型部署实战从零到一的高效AI应用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表