webman默认不支持graphql订阅,因其基于swoole的http框架设计面向短连接restful场景,而订阅需websocket长连接全双工通信,必须单独启用websocket服务并集成graphql-ws协议。

Webman 本身不原生支持 WebSocket 或 GraphQL 订阅,必须手动集成 graphql-ws(推荐)或旧版 subscriptions-transport-ws。直接用 HTTP 路由跑 Subscription 操作会失败——因为订阅依赖长连接,而 Webman 的 HTTP 请求是短连接、无状态的。
为什么 Webman 默认不支持 Subscription?
Webman 是基于 Swoole 的高性能 PHP HTTP 框架,其核心设计面向 RESTful 场景:每个请求独立生命周期、无连接保持。而 Subscription 必须依托 WebSocket 实现全双工通信,这需要:
- 单独启动 WebSocket 服务(不能复用 HTTP Server)
- 在 WebSocket 连接生命周期内维护上下文(如用户认证、PubSub 实例)
- 将 GraphQL 执行引擎接入 WebSocket 消息流,而非 HTTP 请求流
所以,不是“Webman 不支持 GraphQL”,而是它不自动提供 Subscription 所需的传输层基础设施。
必须启用 WebSocket Server 并配置 graphql-ws 协议
Webman 支持通过 webman/websocket 插件启动独立 WebSocket 服务。关键点:
- WebSocket Server 必须监听独立端口(如
ws://localhost:8081),不能和 HTTP 共用80/443 - 客户端必须使用
graphql-ws客户端库(如@graphql-ws/client),而非 Apollo Client 默认的 HTTP 链接器 - 服务端需实现
onOpen、onMessage、onClose三个回调,并按graphql-ws协议解析 JSON 消息
示例片段(config/plugin/webman/websocket/app/WebSocketHandler.php):
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
public function onMessage($connection, $data)
{
$payload = json_decode($data, true);
switch ($payload['type']) {
case 'connection_init':
$connection->send(json_encode(['type' => 'connection_ack']));
break;
case 'subscribe':
$id = $payload['id'];
$query = $payload['payload']['query'];
// 解析 query,绑定 resolver,启动 PubSub 监听
$this->startSubscription($connection, $id, $query);
break;
case 'complete':
$this->unsubscribe($connection, $payload['id']);
break;
}
}
resolver 中触发 PubSub 的时机必须显式 publish
GraphQL Subscription 字段的 resolve 函数不返回值,而是返回一个 AsyncIterator 或 Observable。但在 PHP 中无法原生支持 async iterator,因此必须借助外部 PubSub 系统(如 Redis PUB/SUB 或内存 ReactPHP\Promise\Deferred)。
- 不要在 Mutation 的 resolver 里直接调用
return $order就完事——必须额外publish到对应主题 - 例如:当
updateOrderStatus执行成功后,需$pubsub->publish("ORDER_{$orderId}", [...]) - 订阅端监听的是主题名(如
ORDER_123),不是 GraphQL 字段名(onOrderUpdated)——二者需映射清楚
常见错误:只改了数据库,忘了 publish,导致客户端永远收不到推送。
权限校验不能只做一次,得在 subscribe 和 publish 两个环节都检查
WebSocket 连接建立时做的用户认证(如从 token 解析 $userId)只是第一步。真正危险的是:用户可能订阅自己无权访问的资源(如 onOrderUpdated(orderId: "999"),但该订单属于别人)。
-
onSubscribe阶段:解析variables,调用 Pundit-style 策略类(如OrderPolicy::canView($user, $order)) -
onPublish阶段:发布前再次校验(防止权限变更后仍推送) - 避免把
userId存在全局变量里——每个 WebSocket 连接应持有独立的$context对象
最容易被忽略的一点:WebSocket 连接不会自动感知用户权限变更。如果管理员中途禁用了某用户,已建立的连接不会断开,后续推送仍可能泄露数据——必须配合心跳 + 权限轮询或主动踢出机制。










