webman中应单例复用guzzlehttp\client实例,避免每次请求new导致连接泄漏或fd耗尽;需在config/container.php中绑定单例,特殊场景下new后显式调用close(),禁在onmessage或中间件中无条件new。

Webman里直接用Guzzle发请求没问题,但别在全局new Client
Webman是常驻内存的长连接框架,GuzzleHttp\Client 实例默认会复用底层 cURL handle 和连接池。如果每次请求都 new \GuzzleHttp\Client(),不仅浪费资源,还可能触发连接泄漏或 fd 耗尽(尤其高并发时)。正确做法是在容器中单例注册,或在控制器/服务类里复用同一个实例。
常见错误现象:Too many open files、响应变慢、偶发 cURL error 7(Failed to connect)。
- 在
config/container.php中绑定单例:$container->singleton(\GuzzleHttp\Client::class, function () { return new \GuzzleHttp\Client(['timeout' => 5.0, 'verify' => false]); }); - 若需不同配置(如某接口必须走代理),单独 new 一次并显式关闭:用完后调用
$client->getConfig('handler')->close()(仅限 Guzzle 7+) - 避免在
onMessage或中间件里无条件 new Client —— 这是最容易被忽略的性能坑
Webman控制器中调用Guzzle的典型写法(含错误处理)
Webman 的控制器不是每次请求都重建对象,所以不能像 Laravel 那样依赖构造注入又不加类型提示。建议显式从容器获取或传参初始化,确保可测可控。
示例场景:向支付网关回调地址发起 POST 确认请求
// app/controller/PaymentController.php
<?php namespace app\controller;
use support\Request;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
class PaymentController
{
public function notify(Request $request)
{
$client = \support\Container::get(\GuzzleHttp\Client::class);
try {
$response = $client->post('https://pay.example.com/api/v1/confirm', [
'json' => $request->all(),
'timeout' => 8.0,
'headers' => ['X-Api-Key' => 'xxx']
]);
if ($response->getStatusCode() === 200) {
return json(['code' => 0, 'msg' => 'success']);
}
} catch (ConnectException $e) {
// DNS失败、目标不可达等网络层问题
\support\Log::error('Guzzle connect failed: ' . $e->getMessage());
} catch (RequestException $e) {
// HTTP 4xx/5xx 或超时
\support\Log::warning('Guzzle request failed: ' . $e->getMessage());
}
return json(['code' => 1, 'msg' => 'fail']);
}
}
file_get_contents 在 Webman 中慎用,尤其带超时或 header
file_get_contents 看似简单,但在 Webman 常驻进程模型下有隐性风险:它不支持连接复用,每次调用都新建 TCP 连接;且 stream_context_create 设置的 timeout 实际是整个请求生命周期(DNS + connect + send + recv),无法分别控制。
更麻烦的是,Webman 默认禁用 allow_url_fopen(出于安全考虑),强行开启会导致日志里频繁刷出 PHP Warning: file_get_contents(): php_network_getaddresses: getaddrinfo failed 类错误。
- 不要用
file_get_contents替代 Guzzle 做业务请求 —— 它连基本的 302 重定向都不自动跟随(除非显式设follow_location) - 若只是读取本地配置文件或静态资源,用
file_get_contents(__DIR__ . '/config.json')完全 OK - 真要极简 GET,改用
curl_init+CURLOPT_RETURNTRANSFER,至少能控制超时和错误码
Guzzle 与 Webman 自带的 HTTP 客户端冲突吗?
Webman 本身不提供内置 HTTP 客户端,所谓“自带”其实是开发者误把 support\HttpClient 当成框架组件。实际上它是 workerman/support 包里的一个轻量封装,底层仍是 cURL,功能非常有限(只支持 GET/POST,无中间件、无连接池、不支持 Promise)。
两者共存完全没问题,但别混用:如果你已经装了 Guzzle,就别再引入 support\HttpClient 增加维护负担;反之,若项目极小、只做几个简单 GET,用 support\HttpClient 更轻——但它不支持 JSON 自动序列化、无统一异常类、header 设置语法也不如 Guzzle 直观。
关键区别点:
-
support\HttpClient::get()返回字符串,GuzzleHttp\Client::get()返回ResponseInterface对象 - Guzzle 的
json选项会自动设Content-Type并 encode body;support\HttpClient需手动json_encode+ 手动加 header - Webman 日志里看到
GuzzleHttp\Handler\CurlMultiHandler表示启用了并发池;而support\HttpClient每次都是独立 cURL 句柄
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











