Search by

laybot / request-sdk

laybot

Reliable HTTP, SSE, JSON Lines and WebSocket client for PHP/Webman

Package info

github.com/laybot/request-sdk

pkg:composer/laybot/request-sdk

Statistics

Installs: 59

Dependents: 1

Suggesters: 0

Stars: 1

Open Issues: 0

v2.0.6 2026-09-07 06:33 UTC

README

通用 PHP 网络通信与流式请求基础库
稳定 · 易用 · 框架无关 · 面向企业项目与上层 SDK 的统一网络底座

HTTP · Async HTTP · SSE · NDJSON · Raw Stream · WebSocket · Upload · Download

Laybot MPA PHP Version

关于该项目: 本库是 Laybot - 现代 MPA 工程平台 后端生态的核心网络通信底座。从企业级 Web 交付到大模型流式通信,历经真实高并发场景打磨,现作为独立组件完全开源

1. 全链路的企业级工程化:关于 LayBot 体系

本 SDK 脱胎于 Laybot (laybot.cn) 生态。

在实际业务中,我们致力于为企业级 Web 交付打造现代 MPA(多页面)工程平台。为了让老旧资产(如 jQuery/layui 项目)重获新生,同时支持 AOT 预编译、AI 工程协作、GEO/SEO 流量引擎以及商业级私有化交付,我们在前端构建了强大的响应式指令与页面级隔离体系。

而在后端的灵语智教 AI 引擎建设中(面向教学问答、作文精批、高并发结构化题库等场景),我们需要在 PHP 业务系统与各大模型厂商之间,建立一条支持复杂流式输出(SSE/NDJSON/WebSocket)、连接稳定、支持背压控制的“高速公路”。

为此,我们将 LayBot 通用能力逐步整理为可独立复用的系列 SDK:

  • laybot/request-sdk:PHP 通用网络通信底座(本项目)
  • laybot/ai-sdk:多模型厂商 AI 与语音能力聚合 SDK(即将发布)
  • laybot/storage-sdk:文件与对象存储相关能力

不仅服务于 Laybot 平台自身,更期望成为 PHP 开发者接入现代 API 与 AI 大模型时的首选网络基建。如果你对现代 MPA 平台、兼容 layui 生态的重构或桌面端商业交付感兴趣,欢迎访问 Laybot 官方网站

2. 项目简介

在真实服务端项目中,网络请求通常不只是简单调用一次 curl

常见需求包括:

  • 接入第三方 OpenAPI
  • 调用内部微服务
  • 访问 AI、支付、短信、存储和办公平台
  • 上传或下载大型文件
  • 处理 SSE、NDJSON 和原始字节流
  • 建立 WebSocket 长连接
  • 处理 Bearer、API Key、Basic、HMAC 等鉴权
  • 区分连接超时、请求超时和流空闲超时
  • 安全控制请求重试
  • 统一异常和响应对象
  • 记录日志并避免泄露 Token
  • 在常驻进程中可靠释放连接、定时器和文件句柄

Guzzle、Workerman 等组件分别提供了优秀的底层能力,但业务项目通常仍需要统一处理:

请求配置
鉴权
Header
Query
JSON
上传下载
流式协议
超时
取消
重试
异常
日志与脱敏

laybot/request-sdk 的目标,就是将这些网络层能力收敛成一个:

可长期复用
可持续演进
框架无关
安全默认
适合常驻进程

的 PHP 网络通信基础库。

它解决的不是单纯的“能不能发请求”,而是:

如何将请求安全、稳定、可控地发送出去,并以统一方式接收、解析和管理响应生命周期。

3. 主要能力

laybot/request-sdk 提供:

  • 同步 HTTP 请求
  • Workerman 异步 HTTP 请求
  • 同步和异步 SSE
  • 同步和异步 NDJSON / JSON Lines
  • 同步和异步原始字节流
  • Workerman WebSocket Client
  • JSON、Form、Multipart 和原始 Body
  • 文件上传
  • 大文件低内存下载
  • Bearer、API Key、Basic、HMAC、Inner Token
  • 自定义 Signer
  • 连接超时、请求总超时和流空闲超时
  • CancellationToken 和异步 Handle 取消
  • 受控重试与 Retry-After
  • HTTP / HTTPS / WS / WSS 代理
  • TLS 证书和主机名校验
  • 跨 Origin 凭证保护
  • Header 安全合并
  • 日志脱敏
  • 响应大小限制
  • 统一响应 DTO
  • 统一异常体系

4. 框架支持

request-sdk 不是 Webman 专用库。

