laybot / request-sdk
LayBot universal PHP HTTP request toolkit (Guzzle + Workerman + SSE)
Requires
- php: >=8.1
- guzzlehttp/guzzle: ^7.8
- psr/http-message: ^1.0 || ^2.0
- psr/log: ^3.0
- workerman/workerman: ^4.1 || ^5.0
Requires (Dev)
- phpunit/phpunit: ^9.6
Suggests
- workerman/workerman: Enable coroutine transport for Webman/Workerman stream mode
README
通用 PHP HTTP 请求与基础流式工具库
稳定 · 易用 · 框架无关 · 适合作为第三方平台 SDK 与企业项目的网络底座
1. 为什么使用 Request-SDK?
在实际工程里,服务端项目很少只面对一种请求场景。
你既需要覆盖绝大多数普通 HTTP 请求,又经常会遇到下面这些现实问题:
- 接第三方 OpenAPI
- 调内部微服务
- 做管理后台与定时同步
- 上传下载文件
- 记录请求日志
- 处理复杂鉴权
- JSON 编解码
- 重试机制
- 统一异常体系
- 在 Webman / Workerman 环境下接基础流式响应
单独看,每一项都不复杂;但一旦落到真实项目里,问题往往不是“能不能发请求”,而是:
- 请求方式是否统一
- 鉴权逻辑是否可复用
- JSON 编解码是否一致
- 异常是否可控
- 日志是否能脱敏
- 文件下载是否会留下半文件
- 流式请求在不同运行环境下是否还能保持一致调用方式
常见方案各有侧重:
- Guzzle:成熟稳定,适合普通同步 HTTP 请求
- Workerman:适合事件循环和长连接场景
- 但在项目里,通常还需要把这些能力统一起来:
- Header 鉴权
- JSON 编解码
- Retry
- Trace 日志
- 文件上传下载
- 基础流式处理
- 统一异常体系
此外,很多服务并不是标准 Bearer 或 Basic 鉴权,而是固定 Header Token,例如:
- 内部微服务固定 Header Token
- 导出服务 Token
- 平台间服务身份标识
- 任意
X-XXX-*风格静态 Header 鉴权
laybot/request-sdk 的目的,就是把这些网络层能力收敛成一个可长期复用、可持续演进的基础组件库,适合作为:
- OpenAI / Gemini / Claude 等管理类 SDK 的底层网络基座
- Webman / Workerman 后台服务的通用 HTTP 组件
- 内部 OpenAPI / 微服务调用工具
- 定时同步、管理后台、Server-to-Server 通信基础库
- 企业级 PHP 项目的统一网络访问层
它不仅解决“能发请求”的问题,更关注:
- 统一调用方式
- 统一鉴权入口
- 统一异常模型
- 统一日志与脱敏策略
- 统一上传下载处理
- 统一基础流式能力
- 统一底层可扩展能力
换句话说,这不是一个只为单个项目服务的请求工具,而是一套适合在多个 PHP 项目、多个 SDK、多个服务之间长期复用的网络底座。
laybot/request-sdk 的目标不是替代业务 SDK,也不负责模型语义封装。它只解决一件事:把请求稳定地发出去,把响应按统一方式收回来。
适用场景包括:
- 内部微服务调用
- 第三方 OpenAPI 接入
- 管理后台服务端请求
- 定时任务与数据同步
- 文件上传下载
- 基础流式响应处理
- 作为上层 SDK 的网络底座
如果你的目标是做大模型对话、流式增量聚合、厂商协议适配,请使用上层 laybot/ai-sdk。
request-sdk 只负责网络层,不负责模型语义层。
2. 定位与边界
laybot/request-sdk 的定位是:
- 通用服务端 HTTP 请求组件库
- 第三方平台 SDK 的底层网络基座
- 适用于 Webman / Workerman / CLI / FPM 的网络通信层
本库主要负责:
- HTTP 请求发送
- JSON / Form / Upload / Download
- Bearer / ApiKey / Basic / Hmac / Inner 鉴权
- Retry / Trace / Timeout
- 原始响应获取
- 基础流式请求
- Query 编码控制
本库不负责:
- 大模型对话语义封装
- Chat / Messages / Tool Calls / Function Calls 抽象
- 多模型厂商(OpenAI / Gemini / Claude)对话协议适配
- 流式对话增量聚合与最终消息拼装
- 模型调用层的业务语义封装
如果你的目标是:
- 调用大模型对话接口
- 统一封装 OpenAI / Gemini / Claude
- 处理流式对话增量
- 封装 Chat / Embedding / Image / Audio 等模型能力
请使用上层语义 SDK:
laybot/ai-sdk
也就是说:
request-sdk负责:怎么请求ai-sdk负责:怎么调用大模型
3. 推荐分层
在实际项目中,建议按以下分层使用:
request-sdk:底层网络请求层openai/gemini/ 其他平台管理 SDK:平台接口层laybot/ai-sdk:大模型调用语义层- 业务项目:业务逻辑层
这样做的好处是:
- 网络层能力统一
- 鉴权、重试、日志、下载等能力不重复实现
- 上层 SDK 只关心业务语义,不重复处理底层传输细节
- 后续扩展新的平台 SDK 时,可以直接复用同一套网络底座
4. 安装
composer require laybot/request-sdk:^0.5
5. 适用场景
适合:
- 各类 AI 平台管理类 SDK 底座
- Webman / Workerman 后台服务
- 定时同步任务
- 管理后台
- 内部 OpenAPI / 微服务调用
- 文件上传下载
- 基础流式请求
- 其他企业级 server-to-server 网络请求
不直接面向:
- 大模型对话语义封装
- Chat / Embedding / Tool Calls 等模型调用层
- 厂商流式协议适配与消息聚合
普通请求默认使用 Guzzle,稳定优先。
流式请求支持 Guzzle 与 Workerman 两种 transport,适合在不同运行环境下统一调用方式。
6. 快速开始
6.1 普通 GET
use LayBot\Request\Client; $http = Client::make([ 'base_uri' => 'https://httpbin.org', ]); $res = $http->get('/get', [ 'foo' => 'bar', ]); var_dump($res);
6.2 POST JSON
$res = $http->postJson('/post', [ 'name' => 'LayBot', 'scene' => 'demo', ]); var_dump($res);
6.3 POST Form
$res = $http->postForm('/post', [ 'username' => 'demo', 'password' => '123456', ]);
6.4 获取原始响应
$raw = $http->requestRaw('GET', '/get', [ 'query' => ['a' => 1], ]); /* [ 'status' => 200, 'headers' => [...], 'body' => '...' ] */
6.5 获取 JSON(mixed)
对于底层网络场景,有些接口返回的 JSON 不一定是对象,也可能是:
- 数组
- 字符串
- 数字
- 布尔值
- null
可以使用:
$res = $http->requestJsonAny('GET', '/anything');
或者:
$res = $http->getAny('/anything');
6.6 Bearer 鉴权
$http = Client::make([ 'base_uri' => 'https://api.openai.com/v1', 'token' => 'sk-xxx', 'timeout' => 30, 'retry' => 2, 'verify' => true, ]);
6.7 文件上传
$res = $http->upload( '/post', 'file', __DIR__ . '/demo.txt', ['scene' => 'test'] );
6.8 文件下载
$path = $http->download( '/image/png', __DIR__ . '/runtime/demo.png' ); echo $path;
download()使用临时文件写入,成功后再原子替换目标文件,适合大文件下载场景。
6.9 基础流式请求
$http = Client::make([ 'base_uri' => 'https://api.openai.com', 'token' => 'sk-xxx', 'transport' => 'auto', ]); $http->stream('/v1/chat/completions', [ 'model' => 'gpt-4o-mini', 'stream' => true, 'messages' => [ ['role' => 'user', 'content' => 'Hello'] ], ], function (string $chunk, bool $done) { if ($done) { echo PHP_EOL . '[DONE]' . PHP_EOL; return; } echo $chunk; });
stream()面向基础流式场景,默认适合data:行流。
若要进行完整的大模型流式协议处理,请使用laybot/ai-sdk。
7. 配置项
| 配置项 | 说明 | 默认值 |
|---|---|---|
base_uri |
基础地址,必填 | - |
headers |
默认请求头 | [] |
custom_headers |
附加静态请求头 | [] |
timeout |
请求超时(秒) | 10.0 |
transport |
auto / guzzle / workerman |
auto |
retry |
重试次数 | 2 |
verify |
是否校验证书 | true |
query_array_format |
brackets / repeat |
brackets |
user_agent |
自定义 UA | null |
token |
Bearer Token | null |
api_key |
API Key | null |
api_secret |
Hmac Secret | null |
username |
Basic 用户名 | null |
password |
Basic 密码 | null |
inner_token |
内部服务 Token | null |
logger |
PSR-3 Logger | null |
signer |
自定义 signer | null |
8. 鉴权方式
8.1 Bearer
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'token' => 'your-token', ]);
等价于:
Authorization: Bearer your-token
8.2 ApiKey
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'api_key' => 'your-api-key', 'header' => 'X-API-Key', ]);
8.3 Basic
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'username' => 'demo', 'password' => '123456', ]);
8.4 Hmac
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'api_key' => 'app-key', 'api_secret' => 'secret', ]);
8.5 Inner
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'inner_token' => 'inner-token', ]);
8.6 自定义静态 Header
适用于以下场景:
X-Export-TokenX-Service-TokenX-Internal-App- 其他任意固定 Header 标识
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'custom_headers' => [ 'X-Export-Token' => 'your-export-token', 'X-Service-Name' => 'paper-export', ], ]);
8.7 token 与 custom_headers 的区别
token 是语义化快捷配置,专门表示 Bearer Token:
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'token' => 'your-bearer-token', ]);
等价于:
Authorization: Bearer your-bearer-token
而 custom_headers 表示任意附加静态请求头,例如:
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'custom_headers' => [ 'X-Export-Token' => 'your-export-token', ], ]);
等价于:
X-Export-Token: your-export-token
8.8 多 token / 多 Header 共存
在实际项目中,常见场景是:
- 同时需要
Authorization: Bearer xxx - 又需要额外的
X-Export-Token: yyy - 或其他自定义服务身份 Header
这种情况下,可以直接组合使用:
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'token' => 'bearer-xxx', 'custom_headers' => [ 'X-Export-Token' => 'export-yyy', 'X-Service-Name' => 'paper-export', ], ]);
最终会同时携带:
Authorization: Bearer bearer-xxx X-Export-Token: export-yyy X-Service-Name: paper-export
8.9 鉴权自动推断规则
当未显式传入 signer 时,Client 会按以下顺序自动推断:
api_key + api_secret→HmacSignertoken→BearerSignerusername + password→BasicSignerinner_token→InnerSignerapi_key→ApiKeySigner- 默认
NoneSigner
custom_headers 只作为附加请求头,不参与 signer 推断。
如果你希望完全控制行为,建议直接传入 signer。
8.10 高级用法:自定义 signer
如果你需要完全控制签名逻辑,可以显式传入自定义 signer:
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'signer' => $yourSigner, ]);
这适合:
- 自定义签名算法
- 动态时间戳 / 摘要签名
- 非标准平台鉴权协议
- 需要 method / path / body 参与签名的场景
9. Query 数组格式
支持两种 query 数组编码方式。
9.1 brackets(默认)
适合常规数组 query:
[
'group_by' => ['project_id', 'line_item']
]
编码后类似:
group_by[0]=project_id&group_by[1]=line_item
9.2 repeat
适合某些 API 需要重复参数形式:
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'query_array_format' => 'repeat', ]);
会编码为:
group_by=project_id&group_by=line_item
repeat主要适合一级数组重复参数场景。
10. 链式配置
$http = Client::make([ 'base_uri' => 'https://api.example.com', ]); $http2 = $http ->withTimeout(30) ->withRetry(3) ->withVerify(true) ->withUserAgent('AI-Admin-Client/1.0') ->withHeaders([ 'X-App-Name' => 'admin-service', ]);
支持:
withSigner()withLogger()withRetry()withHeaders()withTimeout()withVerify()withUserAgent()withQueryArrayFormat()
这些方法返回的是新的 Client 实例,不会修改原对象。
适合在常驻进程和并发场景中复用。
11. 方法总览
11.1 返回 JSON array 的方法
这些方法默认要求响应 JSON 解码结果为数组:
| 方法 | 说明 |
|---|---|
send() |
通用请求,JSON 解码为 array |
requestJsonArray() |
通用请求,JSON 解码为 array |
get() |
GET |
postJson() |
POST JSON |
postForm() |
POST Form |
post() |
POST,数组默认按 Form 发送 |
put() |
PUT,数组默认按 JSON 发送 |
patch() |
PATCH,数组默认按 JSON 发送 |
delete() |
DELETE |
11.2 返回 JSON mixed 的方法
这些方法适合底层通用场景:
| 方法 | 说明 |
|---|---|
sendAny() |
通用请求,JSON 解码为 mixed |
requestJsonAny() |
通用请求,JSON 解码为 mixed |
getAny() |
GET |
postJsonAny() |
POST JSON |
postFormAny() |
POST Form |
postAny() |
POST |
putAny() |
PUT |
patchAny() |
PATCH |
deleteAny() |
DELETE |
11.3 原始响应方法
| 方法 | 说明 |
|---|---|
requestRaw() |
获取原始响应 |
head() |
HEAD,返回原始响应 |
options() |
OPTIONS,返回原始响应 |
11.4 文件与流式方法
| 方法 | 说明 |
|---|---|
upload() |
文件上传 |
download() |
下载到本地文件 |
stream() |
基础流式请求 |
12. 关于 post() / put() / patch() 的数组行为
为了兼顾常见使用习惯,这几个方法对数组参数的处理规则不同:
post(array):按form_params发送put(array):按json发送patch(array):按json发送
如果你需要明确语义,建议优先使用:
postJson()postForm()postJsonAny()postFormAny()
13. 文件上传与下载
13.1 文件上传
$res = $http->upload( '/upload', 'file', __DIR__ . '/demo.txt', ['scene' => 'test'] );
说明:
- 上传时不要手动设置
Content-Type multipart boundary由底层 HTTP 客户端自动处理- 上传结束后文件句柄会自动释放
13.2 文件下载
$path = $http->download( '/image/png', __DIR__ . '/runtime/demo.png' ); echo $path;
说明:
- 下载使用临时文件写入,成功后再原子替换目标文件
- 失败时会清理
.part临时文件 - 适合大文件下载场景
14. 流式请求
14.1 最简单的用法
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'token' => 'sk-xxx', ]); $http->stream('/v1/stream', [ 'stream' => true, 'input' => 'hello', ], function (string $chunk, bool $done) { if ($done) { echo PHP_EOL . '[DONE]' . PHP_EOL; return; } echo $chunk . PHP_EOL; });
14.2 指定 Guzzle 作为流式传输层
$http->stream('/v1/stream', [ 'stream' => true, ], function (string $chunk, bool $done) { // ... }, [], [ 'transport' => 'guzzle', ]);
对于关键业务链路,建议优先使用 guzzle。
14.3 指定 Workerman 作为流式传输层
$http->stream('/v1/stream', [ 'stream' => true, ], function (string $chunk, bool $done) { // ... }, [], [ 'transport' => 'workerman', ]);
说明:
- 仅适合事件循环环境
- 适合简单文本流场景
- 遇到复杂响应头时会自动回退到 Guzzle
14.4 流式解析模式
data-line(默认)
只提取以 data: 开头的行,适合常见 SSE 风格接口。
$http->stream('/v1/stream', $payload, $cb, [], [ 'decode' => [ 'mode' => 'data-line', ], ]);
raw-line
按行返回原始文本,不要求必须是 data: 行。
$http->stream('/v1/stream', $payload, $cb, [], [ 'decode' => [ 'mode' => 'raw-line', ], ]);
14.5 自定义结束标记
默认结束标记为 [DONE]。
$http->stream('/v1/stream', $payload, $cb, [], [ 'decode' => [ 'done_token' => '[END]', ], ]);
如果不希望底层识别结束标记,可以传 null:
$http->stream('/v1/stream', $payload, $cb, [], [ 'decode' => [ 'done_token' => null, ], ]);
14.6 流式超时控制
$http->stream('/v1/stream', $payload, $cb, [], [ 'connect_timeout' => 10, 'idle_timeout' => 120, ]);
说明:
connect_timeout:建立连接超时idle_timeout:流式过程中连续静默超时,0表示不限制
14.7 关于流式能力的边界
stream() 提供的是基础流式能力,适合:
text/event-streamdata:行流- 文本行流
- 基础 JSON 行流
默认会发送:
Accept: text/event-stream
如果目标服务要求其他 Accept,可以通过请求头覆盖。
它不负责:
- 厂商流式协议适配
- 增量消息聚合
- 对话消息拼装
- 模型层结束语义处理
如果你的目标是做大模型流式对话,请在上层 SDK 中处理协议语义,而不是把这些逻辑放在 request-sdk 中。
15. Retry 与 Trace
15.1 Retry
默认支持:
- 网络异常重试
- 5xx 重试
- 429 重试
- 指数退避 + 抖动
Retry-After识别
配置示例:
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'retry' => 3, ]);
15.2 Trace
如果传入 PSR-3 Logger,会记录请求与响应调试日志。
$http = Client::make([ 'base_uri' => 'https://api.example.com', 'logger' => $logger, ]);
Trace 中间件会自动对敏感 Header 做脱敏处理。
当前脱敏规则包括:
- 明确高风险头:
Authorization、Proxy-Authorization - 以及 Header 名中包含以下关键词的请求头:
tokensecretkeysignaturesign
此外:
- 文本类型 body 会按长度截断记录
- 二进制 body 不直接写日志
生产环境建议谨慎开启 debug 级别 trace。
16. 异常说明
LayBot\Request\Exception\RequestException
基础请求异常。
LayBot\Request\Exception\HttpException
HTTP 非 2xx 响应异常,可获取:
getStatusCode()getResponseBody()getResponseHeaders()getMethod()getUri()
LayBot\Request\Exception\JsonException
JSON 编解码异常。
LayBot\Request\Exception\StreamException
流式请求异常。
17. 设计说明
17.1 普通请求
普通请求默认走 Guzzle,稳定优先。
17.2 流式请求
流式请求支持 Guzzle 与 Workerman 两种 transport。
其中:
guzzle适合作为默认和主力方案workerman适合事件循环环境下的简单文本流场景
当检测到复杂响应头时,Workerman transport 会优先回退到 Guzzle 处理。
17.3 下载
download() 使用临时文件写入并原子替换目标文件,适合大文件下载场景。
17.4 参数约束
同一次请求中,以下 payload 模式只能使用一种:
jsonform_paramsmultipartbody
18. 模块总览
| 模块 | 组件 | 作用 |
|---|---|---|
| Client | Client |
统一请求入口与便捷方法 |
| Config | Config |
不可变配置对象 |
| Transport | GuzzleTransport / WorkermanTransport |
普通请求与流式请求传输层 |
| Signer | BearerSigner / ApiKeySigner / BasicSigner / HmacSigner / InnerSigner / NoneSigner |
鉴权插拔 |
| Middleware | Retry / Trace |
重试、追踪 |
| Support | Json / Query / UserAgent / Env |
JSON、Query、UA、环境辅助 |
| Util | StreamDecoder |
基础流式解码 |
| Facade | PartnerApi / InnerApi |
固定场景快捷封装 |
19. 与 laybot/ai-sdk 的关系
建议按以下方式分层:
laybot/request-sdk:网络层laybot/ai-sdk:模型语义层- 业务项目:业务逻辑层
职责划分如下:
request-sdk 负责
- HTTP 请求
- 鉴权
- Retry / Trace / Timeout
- 上传下载
- 原始响应
- 基础流式传输
ai-sdk 负责
- 模型厂商协议适配
- Chat / Embedding / Image / Audio 等语义封装
- 流式增量 JSON 解析
[DONE]/finish_reason/ 厂商结束语义- 对话消息聚合
这样可以避免网络层和模型语义层相互耦合。
如果你的目标是做大模型对话、流式增量聚合、厂商协议适配,请使用上层 laybot/ai-sdk。
request-sdk 只负责网络层,不负责模型语义层。
20. 版本建议
当前建议版本:
0.5.x
如果你在大型项目中使用,建议固定小版本范围,并在升级前做一次最小回归验证。
21. 关于 LayBot
LayBot · 灵语智教 专注教育与知识管理的 AIGC 平台,
拥有自研大模型、矢量检索、知识图谱等核心能力,并陆续开源 LayBot 系列 SDK:
laybot/ai-sdk:大模型调用语义层 SDKlaybot/request-sdk:通用网络通信底座storage-sdk:存储相关能力
欢迎关注与 Star。
22. 贡献指南
git clone https://github.com/laybot/request-sdk.git cd request-sdk composer install --dev # 单元测试 vendor/bin/phpunit
License
MIT License