基于 Workerman 的 HTTP/2 协议实现 (RFC 7540 / RFC 7541)
提供完整的 HTTP/2 帧解析、HPACK 头部压缩、流多路复用和流控能力,
通过 Workerman 的 ProtocolInterface 透明接入,Server/Client 均可使用。
- PHP >= 8.1
- Workerman >= 4.0
composer require workbunny/http2-protocolTCP 字节流
│
▼
┌─────────────────────────┐
│ Protocols\Http2 │ Workerman ProtocolInterface
│ input() / decode() │
│ / encode() │
└───────┬─────────────────┘
│
▼
┌─────────────────────────┐
│ Frame │ RFC 7540 Section 4-6
│ DATA / HEADERS / │
│ SETTINGS / PING / ... │
└───────┬─────────────────┘
│
▼
┌─────────────────────────┐
│ Stream │ 流管理、多路复用、流控
│ Request / Response │
└───────┬─────────────────┘
│
▼
┌─────────────────────────┐
│ HPack │ RFC 7541 头部压缩
│ Encoder / Decoder │ 静态表 + 动态表 + Huffman
│ StaticTable / │
│ DynamicTable │
└─────────────────────────┘
收: TCP → input(帧完整性) → decode(帧解析+路由) → Frame → handler → HPack 解码 → onMessage
发: onMessage → 业务数组 → encode(编码) → Frame → HPack 编码 → TCP
use Protocols\Http2;
use Workerman\Worker;
$worker = new Worker('http2://0.0.0.0:8080');
$worker->onConnect = function ($connection) {
Http2::initConnection($connection);
};
$worker->onClose = function ($connection) {
Http2::destroyConnection($connection);
};
$worker->onMessage = function ($connection, $request) {
// $request = [
// 'stream_id' => int,
// 'stream' => Stream,
// 'headers' => list<array{string, string}>,
// 'body' => string,
// ]
$streamId = $request['stream_id'];
$connection->send([
'stream_id' => $streamId,
':status' => '200',
'headers' => ['content-type' => 'text/plain'],
'body' => 'Hello HTTP/2!',
]);
};
Worker::runAll();测试:
php server.php start
curl --http2-prior-knowledge -v http://127.0.0.1:8080/use Protocols\Http2;
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Worker;
$worker = new Worker();
$worker->onWorkerStart = function () {
$conn = new AsyncTcpConnection('http2://127.0.0.1:8080');
$conn->onConnect = function ($conn) {
Http2::initConnection($conn);
// 发送请求,stream_id 自动分配(1, 3, 5, ...)
$conn->send([
':method' => 'GET',
':path' => '/',
':scheme' => 'http',
':authority'=> '127.0.0.1:8080',
]);
};
$conn->onMessage = function ($conn, $data) {
if ($data === null) return;
echo $data['body'];
};
$conn->onClose = function ($conn) {
Http2::destroyConnection($conn);
};
$conn->connect();
};
Worker::runAll();完整示例见 examples/。
.
├── src/
│ ├── Protocol/Http2.php # Workerman 协议入口 (input/decode/encode)
│ ├── Constants.php # RFC 7540 常量定义
│ ├── Stream.php # 流管理、多路复用、流控
│ ├── Frame/
│ │ ├── AbstractFrame.php # 帧基类 (解析/编码/handler)
│ │ ├── DataFrame.php # DATA 帧
│ │ ├── HeadersFrame.php # HEADERS 帧
│ │ ├── PriorityFrame.php # PRIORITY 帧
│ │ ├── RstStreamFrame.php # RST_STREAM 帧
│ │ ├── SettingsFrame.php # SETTINGS 帧
│ │ ├── PushPromiseFrame.php # PUSH_PROMISE 帧
│ │ ├── PingFrame.php # PING 帧
│ │ ├── GoawayFrame.php # GOAWAY 帧
│ │ ├── WindowUpdateFrame.php# WINDOW_UPDATE 帧
│ │ └── ContinuationFrame.php# CONTINUATION 帧
│ └── HPack/
│ ├── StaticTable.php # 静态表 (RFC 7541 Appendix A)
│ ├── DynamicTable.php # 动态表
│ ├── IntegerCodec.php # N-bit prefix 整数编解码
│ ├── Huffman.php # 哈夫曼编解码 (RFC 7541 Appendix B)
│ ├── Encoder.php # HPACK 编码器
│ └── Decoder.php # HPACK 解码器
├── examples/
│ ├── http2-server.php # Server 示例
│ └── http2-client.php # Client 示例
├── tests/
│ └── HPack/ # HPack 单元测试
└── vendor/
遵循 Workerman ProtocolInterface:
| 方法 | 职责 | 输入 → 输出 |
|---|---|---|
input() |
判定帧完整性 | string → int(帧长度) |
decode() |
解析帧 → handler 路由 | string → array|null |
encode() |
编码为二进制帧 | Frame|array|string → string |
// onConnect 中初始化
Http2::initConnection($connection);
// 优雅关闭(发送 GOAWAY 帧后关闭连接)
Http2::shutdown($connection);
$connection->close();
// onClose 中清理
Http2::destroyConnection($connection);Server 收到的请求 (decode() 返回):
[
'stream_id' => 1,
'stream' => Stream, // Stream 实例
'headers' => [ // HPack 解码后的头部列表
[':method', 'GET'],
[':path', '/'],
[':scheme', 'http'],
[':authority', 'example.com'],
['user-agent', 'curl/8.0'],
],
'body' => '', // 请求体
]Server 发送的响应 (encode() 接受):
[
'stream_id' => 1,
':status' => '200', // 或 'status' => 200
'headers' => [ // 可选,自定义响应头
'content-type' => 'text/html',
],
'body' => '<h1>OK</h1>', // 可选
]Client 发送的请求 (encode() 接受):
[
'stream_id' => 1,
':method' => 'GET',
':path' => '/api/users',
':scheme' => 'https',
':authority'=> 'example.com',
'headers' => [ // 可选,自定义请求头
'accept' => 'application/json',
],
'body' => '', // 可选
]
encode()自动判断:有:method→ 按请求编码,有:status→ 按响应编码。
所有帧类型继承 AbstractFrame,统一提供:
parse(buffer)— 静态工厂,根据 buffer 中 type 字段自动分发到对应子类validate(buffer)— 验证帧合法性encode()— 编码为二进制handler(stream, connection)— 帧到达时的处理逻辑
- 状态机:
IDLE → OPEN → HALF_CLOSED → CLOSED - 每个 Stream 独立持有 HPack 编解码器(共享动态表)
- 支持流级别流控窗口(自动 WINDOW_UPDATE)
- 连接通过
$connection->streamPool持有 Stream 池
composer install --dev
./vendor/bin/phpunitphp examples/http2-server.php startcomposer fmt当前无已知待完善项。如需扩展可参考:
| 方向 | 说明 |
|---|---|
| 流式发送大 body | encodeResponse() 一次性编码 HEADERS + DATA,超大 body 可改为分片发送 |
| Client 端协议封装 | 当前 Client 需手动处理 PREFACE + SETTINGS 交换,可封装 Http2Client 类 |
Apache-2.0