同步能力基于标准 PHP 和 Guzzle,可用于绝大多数服务端 PHP 框架:

  • Webman
  • Workerman
  • ThinkPHP
  • Laravel
  • Symfony
  • Yii
  • 原生 PHP
  • CLI
  • PHP-FPM
  • WordPress 服务端插件
  • Composer 管理的其他 PHP 项目

4.1 能力兼容矩阵

运行环境 同步 HTTP 同步 SSE/流 异步 HTTP/SSE WebSocket Client
Webman / Workerman 支持 支持 支持 支持
ThinkPHP 支持 支持 需 Workerman 事件循环 需 Workerman 事件循环
Laravel 支持 支持 需 Workerman 事件循环 需 Workerman 事件循环
Symfony 支持 支持 需 Workerman 事件循环 需 Workerman 事件循环
Yii 支持 支持 需 Workerman 事件循环 需 Workerman 事件循环
PHP CLI 支持 支持 需启动 Workerman 需启动 Workerman
PHP-FPM 支持 支持,但受执行时间和网关缓冲影响 不建议 不适用
Swoole / Hyperf 可以使用同步能力 可以,但可能阻塞协程 暂无原生适配器 暂无原生适配器

4.2 同步与异步边界

同步方法始终同步执行:

$http->request(...);
$http->streamSse(...);
$http->streamJsonLines(...);
$http->streamRaw(...);

异步方法始终要求有效的 Workerman 事件循环:

$http->requestAsync(...);
$http->streamSseAsync(...);
$http->streamJsonLinesAsync(...);
$http->streamRawAsync(...);
$http->connectWebSocketAsync(...);

SDK 不会因为检测到 Webman 或 Workerman 环境而自动改变方法语义。

也就是说:

调用同步方法 → 一定同步
调用异步方法 → 一定异步

4.4 支撑完整企业级生态:Laybot MPA 与 AI 协作底座

本 SDK 已在 Laybot 现代 MPA 工程平台 及其内置的 灵语智教 AI 引擎 中历经深度生产检验,完美支撑了以下核心商业链路:

  • LayPilot (AI 开发侧车) 通信:保障 AI 上下文打包、代码补丁生成、流式响应(SSE)与终端环境的稳定双向通信。
  • 私有化交付网络层:配合 Laybot Client(桌面客户端)、License 商业授权系统以及 Publish Worker 进行高可用、强安全校验的通信调度。
  • 企业级大模型路由:作为 laybot/ai-sdk 的下层基建,稳定承载跨学科教学、作业批改等复杂 Prompt 的分发、流式读取与多模型厂协议兼容。

探索 Laybot MPA 平台,重掌 Web 工程控制权 🚀

5. 定位与职责边界

laybot/request-sdk 的定位是:

  • 通用服务端网络通信组件
  • 第三方平台 SDK 的底层网络基座
  • 企业 PHP 项目的统一网络访问层
  • laybot/ai-sdk 的底层传输依赖
  • 内部 OpenAPI 和微服务调用组件

本库负责:

怎么建立连接
怎么发送请求
怎么读取响应
怎么处理流
怎么控制超时
怎么安全重试
怎么取消
怎么上传下载
怎么处理网络异常

本库不负责:

大模型消息语义
Chat Messages
Tool Calls
Function Calls
模型参数映射
模型Usage
流式文本增量归并
TTS/ASR业务状态机
供应商模型错误码

如果需要统一调用 OpenAI、Gemini、Anthropic、DeepSeek、豆包 Ark、千问或火山语音,请使用:

laybot/ai-sdk

职责关系:

request-sdk 负责:怎么请求
ai-sdk      负责:怎么调用模型和语音供应商
业务项目     负责:用户、权限、消息、任务、计费和数据存储

6. 推荐分层

业务项目
    ↓
laybot/ai-sdk 或其他平台SDK
    ↓
laybot/request-sdk
    ↓
Guzzle / Workerman / TCP / TLS

这样可以避免每个上层 SDK 重复实现:

  • HTTP Client
  • SSE Parser
  • NDJSON Parser
  • WebSocket Client
  • Timeout
  • Retry
  • Cancellation
  • TLS
  • Proxy
  • 文件上传下载
  • 日志脱敏
  • 网络异常

7. 运行要求

  • PHP >= 8.1
  • ext-curl
  • ext-json
  • ext-openssl
  • Guzzle 7.x
  • PSR-3 Logger 2.x 或 3.x

使用异步 HTTP 或 WebSocket 时,还需要:

  • Workerman 5.x
  • Workerman HTTP Client

推荐生产环境:

PHP 8.4
当前稳定版Webman
Workerman 5.2+

8. 安装

composer require laybot/request-sdk:^2.0

本地开发:

git clone https://github.com/laybot/request-sdk.git
cd request-sdk

composer install

9. 快速开始

