ARTICLE DETAIL

资讯详情

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

Dolibarr 内置的 sabre/http 库解析:PHP 请求与响应对象的封装、HTTP 客户端与反向代理实战

Dolibarr 内置的 sabre/http 库解析:PHP 请求与响应对象的封装、HTTP 客户端与反向代理实战 企业应用后端【免费下载链接】dolibarrDolibarr ERP CRM is a modern software package to manage your company or foundations activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). its an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers.项目地址https://gitcode.com/gh_mirrors/do/dolibarr点击查看免费下载本文以 Dolibarr 仓库中随 htdocs/includes/sabre/sabre/http/README.md 捆绑的 sabre/http 5.1.10 库为核心讲解它如何用Request/Response对象统一封装 PHP 的$_GET、$_POST、$_SERVER、php://input、header()与php://output并深入源码介绍 SAPI 桥接、装饰器扩展、cURL 客户端事件机制、异步请求与反向代理写法。读完本文你将掌握如何在 Dolibarr 的 WebDAV 生态sabre/dav 依赖链中理解这套 HTTP 抽象层并能在自己的 PHP 应用里直接复用这套可测试、可扩展的请求响应模型。sabre/http 是什么把原始的 PHP 超全局变量变成干净的对象大多数 PHP 脚本运行在一个 HTTP 请求上下文中但直接读取$_GET、$_POST、$_SERVER、php://input或已被移除的$HTTP_RAW_POST_DATA再通过header()、echo/php://output拼装输出既繁琐又容易出错且在不同 SAPIApache mod_php、CGI/FastCGI、CLI下行为不一致。sabre/http 本质上是围绕上述 PHP 内建构造的一层封装对外提供两个核心对象Request表示一次 HTTP 请求统一收纳方法、URL、头部、查询参数、POST 数据、$_SERVER原始值以及消息体Response表示一次 HTTP 响应统一管理状态码、头部和消息体并可交由 SAPI 桥接类一次性写回客户端。这两个对象都基于接口RequestInterface/ResponseInterface设计可继承、可装饰、易于在单元测试中 mock。原文档明确说明该库于 2009 年诞生于sabre/dav项目随后被拆分独立发布与 Symfony 的HttpFoundation在功能上有大量重叠但更轻量、更聚焦。在 Dolibarr 中的定位从仓库结构看htdocs/includes/sabre/ 目录下同时捆绑了dav、event、http、uri、vobject、xml六个 sabre 系包。其中 htdocs/includes/sabre/sabre/dav/composer.json 声明依赖sabre/http: ^5.0.5而 Dolibarr 的 WebDAV 入口 htdocs/dav/fileserver.php 通过require_once DOL_DOCUMENT_ROOT./includes/sabre/autoload.php加载整套 sabre 自动加载器。因此可以推断sabre/http 是 Dolibarr WebDAV 文件服务功能的 HTTP 基础设施层——它不直接出现在业务控制器里而是被 sabre/dav 的服务器实现消费。安装与依赖原文档给出的安装方式是基于 Composer 的标准流程在项目composer.json中声明sabre/http: ~5.0.0然后执行composer install。不过在当前 Dolibarr 仓库中该库已经以完整源码形式捆绑在htdocs/includes/目录下随主程序一起分发无需单独安装。htdocs/includes/sabre/sabre/http/composer.json 揭示了库的真实依赖与环境要求条目要求说明PHP 版本^7.1 \|\| ^8.0兼容 PHP 7.1 与 8.xext-mbstring*路径解码时进行 UTF-8/ISO-8859-1 转换ext-ctype*状态码字符串检测ctype_digitext-curl*运行时建议Client类发请求依赖 cURLsabre/event4.0 6.0Client的事件发射机制sabre/uri^2.0URL 规范化Uri\normalize、Uri\resolve自动加载采用 PSR-4Sabre\\HTTP\\映射到lib/目录同时lib/functions.php被注册到files自动加载。当前捆绑版本号见 lib/Version.phpconst VERSION 5.1.10。快速上手一次请求、一次响应原文档强调整个应用中Sapi::getRequest()只应调用一次其余地方通过依赖注入传递请求对象并始终以接口类型做类型提示use Sabre\HTTP; include vendor/autoload.php; $request HTTP\Sapi::getRequest();use Sabre\HTTP; function handleRequest(HTTP\RequestInterface $request) { // Do something with this request :) }Sapi::getRequest()的实现位于 lib/Sapi.php它读取$_SERVER通过createFromServerArray()构建请求对象然后把php://input作为消息体流挂上去再把$_POST写入 postData。值得注意的是 CLI 环境下的处理当PHP_SAPI cli时会自动补上REQUEST_URI默认/和REQUEST_METHOD默认CLI使库在命令行脚本中同样可工作——这对测试和后台任务场景很有价值。响应对象的构建与发送同样简洁use Sabre\HTTP; include vendor/autoload.php; $response new HTTP\Response(); $response-setStatus(201); // created ! $response-setHeader(X-Foo, bar); $response-setBody( success! );组装完成后调用HTTP\Sapi::sendResponse($response);一次性写回客户端。这句代码同样应当只在整个应用的末尾出现一次。查看 Sapi::sendResponse 的源码可以发现它做了不少看不见的工程细节用header(HTTP/.版本. .状态码. .状态文本)输出状态行再遍历响应头部逐个header()重复头部用第二个参数false追加而非覆盖消息体支持三种形态可调用对象callable、字符串、流资源当指定了Content-Length时按4 MiB 块通过stream_copy_to_stream拷贝并配合stream_set_chunk_size优化性能支持Content-Range断点续传场景解析bytes 1234-5678/7890形式的范围先按 4 KiB 页大小对齐补齐偏移再继续拷贝通过ignore_user_abort()与connection_aborted()配合在客户端断开时中止大文件传输。Request 对象 API 详解原文档完整列出了Request的公共 API下面按主题分组并补充源码行为说明。构造函数签名public function __construct($method null, $url null, array $headers null, $body null);方法 / URL / 路径getMethod()/setMethod($method)读写 HTTP 方法getUrl()/setUrl($url)读写请求 URLgetAbsoluteUrl()/setAbsoluteUrl($url)绝对 URL。查看 lib/Request.php若未显式设置会自动用http://Host头 请求 URL 推断getBaseUrl()/setBaseUrl($url)基准 URL默认/用于相对路径计算getPath()基于 baseUrl 计算相对路径。源码Request.php#L163-L185会先去除重复斜杠、经Uri\normalize规范化再剥离 baseUrl 前缀返回值不以斜杠开头如example/path.html若完整路径恰好等于 baseUrl 则返回空字符串路径位于 baseUrl 之外时抛出LogicException。数据访问getQueryParameters()等价于$_GET。源码用parse_str()解析 URL 中?之后的部分getPostData()/setPostData(array $postData)等价于$_POST。原文档特别注解如果 POST 数据可直接从php://input读取本不需要此方法但由于解析表单编码需要特判所以单独暴露getRawServerValue($valueName)/setRawServerData(array $data)读写原始$_SERVER数组键不存在时返回null。消息体getBodyAsStream()以可读流资源返回注意底层可能是不可回绕的流可能只能读一次getBodyAsString()以字符串返回同样因为底层可能是流只有第一次调用能保证正确getBody()/setBody($body)返回/设置内部表示可以是字符串、流资源或 callablecallable 形式用于把响应体直接写入php://output。头部getHeaders()/setHeaders(array)/addHeaders(array)分别读取全部头部、整体覆盖、增量合并已存在的同名头被覆盖其余保留getHeader($name)大小写不敏感地读取单个头部不存在时返回null同名多次出现的头部会被逗号拼接Message::getHeader实现像Set-Cookie这类不能逗号合并的头部应改用getHeaderAsArray()setHeader($name, $value)写头部保留传入的大小写形态removeHeader($name)大小写不敏感地删除成功返回true不存在返回falsesetHttpVersion($version)/getHttpVersion()HTTP 版本应为1.0、1.1或2.0。从$_SERVER构建请求时的隐式映射Sapi::createFromServerArray 是理解库行为的钥匙它把 PHP 的$_SERVER转成结构化的请求对象映射规则包括SERVER_PROTOCOL为HTTP/1.0→ 版本1.0HTTP/2.0→ 版本2.0其余默认1.1CONTENT_TYPE/CONTENT_LENGTH特殊处理为Content-Type/Content-Length头这两种键名不以HTTP_开头PHP_AUTH_USERPHP_AUTH_PW→ 自动拼出Basic base64(...)形式的Authorization头mod_php 特有的行为PHP_AUTH_DIGEST→Digest ...REDIRECT_HTTP_AUTHORIZATION→ 原样透传覆盖 Apache mod_rewrite 改写头名的情况HTTPS非空且非off→ 协议记为https否则http其余HTTP_前缀键 → 去掉前缀、_转-、ucwords首字母大写得到规范头名如HTTP_X_FOO_BAR→X-Foo-Bar缺少REQUEST_URI或REQUEST_METHOD时抛出InvalidArgumentException。此外Request::__toString()会把请求序列化为可读文本用于调试并且会对Authorization头做脱敏只保留 scheme、值替换为REDACTED避免调试日志泄露凭据。Response 对象状态码与消息体Response的 API 与Request共享由 lib/Message.php 提供的头部与消息体能力两者都继承Message基类另有getStatus()返回当前状态码整数getStatusText()返回人类可读的状态文本如200对应OKsetStatus($status)接受403 I cant let you do that, Dave这种码 文本的完整形式也接受纯数字——此时自动从内置状态码表补默认文本代码不在 100–999 三位数范围时抛出InvalidArgumentException见 Response.php#L150-L168。Response::$statusCodesResponse.php#L21-L83内置了 100–511 的完整标准状态码表其中不乏 WebDAV 相关条目——例如207 Multi-Status、208 Already Reported、422 Unprocessable Entity、423 Locked、507 Insufficient Storage均标注 RFC 4918/5842——这正是 sabre/http 为 sabre/dav 服务的痕迹。__toString()同样提供调试友好的完整响应文本序列化。Message基类还提供了两个易被忽略的能力getHeaderAsArray()用于取同名多值头部hasHeader($name)大小写不敏感地判断头部是否存在。消息体在Message层面统一处理三种形态——字符串、流资源、callable——getBodyAsStream()对字符串会透明地写入php://temp流再返回getBodyAsString()对 callable 会用输出缓冲捕获其写入内容。装饰器Decorator扩展请求/响应对象的推荐姿势原文档用一个专门小节警告不要直接继承Request/Response来加行为。理由有三应用的不同子系统可能想用不同方式扩展同一对象Sapi::getRequest()工厂始终返回Request实例想换子类就得连带覆盖工厂方法库或应用一旦强依赖具体Request/Response子类就难以与其他同样使用 sabre/http 的应用协作。推荐方案是装饰器模式。sabre/http 提供了现成的辅助类RequestDecorator/ResponseDecorator以及共享的MessageDecoratorTrait。以给请求加一个isLoggedIn()方法为例use Sabre\HTTP; class MyRequest extends HTTP\RequestDecorator { function isLoggedIn() { return true; } }真正的Request对象仍由其他子系统或单元测试创建当前子系统只需$request new MyRequest($request);$request只要实现了Sabre\HTTP\RequestInterface即可被装饰。查看 lib/RequestDecorator.php它实现RequestInterface构造函数接收内部对象$inner其余每个方法getMethod、getUrl、getPath、getQueryParameters……都只是把调用转嫁给$inner。因此新增行为完全隔离在装饰器内部不触碰核心实例也不破坏接口契约。HTTP 客户端 Client同步、事件、重试与异步sabre/http还包含一个围绕 cURL 的轻量客户端lib/Client.php允许复用熟悉的Request/Response对象直接发请求。原文档明确表态它不是 Guzzle 的替代品而是适合偶尔调一次 API的轻量选择。同步请求use Sabre\HTTP; $request new HTTP\Request(GET, http://example.org/); $request-setHeader(X-Foo, Bar); $client new HTTP\Client(); $response $client-send($request); echo $response-getBodyAsString();Client继承自sabre/event的EventEmitter共发射3 个事件exception事件在源码中还存在用于 cURL 底层错误$client new HTTP\Client(); $client-on(beforeRequest, function($request) { // 可在此向 Request 注入额外头部 }); $client-on(afterRequest, function($request, $response) { // 适合做日志或对响应做改写 }); $client-on(error, function($request, $response, $retry, $retryCount) { // 所有状态码高于 399 的响应都会触发 }); $client-on(error:401, function($request, $response, $retry, $retryCount) { // 也可以监听特定错误码401 时注入认证头并重试一次 if ($retryCount 1) { // Were only going to retry exactly once. } $request-setHeader(Authorization, Basic xxxxxxxxxx); $retry true; });事件回调中的$retry是引用传递置为true即可让send()自动重发请求$retryCount从 0 开始计数用于限制重试次数。源码Client::send还揭示了几个 README 未展开的行为应用内重定向对301/302/307/308状态码客户端不依赖 cURL 的FOLLOW_LOCATION因为在open_basedir限制下它会报错而是自行克隆请求、用Uri\resolve解析Location头后重新发送默认最多跟随maxRedirects 5次异常模式setThrowExceptions(true)后状态码 ≥ 400 的响应会抛出ClientHttpException该方法仅对send()生效不支持sendAsync()cURL 透传addCurlSetting($name, $value)可向每个请求注入额外 cURL 选项默认设置包括CURLOPT_RETURNTRANSFER、CURLOPT_USERAGENT sabre-http/5.1.10且限制协议仅为 HTTP/HTTPSCURLPROTO_HTTP | CURLPROTO_HTTPS请求体安全非 GET/HEAD 方法的请求体会强制(string)强转避免数组经 cURL 的语法上传本地文件的注入风险。异步请求Client支持基于 cURL multi 处理器的异步请求适合批量并行执行use Sabre\HTTP; $request new Request(GET, http://localhost/); $client new Client(); // Executing 1000 requests for ($i 0; $i 1000; $i) { $client-sendAsync( $request, function(ResponseInterface $response) { // Success handler }, function($error) { // Error handler } ); } // Wait for all requests to get a result. $client-wait();配合的轮询模型是sendAsync()入队后立即调用一次poll()poll()内部通过curl_multi_exec驱动传输并分发完成回调成功、HTTP 错误、cURL 错误三条路径各自触发afterRequest/error/exception事件且支持基于$retryCount的自动重试wait()则循环curl_multi_select poll()直到全部完成。原文档提到可参考examples/asyncclient.php获取更多信息——该示例文件未包含在当前 Dolibarr 捆绑目录中捆绑包仅含lib/与元数据文件。综合实战十行代码写出反向代理原文档用一个完整的反向代理示例串联了全部工具——Sapi读请求、克隆并改写请求、Client转发、Sapi回写响应use Sabre\HTTP\Sapi, Sabre\HTTP\Client; // The url were proxying to. $remoteUrl http://example.org/; // The url were proxying from. Please note that this must be a relative url, // and basically acts as the base url. // // If your $remoteUrl doesnt end with a slash, this one probably shouldnt // either. $myBaseUrl /reverseproxy.php; // $myBaseUrl /~evert/sabre/http/examples/reverseproxy.php/; $request Sapi::getRequest(); $request-setBaseUrl($myBaseUrl); $subRequest clone $request; // Removing the Host header. $subRequest-removeHeader(Host); // Rewriting the url. $subRequest-setUrl($remoteUrl . $request-getPath()); $client new Client(); // Sends the HTTP request to the server $response $client-send($subRequest); // Sends the response back to the client that connected to the proxy. Sapi::sendResponse($response);这个示例的价值在于展示了对象模型的组合性clone让请求对象可安全复制后再改写setBaseUrlgetPath完成相对路径提取removeHeader(Host)避免把本地 Host 头透传给上游Client::send与Sapi::sendResponse一进一出恰好对应整个库请求入、响应出的对称设计。辅助函数与认证工具除对象体系外lib/functions.php 还提供了一批无状态的 HTTP 工具函数常被上层直接使用parseDate(string)/toDate(DateTime)解析/生成 HTTP 日期。parseDate严格校验 RFC 7231 定义的三种日期格式IMF-fixdate、RFC 850、ANSI C asctime非法输入返回falsetoDate把DateTime转为D, d M Y H:i:s GMT格式negotiateContentType($acceptHeaderValue, array $availableOptions)内容协商助手。根据Accept头的媒体类型、q质量因子与参数做匹配打分选出最佳选项无匹配返回null此时按规范可返回默认值或406 Not AcceptableAccept头缺失时直接返回可用列表第一项parsePrefer($input)解析 RFC 7240 的Prefer头如returnminimal→[return minimal]、foo, wait10→[foo true, wait 10]并自动把旧草案值return-asynch、return-representation、return-minimal、strict、lenient映射到新规范语义parseMimeType($str)/getHeaderValues($values)MIME 类型解析拆出 type/subType/quality/parameters*归一化为*/*与逗号分隔头部值拆分encodePath/encodePathSegment/decodePath/decodePathSegmentURL 路径编解码。decodePathSegment会做rawurldecode若结果是 ISO-8859-1 编码则自动转成 UTF-8——这与Request::getPath()的文档描述一致是处理非 ASCII 文件名WebDAV 场景常见的关键。此外lib/Auth/目录下提供了Basic、Digest、Bearer、AWS四种认证辅助类共同基类AbstractAuth它们与Sapi::createFromServerArray中针对PHP_AUTH_*、REDIRECT_HTTP_AUTHORIZATION的自动头映射配合覆盖了从服务端解析认证信息到客户端构造认证头的常见场景。小结sabre/http 的价值在于把 PHP 散落各处的 HTTP 原生构造收敛为两个接口化、可组合、可 mock 的对象并以Sapi作为 PHP 环境与对象模型之间的桥接层。在 Dolibarr 中它作为sabre/dav的依赖随htdocs/includes/sabre/一同分发承担 WebDAV 服务的 HTTP 底层工作对开发者而言理解Request/Response的完整 API、装饰器扩展范式、Client的事件驱动同步/异步请求以及functions.php中的内容协商与路径编解码工具足以在任意 PHP 项目中独立复用这套成熟模式。赞分享企业应用后端【免费下载链接】dolibarrDolibarr ERP CRM is a modern software package to manage your company or foundations activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). its an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers.项目地址https://gitcode.com/gh_mirrors/do/dolibarr点击查看免费下载相关推荐OpCore Simplify智能自动化黑苹果配置的革命性工具OpCore Simplify智能自动化黑苹果配置的革命性工具 还在为复杂的黑苹果配置过程而烦恼吗OpCore Simplify作为一款革命性的 OpenC开发工具CLIFindMy.py网络请求封装HTTP客户端与响应处理优化FindMy.py网络请求封装HTTP客户端与响应处理优化 ? 痛点场景为什么需要专业的HTTP封装 还在为Apple Find My网络API的复杂认证企业如何选型通义千问Qwen开源大语言模型从单卡部署到72B规模的一次讲清企业如何选型通义千问Qwen开源大语言模型从单卡部署到72B规模的一次讲清 当你的团队决定把大语言模型从调用API转向私有化部署时第一个撞上的问题往大模型人工智能微调模型量化模型评测本地部署模型推理服务Qwen上一篇终极指南如何使用xhydra图形界面快速进行网络密码安全测试下一篇FileBag在gh_mirrors/ht/http-foundation中的应用文件上传数据的便捷管理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表