后端【免费下载链接】psr7PSR-7 HTTP message library项目地址https://gitcode.com/gh_mirrors/ps/psr7点击查看免费下载导读本文以guzzlehttp/psr7的官方文档 docs/psr-7-messages.md 为骨架系统讲解 PSR-7 消息对象体系Request、Response、ServerRequest、UploadedFile以及消息级的 Header、URI、Body API。读完本文你将掌握如何创建与加工 HTTP 消息、如何从 PHP 超全局变量还原服务端请求、如何解析复杂响应头、如何正确处理文件上传并理解这些对象在 Guzzle、PSR-18 客户端、PSR-15 中间件等生态中自由流转的底层机制。PSR-7 消息模型与不可变性HTTP 请求与响应在 PSR-7 中都是「消息」Message。一条消息由三部分组成起始行start line、头部headers、可选的消息体流body stream。请求的起始行包含方法、请求目标和协议版本响应的起始行包含协议版本、状态码和原因短语。本包提供的消息对象Request、Response、ServerRequest均实现 PSR-7 对应接口可以在 Guzzle、PSR-18 客户端、PSR-15 中间件及其他 PSR-7 兼容库之间自由传递。消息与 URI 对象都是不可变immutable的所有以with*()开头的方法不会修改原对象而是返回一份修改后的副本。例如withHeader()、withUri()、withStatus()等。而消息体流是可变的句柄读取、写入、seek 都会改变其游标或内容。关于流的细节见 Streams and DecoratorsURI 辅助函数见 URI Helpers。以源码实现为例MessageTraitsrc/MessageTrait.php中withHeader()通过clone $this生成新对象再写入头部withProtocolVersion()在版本一致时甚至直接返回自身以节省开销这正是不可变对象的标准实现模式。创建 Request 请求对象使用GuzzleHttp\Psr7\Request创建请求use GuzzleHttp\Psr7\Request; $request new Request(GET, https://example.com/users/123); // 也可以提供可选的头和消息体 $headers [Accept application/json]; $body request body; $request new Request(PUT, https://example.com/users/123, $headers, $body);从构造函数签名src/Request.php可以看到完整参数顺序为new Request( string $method, // HTTP 方法 $uri, // string | UriInterface array $headers [], // (string|string[])[] 头部映射 $body null, // string | resource | StreamInterface string $version 1.1 // 协议版本默认 1.1 );构造过程中的几个关键点URI 字符串自动解析当$uri不是UriInterface实例时会被new Uri($uri)包装src/Request.php方法名校验方法必须符合 RFC 9110 的 token 规则非法方法会抛出InvalidArgumentExceptionsrc/Request.phpHost 头自动填充如果构造时没有显式提供host头会从 URI 的 host/port 自动生成 Host 头并且按 RFC 9110 第 7.2 节的要求保证 Host 是第一个头src/Request.php请求目标request-target推导从 URI 的 path 和 query 组成 origin-form 目标空路径被规范为/src/Request.php。创建 Response 响应对象使用GuzzleHttp\Psr7\Response创建响应use GuzzleHttp\Psr7\Response; // 构造函数不要求任何参数 $response new Response(); echo $response-getStatusCode(); // 200 echo $response-getProtocolVersion(); // 1.1 // 可以提供状态码、头、消息体和协议版本 $response new Response(200, [Content-Type application/json], {ok:true}, 1.1);构造函数完整签名src/Response.php还包含第五个可选参数?string $reason用于自定义原因短语new Response( int $status 200, array $headers [], $body null, string $version 1.1, ?string $reason null );从源码结构可以确认两点实现细节状态码范围校验状态码必须是 100599 之间的整数否则抛出异常src/Response.php原因短语自动映射包内维护了一张标准状态码到原因短语的映射表PHRASES涵盖 100511 共 60 余个标准状态码src/Response.php。当未提供$reason或提供的为时会自动按状态码查表生成例如 200 对应OK、404 对应Not Found。因此响应对象的三个核心读取方法总是可用getStatusCode()、getReasonPhrase()、getProtocolVersion()。创建 ServerRequest 服务端请求对象服务端请求ServerRequest代表服务器侧收到的入站 HTTP 请求。它在普通请求的方法、URI、头、消息体之外还额外携带服务器参数、Cookie、查询参数、已解析的消息体数据、属性attributes和上传文件。use GuzzleHttp\Psr7\ServerRequest; $request new ServerRequest(POST, https://example.com/form, [], nameGuzzle, 1.1, [ REMOTE_ADDR 192.0.2.1, ]); $request $request -withCookieParams([session abc]) -withQueryParams([page 1]) -withParsedBody([name Guzzle]) -withAttribute(route, profile); echo $request-getServerParams()[REMOTE_ADDR]; echo $request-getCookieParams()[session]; echo $request-getQueryParams()[page]; echo $request-getParsedBody()[name]; echo $request-getAttribute(route);构造函数与Request几乎一致仅多出第六个参数array $serverParams []通常传入$_SERVERsrc/ServerRequest.php。注意该参数与 Cookie 参数在源码中都被标记了#[\SensitiveParameter]用于避免敏感信息进入异常堆栈或日志。ServerRequest继承了Request的全部能力同时提供一组with*()/get*()配对方法src/ServerRequest.php方法对语义withServerParams()/getServerParams()服务器环境参数如REMOTE_ADDR、SERVER_NAMEwithCookieParams()/getCookieParams()请求 Cookie 数组withQueryParams()/getQueryParams()查询参数通常来自$_GETwithParsedBody()/getParsedBody()已解析的消息体数组、对象或 null传入其他类型会抛异常withAttribute()/getAttribute($name, $default)/withoutAttribute()应用层属性常用于路由匹配结果、中间件上下文传递属性不存在时返回默认值fromGlobals()从 PHP 超全局变量还原请求ServerRequest::fromGlobals()从 PHP 超全局变量一次性构建服务端请求。它读取$_SERVER、$_GET、$_POST、$_COOKIE和$_FILES并在可用时尝试还原请求头use GuzzleHttp\Psr7\ServerRequest; $request ServerRequest::fromGlobals();从源码看fromGlobals()委托给内部的ServerRequestGlobalsFactory::fromArrays()src/ServerRequestGlobalsFactory.php其还原逻辑包括方法从$_SERVER[REQUEST_METHOD]读取缺失时默认GET并统一转为大写见后文「HTTP 方法大小写」一节头优先使用apache_request_headers()若函数存在否则从HTTP_*、CONTENT_TYPE、CONTENT_LENGTH、CONTENT_MD5等键还原还会把REDIRECT_HTTP_AUTHORIZATION、PHP_AUTH_USER/PHP_AUTH_PW转为 Basic 认证、PHP_AUTH_DIGEST组装成Authorization头src/ServerRequestGlobalsFactory.phpURI综合HTTPS、HTTP_HOST、SERVER_NAME/SERVER_ADDR、SERVER_PORT、REQUEST_URI、QUERY_STRING拼装并支持 CONNECT authority-form、absolute-form、asterisk-form 等多种请求目标形态src/ServerRequestGlobalsFactory.php消息体使用php://input包装成可缓存流CachingStreamsrc/ServerRequestGlobalsFactory.php协议版本从SERVER_PROTOCOL解析默认1.1。集成测试 tests/Integration/ServerRequestFromGlobalsTest.php 通过真实的 HTTP 服务器验证了fromGlobals()能正确还原 method、headers、body 等字段。getUriFromGlobals()仅提取 URI如果只需要从$_SERVER推导 URI使用ServerRequest::getUriFromGlobals()use GuzzleHttp\Psr7\ServerRequest; $uri ServerRequest::getUriFromGlobals();该方法同样由ServerRequestGlobalsFactory::getUriFromServerParams($_SERVER)实现src/ServerRequestGlobalsFactory.php。URI 的构造与规范化辅助函数详见 URI Helpers。请求对象速览方法与 URIuse GuzzleHttp\Psr7\Request; $request new Request(GET, https://example.com/users/123, [ Accept application/json, ]); echo $request-getMethod(); echo $request-getUri();PSR-7 消息不可变。withHeader()、withUri()等返回修改后的副本$jsonRequest $request-withHeader(Accept, application/json);值得留意的是withUri()还接受第二个布尔参数$preserveHost为true时保留原有 Host 头而不从新 URI 覆盖src/Request.php这在代理场景下非常有用。响应对象速览状态、头与消息体use GuzzleHttp\Psr7\Response; $response new Response(200, [Content-Type application/json], {ok:true}); echo $response-getStatusCode(); echo $response-getHeaderLine(Content-Type); echo $response-getBody();getHeaderLine()会把同名多值头用,拼接成单行字符串src/MessageTrait.php适合直接展示getBody()返回消息体流实例。URI 对象速览use GuzzleHttp\Psr7\Uri; $uri new Uri(https://example.com/users?active1); echo $uri-getHost(); echo $uri-getQuery();Uri是本包对Psr\Http\Message\UriInterface的完整实现src/Uri.php构造时由UriParser按 RFC 3986 解析解析失败抛出MalformedUriExceptionsrc/Uri.php。URI 特有的辅助方法比较、规范化、解析等见 URI Helpers。Headers 头部操作请求和响应消息都包含 HTTP 头。头部存储采用「小写名 → 原始名」的映射结构src/MessageTrait.php因此hasHeader()、getHeader()对大小写不敏感同时又能保持原始书写风格。检查与读取头部使用hasHeader()检查消息是否包含某个头use GuzzleHttp\Psr7\Request; $request new Request(GET, /, [X-Foo bar]); if ($request-hasHeader(X-Foo)) { echo It is there; }用getHeader()获取某头的全部值字符串数组$request-getHeader(X-Foo); // [bar] // 缺失的头返回空数组 $request-getHeader(X-Bar); // []用getHeaders()遍历消息的所有头foreach ($request-getHeaders() as $name $values) { echo $name . : . implode(, , $values) . \r\n; }从实现看getHeaders()返回的是原始大小写名称到值数组的映射getHeader()对缺失头返回[]src/MessageTrait.php。此外构造消息时若同名头出现多次值会被自动合并进同一个头的值数组src/MessageTrait.php。解析复杂头Complex Headers某些头包含额外的键值对信息。例如Link头除了链接本身还携带参数https://example.com/front.jpeg; relfront; typeimage/jpeg使用GuzzleHttp\Psr7\Header::parse()解析这类头use GuzzleHttp\Psr7\Header; use GuzzleHttp\Psr7\Request; $request new Request(GET, /, [ Link https://example.com/front.jpeg; relfront; typeimage/jpeg, ]); $parsed Header::parse($request-getHeader(Link)); var_export($parsed);输出结果array ( 0 array ( 0 https://example.com/front.jpeg, rel front, type image/jpeg, ), )结果由键值对组成没有键的头值如裸链接按数字索引存放而构成键值对的部分则以其参数名作为键。从源码看Header::parse()先按逗号切分列表值splitList()再按分号切分参数splitParameters()过程中会正确跳过引号内和反斜杠转义的内容src/Header.php。splitList()同样可以独立使用用于拆分accept、cache-control、if-none-match这类逗号分隔的头但不能用于user-agent、set-cookie等非列表头src/Header.php。Body 消息体请求和响应的消息体都是Psr\Http\Message\StreamInterface实例。流既用于上传数据也用于下载数据use GuzzleHttp\Psr7\Response; $response new Response(200, [], response body); echo $response-getBody(); // response body消息体可以整体转成字符串也可以按需读取字节$body $response-getBody(); echo $body-read(4); $body-seek(0); echo $body-getContents();注意echo $response-getBody()实际触发的是流的__toString()而getBody()本身返回的是流对象。这里体现了 PSR-7 的核心设计——消息是值对象消息体是可变句柄read(4)会推进游标因此读取前需要用seek(0)回到开头getContents()才返回完整内容。关于流的创建与装饰器streamFor()、LimitStream、CachingStream等的更多示例见 Streams and Decorators。Uploaded Files 上传文件上传文件由Psr\Http\Message\UploadedFileInterface实例表示。本包提供的GuzzleHttp\Psr7\UploadedFile可以包装本地文件路径、PHP 流资源或 PSR-7 流三种数据源use GuzzleHttp\Psr7\UploadedFile; use GuzzleHttp\Psr7\Utils; $stream Utils::streamFor(file contents); $upload new UploadedFile($stream, $stream-getSize(), UPLOAD_ERR_OK, example.txt, text/plain); echo $upload-getClientFilename(); echo $upload-getClientMediaType(); echo $upload-getSize();构造函数签名src/UploadedFile.php为new UploadedFile( $streamOrFile, // StreamInterface | string(路径) | resource ?int $size, int $errorStatus, // PHP 的 UPLOAD_ERR_* 常量 ?string $clientFilename null, ?string $clientMediaType null );从源码可以确认当传入的是路径字符串时getStream()会按需以r模式惰性打开LazyOpenStreamsrc/UploadedFile.php传入的资源会被包装为Stream。调用getStream()读取上传内容或调用moveTo()把文件移动/复制到目标路径。moveTo()成功之后isMoved()返回true任何需要活动上传流的调用都会抛出异常$body $upload-getStream(); echo $body-getContents(); $upload-moveTo(/path/to/target.txt); var_export($upload-isMoved()); // truemoveTo()的实现src/UploadedFile.php值得说明目标是空字符串会抛InvalidArgumentException底层为文件路径时CLI 环境用rename()Web 环境用move_uploaded_file()后者保证了安全性检查底层为流时先把流 rewind再用Utils::copyToStream()拷贝到目标文件移动失败抛出RuntimeException。错误状态的处理如果上传错误码不是UPLOAD_ERR_OK对象仍然暴露getError()、getSize()、getClientFilename()、getClientMediaType()但getStream()和moveTo()会抛出异常——因为此时并不存在成功上传的内容。源码通过validateActive()统一把关错误码非 OK 或文件已被移动都会抛RuntimeExceptionsrc/UploadedFile.php合法的错误码集合定义在ERROR_MAP中UPLOAD_ERR_INI_SIZE、UPLOAD_ERR_FORM_SIZE、UPLOAD_ERR_PARTIAL、UPLOAD_ERR_NO_FILE等src/UploadedFile.php。normalizeFiles()把 $_FILES 结构转成上传文件树ServerRequest::normalizeFiles()把$_FILES风格的数组转换成上传文件实例的树形结构。它接受简单文件规格、嵌套的 PHP$_FILES形态、已有的UploadedFileInterface实例以及上传文件的嵌套数组use GuzzleHttp\Psr7\ServerRequest; $files ServerRequest::normalizeFiles([ avatar [ tmp_name /tmp/php123, size 1024, error UPLOAD_ERR_OK, name avatar.png, type image/png, ], photos [ tmp_name [ first /tmp/php456, ], size [ first 2048, ], error [ first UPLOAD_ERR_OK, ], name [ first photo.jpg, ], type [ first image/jpeg, ], ], ]); $request (new ServerRequest(POST, /upload))-withUploadedFiles($files);上例中的photos正是 PHP$_FILES处理input namephotos[first]这类多文件上传时产生的嵌套形态tmp_name、size、error等键内部再按索引拆分。从源码看normalizeFiles()委托给UploadedFileNormalizer::normalize()src/ServerRequest.php其递归逻辑为src/UploadedFileNormalizer.php值已是UploadedFileInterface→ 直接保留值是含tmp_name键的数组 → 视为文件规格构建UploadedFiletmp_name/size/error三个键必填否则抛InvalidArgumentExceptionsrc/UploadedFileNormalizer.php值是不含tmp_name的数组 → 递归归一化支持任意层级嵌套其他类型 → 抛InvalidArgumentException。嵌套规格内部还要求tmp_name、size、error三个数组的键一一对应否则视为非法src/UploadedFileNormalizer.php。HTTP 方法大小写Method CasingHTTP 方法名在 PSR-7 中区分大小写。通过Request、ServerRequest、withMethod()、Message::parseRequest()或 PSR-17 工厂显式创建的请求会原样保留传入的方法字符串只有ServerRequest::fromGlobals()在从 PHP 服务器全局变量灌入请求时会把$_SERVER[REQUEST_METHOD]规范化为大写以保证兼容性——该行为由ServerRequestGlobalsFactory::getRequestMethodFromServer()中的Utils::asciiToUpper()实现src/ServerRequestGlobalsFactory.php。Request Methods 请求方法创建请求时提供要执行的方法。可以指定任意方法包括 RFC 9110 未收录的自定义方法use GuzzleHttp\Psr7\Request; $request new Request(MOVE, https://example.com/resource); echo $request-getMethod(); // MOVEwithMethod()换方法同理且会先经过 RFC 9110 token 校验src/Request.php。Request URI 请求 URI请求 URI 由Psr\Http\Message\UriInterface对象表示本包通过GuzzleHttp\Psr7\Uri提供实现。创建请求时URI 既可以是字符串也可以是UriInterface实例use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Uri; $request new Request(GET, new Uri(https://example.com/users?id123));下面按 URI 组件逐个说明读取方式。Scheme 协议scheme 指明协议HTTP 请求通常为http或https$request new Request(GET, https://example.com); echo $request-getUri()-getScheme(); // httpsHost 主机主机既可从 URI 获取也同时体现在Host头中$request new Request(GET, https://example.com); echo $request-getUri()-getHost(); // example.com echo $request-getHeaderLine(Host); // example.com这正是前文提到的Request构造时会从 URI 自动生成 Host 头并置于头部首位src/Request.php。Port 端口http和https的默认端口无需显式写出$request new Request(GET, https://example.com:8443); echo $request-getUri()-getPort(); // 8443Uri内部维护了一张默认端口表http80、https443、ftp21 等src/Uri.php因此getPort()只在端口非默认值时才返回非 nullHost 头的生成也会把非默认端口拼入host:port。Path 路径请求路径通过 URI 对象访问$request new Request(GET, https://example.com/users/123); echo $request-getUri()-getPath(); // /users/123URI 路径中不允许的字符会按 RFC 3986 section 3.3 进行百分号编码。此外Request会把 URI 路径规范为 origin-form 请求目标空路径变为/以//开头的路径会被折叠以避免被解析成 network-path referencesrc/Request.php。Query String 查询字符串查询字符串同样通过 URI 对象访问$request new Request(GET, https://example.com/?foobar); echo $request-getUri()-getQuery(); // foobar查询串中不允许的字符会按 RFC 3986 section 3.4 或ServerRequest::getQueryParams()。Response Status 响应状态响应暴露状态码、原因短语和协议版本use GuzzleHttp\Psr7\Response; $response new Response(200, [], OK); echo $response-getStatusCode(); // 200 echo $response-getReasonPhrase(); // OK echo $response-getProtocolVersion(); // 1.1如前面「创建 Response」一节所述getReasonPhrase()的返回值来自内置PHRASES映射表200 →OK未标准的状态码可以借助withStatus($code, $reasonPhrase)提供自定义短语原因短语同样要经过 RFC 9112 校验不能包含非法控制字符src/Response.php。总结与延伸阅读guzzlehttp/psr7的消息对象体系覆盖了 HTTP 消息处理的全部核心场景以Request/Response表示客户端与服务端消息以ServerRequest承载服务端入站数据服务器参数、Cookie、查询、解析体、属性、上传文件以UploadedFile封装文件上传并通过不可变的with*()方法与可变的流式消息体实现了消息在 Guzzle、PSR-18 客户端、PSR-15 中间件等 PSR-7 生态组件之间的无缝流转。继续深入阅读仓库内相关文档Streams and Decorators — 流的创建、读取与装饰器模式URI Helpers — URI 的解析、比较与规范化辅助函数Message Helpers — 消息字符串序列化、解析与摘要Header and Query Helpers — 头部与查询串的解析工具PSR-17 Factories — 基于工厂模式创建 PSR-7 消息Diagnostic Values — 安全转义与诊断值工具赞分享后端【免费下载链接】psr7PSR-7 HTTP message library项目地址https://gitcode.com/gh_mirrors/ps/psr7点击查看免费下载相关推荐Guzzle 与 PSR-7 集成实战指南请求、响应、流与 PSR-17 工厂深度解析Guzzle 与 PSR 7 集成实战指南请求、响应、流与 PSR 17 工厂深度解析 Guzzle 是基于 PSR 7 接口构建的 PHP HTTP 客户端后端Falcon ASGI 请求与响应对象全解析falcon.asgi.Request / Response 实战指南Falcon ASGI 请求与响应对象全解析falcon.asgi.Request / Response 实战指南 在 Falcon 的 ASGI 应用中后端Web框架API设计如何3分钟获取阿里云盘Refresh Token扫码实现自动化文件管理的终极指南如何3分钟获取阿里云盘Refresh Token扫码实现自动化文件管理的终极指南 阿里云盘Refresh Token获取工具是一款让普通用户也能轻松掌握云盘自上一篇如何用嵌入式Rust构建飞秒激光控制系统基于awesome-embedded-rust的超短脉冲精密控制指南下一篇Python迭代器协议如何设计自定义可迭代对象的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?