9.1 创建 Client

<?php

use LayBot\Request\Client;

$http = Client::make([
    'base_uri' => 'https://api.example.com',

    'retry' => false,

    'timeouts' => [
        'connect' => 10,
        'request' => 30,
        'idle' => 180,
    ],
]);

base_uri 必须是合法的 HTTP 或 HTTPS 绝对地址。

允许:

https://api.example.com
https://api.example.com/v1

禁止:

https://user:pass@example.com
https://example.com?token=secret
https://example.com#fragment

9.2 普通 GET

$result = $http->get('/users', [
    'page' => 1,
    'limit' => 20,
]);

var_dump($result);

get() 默认要求响应 JSON 根节点为对象或数组。

如果响应可能是字符串、数字、布尔值或 null,使用:

$result = $http->getAny('/value');

9.3 POST JSON

$result = $http->postJson('/orders', [
    'product_id' => 1001,
    'quantity' => 2,
]);

对应:

Content-Type: application/json

9.4 POST Form

$result = $http->postForm('/oauth/token', [
    'grant_type' => 'client_credentials',
    'client_id' => 'client-id',
    'client_secret' => 'client-secret',
]);

对应:

Content-Type: application/x-www-form-urlencoded

9.5 通用请求

$response = $http->request('POST', '/jobs', [
    'query' => [
        'source' => 'admin',
    ],
    'headers' => [
        'X-Request-Source' => 'console',
    ],
    'json' => [
        'name' => 'demo-job',
    ],
]);

request() 返回:

LayBot\Request\DTO\Response

读取响应:

echo $response->status;
echo $response->body;
echo $response->durationMs;
echo $response->attempts;
echo $response->protocolVersion;

$requestId = $response->requestId();
$traceId = $response->traceId();

$array = $response->jsonArray();
$mixed = $response->jsonAny();

10. 在不同框架中使用

10.1 ThinkPHP

<?php

namespace app\service;

use LayBot\Request\Client;

final class PartnerApiService
{
    private Client $http;

    public function __construct()
    {
        $this->http = Client::make([
            'base_uri' => 'https://api.example.com',
            'token' => getenv('PARTNER_API_TOKEN'),
            'retry' => false,
            'timeouts' => [
                'connect' => 5,
                'request' => 30,
                'idle' => 120,
            ],
        ]);
    }

    public function users(int $page = 1): array
    {
        return $this->http->get('/users', [
            'page' => $page,
        ]);
    }
}

Controller 中正常注入或实例化 Service 即可。

10.2 Laravel

use LayBot\Request\Client;

$http = Client::make([
    'base_uri' => config('services.partner.base_uri'),
    'token' => config('services.partner.token'),
]);

$result = $http->get('/users');

可以在 Service Provider 中注册为 Singleton。

10.3 Symfony

use LayBot\Request\Client;

$http = Client::make([
    'base_uri' => $_ENV['PARTNER_BASE_URI'],
    'token' => $_ENV['PARTNER_API_TOKEN'],
]);

$result = $http->get('/users');

可以通过 Symfony DI Container 注册。

10.4 Webman

use LayBot\Request\Client;

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'token' => getenv('API_TOKEN'),
]);

$result = $http->get('/users');

Client 可以在 Worker 生命周期内复用。

对于不能阻塞事件循环的场景,使用明确的异步方法。

10.5 原生 CLI

<?php

require __DIR__ . '/vendor/autoload.php';

use LayBot\Request\Client;

$http = Client::make([
    'base_uri' => 'https://api.example.com',
]);

print_r($http->get('/status'));

11. 请求 Body

同一个请求只能使用一种 Body 模式:

json
form_params
multipart
body

JSON

$response = $http->request('POST', '/data', [
    'json' => [
        'name' => 'LayBot',
    ],
]);

Form

$response = $http->request('POST', '/form', [
    'form_params' => [
        'name' => 'LayBot',
    ],
]);

原始 Body

$response = $http->request('POST', '/xml', [
    'headers' => [
        'Content-Type' => 'application/xml',
    ],
    'body' => '<root><name>LayBot</name></root>',
]);

Multipart

$stream = fopen(__DIR__ . '/demo.txt', 'rb');

try {
    $response = $http->request('POST', '/upload', [
        'multipart' => [
            [
                'name' => 'file',
                'contents' => $stream,
                'filename' => 'demo.txt',
            ],
            [
                'name' => 'scene',
                'contents' => 'document',
            ],
        ],
        'retry' => false,
    ]);
} finally {
    if (is_resource($stream)) {
        fclose($stream);
    }
}

不要手动设置 Multipart Content-Type,Boundary 由底层 Transport 生成。

12. JSON 返回类型

返回数组

$http->send(...);
$http->requestJsonArray(...);
$http->get(...);
$http->postJson(...);
$http->postForm(...);
$http->post(...);
$http->put(...);
$http->patch(...);
$http->delete(...);

返回任意 JSON 类型

$http->sendAny(...);
$http->requestJsonAny(...);
$http->getAny(...);
$http->postJsonAny(...);
$http->postFormAny(...);
$http->postAny(...);
$http->putAny(...);
$http->patchAny(...);
$http->deleteAny(...);

获取原始响应

$response = $http->request('GET', '/data');

兼容数组形式:

$raw = $http->requestRaw('GET', '/data');

/*
[
    'status' => 200,
    'headers' => [...],
    'body' => '...',
]
*/

13. Header

全局 Header

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'headers' => [
        'X-App-Name' => 'admin-service',
    ],
]);

兼容:

'custom_headers' => [
    'X-App-Name' => 'admin-service',
],

单次请求 Header

$response = $http->request('GET', '/users', [
    'headers' => [
        'X-Tenant-Id' => 'tenant-1001',
    ],
]);

Header 名大小写不敏感,后设置的同名 Header 会覆盖前者。

包含 CR 或 LF 的 Header 名和值会被拒绝,防止 Header Injection。

14. 鉴权与签名

14.1 Bearer Token

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'token' => getenv('API_TOKEN'),
]);

生成:

Authorization: Bearer <token>

14.2 API Key

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'api_key' => getenv('API_KEY'),
    'header' => 'X-API-Key',
]);

14.3 Basic

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'username' => 'username',
    'password' => 'password',
]);

14.4 HMAC

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'api_key' => getenv('API_KEY'),
    'api_secret' => getenv('API_SECRET'),
]);

不同供应商的 HMAC 规范可能不同。涉及 Query、Host、最终 Header 或特定 Canonical Request 时,应实现专用 ContextSignerInterface

14.5 内部服务 Token

$http = Client::make([
    'base_uri' => 'https://internal.example.com',
    'inner_token' => getenv('INNER_TOKEN'),
]);

生成:

X-Inner-Token: <token>

14.6 多个认证 Header 共存

$http = Client::make([
    'base_uri' => 'https://api.example.com',

    'token' => getenv('BEARER_TOKEN'),

    'custom_headers' => [
        'X-Export-Token' => getenv('EXPORT_TOKEN'),
        'X-Service-Name' => 'paper-export',
    ],
]);

最终会携带:

Authorization: Bearer ...
X-Export-Token: ...
X-Service-Name: paper-export

14.7 自动推断顺序

未显式设置 Signer 时:

api_key + api_secret → HmacSigner
token                → BearerSigner
username + password  → BasicSigner
inner_token          → InnerSigner
api_key              → ApiKeySigner
无配置               → NoneSigner

复杂生产鉴权推荐显式传入专用 Signer。

15. Query 数组编码

支持:

brackets
indices
repeat
comma

示例:

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'query_array_format' => 'repeat',
]);

$response = $http->request('GET', '/users', [
    'query' => [
        'ids' => [1, 2],
    ],
]);

结果:

ids=1&ids=2

16. 超时

支持三类超时:

配置 说明 默认值
connect TCP、代理或 TLS 连接超时 10 秒
request 整个请求总超时 30 秒
idle 流式请求连续无数据超时 180 秒

全局配置:

$http = Client::make([
    'base_uri' => 'https://api.example.com',

    'timeouts' => [
        'connect' => 10,
        'request' => 30,
        'idle' => 180,
    ],
]);

单次覆盖:

$response = $http->request('GET', '/slow', [
    'connect_timeout' => 5,
    'request_timeout' => 60,
    'idle_timeout' => 30,
]);

流式请求通常建议:

[
    'connect_timeout' => 10,
    'request_timeout' => 0,
    'idle_timeout' => 120,
]

含义:

request_timeout=0:不限制流的总持续时间
idle_timeout=120:连续120秒无数据时中断

17. Retry

默认关闭

'retry' => false

默认关闭是为了避免:

  • POST 重复提交
  • AI 请求重复计费
  • TTS 重复合成
  • ASR 重复创建任务
  • 文件重复上传
  • 支付或订单重复创建

简单配置

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'retry' => 2,
]);

表示首次失败后最多再重试两次。

完整配置

$http = Client::make([
    'base_uri' => 'https://api.example.com',

    'retry' => [
        'max_attempts' => 3,
        'base_delay_ms' => 200,
        'max_delay_ms' => 5000,
        'jitter_ms' => 100,
        'retry_statuses' => [
            429,
            502,
            503,
            504,
        ],
        'safe_methods_only' => true,
    ],
]);

默认安全方法:

GET
HEAD
OPTIONS
TRACE

确认业务幂等后,可显式声明:

$response = $http->request('POST', '/jobs', [
    'idempotent' => true,
    'retry' => [
        'max_attempts' => 3,
    ],
    'json' => [
        'request_id' => 'unique-business-id',
    ],
]);

Multipart 和已经产生部分流式输出的请求不会透明重试。

18. SSE

同步 SSE

use LayBot\Request\DTO\SseEvent;

$result = $http->streamSse(
    'POST',
    '/v1/events',
    [
        'headers' => [
            'Accept' => 'text/event-stream',
        ],
        'json' => [
            'stream' => true,
        ],
        'request_timeout' => 0,
        'idle_timeout' => 120,
    ],
    static function (SseEvent $event): void {
        if ($event->comment) {
            return;
        }

        echo $event->data;
    },
    '[DONE]'
);

支持:

  • 网络半包和粘包
  • CRLF 和 LF
  • 多行 data
  • event
  • id
  • retry
  • Comment 心跳
  • 最后一行无换行
  • 自定义结束 Token
  • 单事件大小限制

异步 SSE

异步方法必须运行在 Workerman 事件循环内:

$handle = $http->streamSseAsync(
    'POST',
    '/v1/events',
    [
        'json' => [
            'stream' => true,
        ],
        'request_timeout' => 0,
        'idle_timeout' => 120,
    ],
    static function ($head): void {
        echo 'HTTP status: ', $head->status, PHP_EOL;
    },
    static function ($event): void {
        if (!$event->comment) {
            echo $event->data;
        }
    },
    static function ($result): void {
        echo $result->termination->value;
    },
    static function (\Throwable $error): void {
        echo $error->getMessage();
    },
    '[DONE]'
);

19. NDJSON / JSON Lines

use LayBot\Request\DTO\JsonLine;

$result = $http->streamJsonLines(
    'GET',
    '/v1/logs',
    [
        'request_timeout' => 0,
        'idle_timeout' => 60,
    ],
    static function (JsonLine $line): void {
        var_dump($line->value);
    }
);

支持:

  • 网络半包和粘包
  • CRLF 和 LF
  • 空行处理
  • 最后一行无换行
  • JSON 错误定位
  • 单行大小限制

20. 原始字节流

$result = $http->streamRaw(
    'GET',
    '/audio',
    [
        'request_timeout' => 0,
        'idle_timeout' => 60,
    ],
    static function (string $chunk): bool {
        echo strlen($chunk), PHP_EOL;

        return true;
    }
);

适用于:

  • 音频
  • 图片
  • 二进制文件
  • 自定义 Chunked 协议
  • 上层 SDK 自行解析的供应商二进制协议

request-sdk 只保证字节顺序,不解释业务帧含义。

21. 兼容基础流入口

保留:

$http->stream(
    '/v1/stream',
    [
        'input' => 'hello',
        'stream' => true,
    ],
    static function (
        string $data,
        bool $done
    ): void {
        if ($done) {
            echo '[DONE]';
            return;
        }

        echo $data;
    }
);

新项目推荐优先使用:

streamSse()
streamJsonLines()
streamRaw()

它们具有更明确的协议语义和终止状态。

22. Workerman 异步 HTTP

use LayBot\Request\Client;
use LayBot\Request\DTO\Response;
use Workerman\Worker;

$worker = new Worker();

$worker->onWorkerStart = static function (): void {
    $http = Client::make([
        'base_uri' => 'https://api.example.com',
        'retry' => false,
    ]);

    $http->requestAsync(
        'GET',
        '/users',
        [],
        static function (Response $response): void {
            var_dump($response->jsonArray());
        },
        static function (\Throwable $error): void {
            echo $error->getMessage();
        }
    );
};

Worker::runAll();

在已经启动的 Webman Worker 中,不要再次调用:

Worker::runAll();

直接调用异步方法即可。

23. 取消请求

通过异步 Handle

$handle = $http->requestAsync(
    'GET',
    '/slow',
    [],
    $onComplete,
    $onError
);

$handle->cancel('user cancelled');

通过 CancellationToken

use LayBot\Request\Stream\CancellationSource;

$source = new CancellationSource();

$handle = $http->requestAsync(
    'GET',
    '/slow',
    [
        'cancellation' => $source->token(),
    ],
    $onComplete,
    $onError
);

$source->cancel('user stopped');

同步请求正在阻塞系统调用时,CancellationToken 无法像异步连接一样瞬时中断底层调用。

需要立即取消的长连接场景应使用异步 API。

24. WebSocket Client

WebSocket Client 基于 Workerman 事件循环。

use LayBot\Request\Contract\WebSocketConnectionInterface;
use LayBot\Request\DTO\WebSocketCloseInfo;
use LayBot\Request\DTO\WebSocketOpenInfo;
use LayBot\Request\DTO\WebSocketRequest;
use LayBot\Request\Stream\WebSocketListener;

$listener = new class extends WebSocketListener {
    public function onOpen(
        WebSocketConnectionInterface $connection,
        WebSocketOpenInfo $info
    ): void {
        $connection->sendText('hello');
    }

    public function onText(
        WebSocketConnectionInterface $connection,
        string $payload
    ): void {
        echo $payload;
    }

    public function onBinary(
        WebSocketConnectionInterface $connection,
        string $payload
    ): void {
        echo 'Binary bytes: ', strlen($payload);
    }

    public function onClose(
        WebSocketConnectionInterface $connection,
        WebSocketCloseInfo $info
    ): void {
        printf(
            "Closed: %d %s\n",
            $info->code,
            $info->reason
        );
    }

    public function onError(
        WebSocketConnectionInterface $connection,
        \Throwable $error
    ): void {
        echo $error->getMessage();
    }
};

$connection = $http->connectWebSocketAsync(
    new WebSocketRequest(
        url: 'wss://api.example.com/realtime',
        connectTimeout: 10,
        idleTimeout: 120,
        pingInterval: 30,
        closeTimeout: 5,
        maxMessageBytes: 16 * 1024 * 1024
    ),
    $listener
);

支持:

  • ws://
  • wss://
  • 自定义 Header
  • Client Signer
  • 文本消息
  • 二进制消息
  • 服务端分片消息
  • Ping / Pong
  • Close 握手
  • TLS / SNI
  • 代理
  • 空闲超时
  • 消息大小限制
  • 发送背压
  • 主动取消

发送:

$connection->sendText('hello');
$connection->sendBinary($binary);
$connection->ping('heartbeat');
$connection->close(1000, 'finished');
$connection->cancel('cancelled');

25. WebSocket 背压

$request = new WebSocketRequest(
    url: 'wss://api.example.com/realtime',
    lowWaterMark: 1 * 1024 * 1024,
    highWaterMark: 4 * 1024 * 1024,
    hardBufferLimit: 16 * 1024 * 1024
);

Listener 可处理:

public function onBufferFull(
    WebSocketConnectionInterface $connection
): void {
    // 暂停上游数据生产。
}

public function onBufferDrain(
    WebSocketConnectionInterface $connection
): void {
    // 恢复上游数据生产。
}

超过硬限制会抛出:

BackpressureException

26. 文件上传

$result = $http->upload(
    '/files',
    'file',
    __DIR__ . '/demo.pdf',
    [
        'scene' => 'document',
    ],
    [
        'X-Tenant-Id' => 'tenant-1001',
    ]
);

说明:

  • 文件必须存在且可读
  • 文件句柄在请求结束后释放
  • Multipart Boundary 自动生成
  • Multipart 默认禁止自动重试

27. 文件下载

$path = $http->download(
    '/files/demo.pdf',
    __DIR__ . '/runtime/demo.pdf',
    query: [],
    headers: [],
    maxBytes: 512 * 1024 * 1024
);

下载流程:

写入目标文件.part
    ↓
完整成功
    ↓
原子重命名为目标文件

失败时清理 .part 文件。

异步下载:

$handle = $http->downloadAsync(
    path: '/files/demo.pdf',
    saveTo: __DIR__ . '/runtime/demo.pdf',
    query: [],
    headers: [],
    onComplete: static function (string $path): void {
        echo $path;
    },
    onError: static function (\Throwable $error): void {
        echo $error->getMessage();
    },
    maxBytes: 512 * 1024 * 1024
);

28. Proxy

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'proxy' => 'http://127.0.0.1:8080',
]);

单次 HTTP 请求可以覆盖代理:

$response = $http->request('GET', '/data', [
    'proxy' => 'http://127.0.0.1:8081',
]);

生产环境应分别验证:

  • HTTP Forward Proxy
  • HTTPS CONNECT
  • WS
  • WSS
  • 代理认证

29. TLS

默认开启:

'verify' => true

本地自签名测试可临时关闭:

$http = Client::make([
    'base_uri' => 'https://localhost:8443',
    'verify' => false,
]);

生产环境不得关闭 TLS 验证。

30. 跨 Origin 安全

绝对 URL 默认只能访问与 base_uri 相同 Origin 的地址。

跨 Origin 请求必须显式允许:

$response = $http->request(
    'GET',
    'https://other.example.com/data',
    [
        'allow_cross_origin' => true,
    ]
);

跨 Origin 后,SDK 默认移除:

  • Authorization
  • Proxy-Authorization
  • Cookie
  • API Key
  • Token
  • Secret
  • Signature
  • 自定义敏感 Header

只有明确可信的目标才允许继续转发凭证:

[
    'allow_cross_origin' => true,
    'forward_cross_origin_credentials' => true,
]

31. 日志与敏感信息脱敏

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'logger' => $logger,
]);

默认脱敏:

  • Authorization
  • Proxy-Authorization
  • Cookie
  • Set-Cookie
  • X-API-Key
  • X-Inner-Token
  • 包含 Token、Secret、Password、Credential、Signature 的 Header
  • -Key 结尾的 Header

自定义敏感 Header:

$http = $http->withSensitiveHeaders([
    'X-Gateway-Credential',
    'X-Proxy-*',
]);

请求和响应正文默认不写日志:

'log_bodies' => false

生产环境应谨慎开启正文日志。

32. 不可变链式配置

use LayBot\Request\Middleware\RetryPolicy;
use LayBot\Request\Timeout\TimeoutConfig;

$configured = $http
    ->withHeaders([
        'X-App-Name' => 'admin-service',
    ])
    ->withTimeouts(new TimeoutConfig(
        connect: 5,
        request: 30,
        idle: 120
    ))
    ->withRetryPolicy(new RetryPolicy(
        maxAttempts: 2
    ))
    ->withVerify(true)
    ->withUserAgent('LayBot-Admin/2.0')
    ->withQueryArrayFormat('repeat')
    ->withSensitiveHeaders([
        'X-Private-*',
    ])
    ->withProxy(null);

链式方法返回新 Client,不修改原对象,适合:

  • 常驻 Worker
  • 多租户
  • 多项目
  • 主备线路
  • 不同安全配置

33. 异常体系

基础异常:

RequestException

主要异常:

HttpException
├── AuthenticationException
├── AuthorizationException
└── RateLimitException

ConnectionException
├── ConnectTimeoutException
└── TlsException

RequestTimeoutException
└── StreamIdleTimeoutException

StreamException
├── StreamCallbackException
├── StreamProtocolException
└── UnexpectedEofException

WebSocketException
├── WebSocketHandshakeException
├── WebSocketProtocolException
└── BackpressureException

CancelledException
ConfigurationException
JsonException
ResponseTooLargeException
SinkException

示例:

use LayBot\Request\Exception\HttpException;
use LayBot\Request\Exception\RequestException;

try {
    $response = $http->request('GET', '/data');
} catch (HttpException $error) {
    echo $error->getStatusCode();
    echo $error->getRequestId();
} catch (RequestException $error) {
    echo $error->getMethod();
    echo $error->getUrl();
    echo $error->getMessage();
}

34. 响应大小限制

默认最大普通响应:

16 MB

配置:

$http = Client::make([
    'base_uri' => 'https://api.example.com',
    'max_response_bytes' => 32 * 1024 * 1024,
]);

流式限制:

[
    'max_event_bytes' => 8 * 1024 * 1024,
    'max_line_bytes' => 8 * 1024 * 1024,
]

WebSocket 消息限制通过 WebSocketRequest 设置。

35. 方法总览

同步 HTTP

request()
send()
sendAny()
requestJsonArray()
requestJsonAny()
requestRaw()

get()
getAny()
postJson()
postJsonAny()
postForm()
postFormAny()
post()
postAny()
put()
putAny()
patch()
patchAny()
delete()
deleteAny()
head()
options()

异步 HTTP

requestAsync()

同步流

streamRaw()
streamSse()
streamJsonLines()
stream()

异步流

streamRawAsync()
streamSseAsync()
streamJsonLinesAsync()

文件

upload()
download()
downloadAsync()

WebSocket

connectWebSocketAsync()

不可变配置

withSigner()
withLogger()
withHeaders()
withTimeout()
withTimeouts()
withVerify()
withRetry()
withRetryPolicy()
withProxy()
withUserAgent()
withQueryArrayFormat()
withSensitiveHeaders()

36. 与 laybot/ai-sdk 的关系

推荐架构:

业务项目
    ↓
laybot/ai-sdk
    ↓
laybot/request-sdk
    ↓
Guzzle / Workerman / TCP / TLS

request-sdk 负责:

  • HTTP
  • SSE
  • NDJSON
  • 原始字节流
  • WebSocket
  • TLS
  • Proxy
  • Timeout
  • Cancellation
  • Retry
  • Header
  • 鉴权
  • 上传下载
  • 网络异常

ai-sdk 负责:

  • OpenAI、Gemini、Anthropic、Ark 等供应商协议
  • Chat、Embedding、Image、Audio 等模型资源
  • stream: true/false
  • 模型参数映射
  • 流式文本增量
  • Tool Call 归并
  • Token Usage
  • Finish Reason
  • TTS 和 ASR 业务协议
  • 供应商异常转换

如果只需要调用任意 HTTP API,直接使用 request-sdk

如果需要统一调用大模型或语音能力,使用 ai-sdk

37. 测试

静态检查与单元测试

composer check

建议包含:

composer validate
PHP语法检查
PHPUnit
PHPStan

安全审计:

composer audit

本地网络集成测试

composer test:integration

集成测试应使用本地 Mock Server 验证:

  • HTTP
  • SSE
  • NDJSON
  • 上传下载
  • Retry
  • 异步 HTTP
  • 异步取消
  • WebSocket 文本和二进制
  • WebSocket 分片
  • Ping / Pong
  • Close

这些测试不需要第三方 API Key,也不会产生费用。

发布检查

composer check:release

建议在以下 PHP 版本执行:

PHP 8.1
PHP 8.2
PHP 8.3
PHP 8.4

38. Webman 使用建议

普通短请求:

$response = $http->request(...);

以下场景优先使用异步 API:

  • WebSocket 服务
  • 长时间 SSE
  • 实时 TTS / ASR
  • 多上游并发
  • 不能阻塞事件循环的 Process

不要在已经运行的 Webman Worker 中再次调用:

Worker::runAll();

39. ThinkPHP、Laravel 和 PHP-FPM 使用建议

普通 HTTP、文件上传下载和同步流都可以直接使用。

如果在 PHP-FPM Controller 中将上游 SSE 继续转发到浏览器,还需要额外处理:

  • PHP 最大执行时间
  • Nginx proxy_buffering
  • FastCGI Buffer
  • 网关读取超时
  • 输出缓冲
  • 客户端断开检测

这些属于 PHP-FPM 和 Web Server 配置,不属于 SDK 本身。

长期 WebSocket Client 或大量异步连接不建议运行在传统 PHP-FPM 请求内。

40. 使用限制

当前 2.x 的明确边界:

  • 不自动处理浏览器 CORS
  • 不维护浏览器 Cookie 会话
  • 不负责网页自动化
  • 不负责模型业务协议
  • 请求级 TLS 覆盖受到安全限制
  • Multipart 签名需要供应商专用实现
  • 同步阻塞请求不能被 CancellationToken 瞬时中断
  • WebSocket Client 需要 Workerman 事件循环
  • 当前没有 ReactPHP、Swoole 或 Amp 原生异步 Transport

41. 从 0.5.x 升级到 2.x

2.x 的主要变化:

同步和异步方法语义明确
不再根据运行环境自动切换Transport
默认关闭透明重试
提供完整SSE Parser
提供NDJSON Parser
提供原始字节流
提供异步HTTP
提供异步取消
提供WebSocket Client
增加三类超时
增加响应大小限制
增加跨Origin凭证保护
完善异常体系
完善日志脱敏
完善下载临时文件处理

0.5.x 中依赖:

transport=auto
Workerman失败后回退Guzzle
普通POST默认重试
简单data行流

的代码,需要按 2.x 明确的同步、异步和重试语义进行调整。

42. 贡献指南

git clone https://github.com/laybot/request-sdk.git
cd request-sdk

composer install

composer check
composer test:integration
composer audit

提交代码前应确保:

Composer Validate通过
PHP语法检查通过
PHPUnit通过
PHPStan通过
没有提交真实API Key
没有提交运行时敏感日志

43. LayBot 系列 SDK

LayBot · 灵语智教 专注教育与知识管理的 AIGC 平台,持续建设并开放可复用的基础组件。

当前系列包括:

  • laybot/request-sdk:PHP 通用网络通信底座
  • laybot/ai-sdk:多模型厂商与语音能力聚合 SDK
  • laybot/storage-sdk:存储相关能力 SDK

request-sdk 负责稳定传输,ai-sdk 负责模型语义,业务项目负责产品逻辑。

欢迎关注与 Star。

License & Copyright

本项目采用 Apache License 2.0 开源协议。

版权所有 © larry · LayBot (https://www.laybot.cn)

为了促进技术交流,本项目核心基础库开放源码。但我们坚决捍卫开源作者的署名权与劳动成果:

  • 允许:免费用于商业项目、修改源码、闭源分发。
  • 强制要求:如果在二次开发、衍生项目或商业产品中复用了本项目代码,必须完整保留根目录的 NOTICE 文件,并在产品的说明文档或关于页面显著标明:“底层网络基础组件基于 LayBot (laybot.cn) 研发”。

严禁直接换皮、抹除作者信息后作为独立开源网络库重新发布。