- 后端
- Web框架
- 微服务
- RPC框架
- 异步编程
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
本指南围绕 Hyperf 框架中的hyperf/guzzle组件展开,讲解如何将传统同步阻塞的 Guzzle HTTP 客户端改造为基于 Swoole 协程调度的非阻塞客户端,涵盖CoroutineHandler、PoolHandler连接池、HandlerStackFactory重试中间件以及ClassMap替换第三方组件内部客户端的完整方案。读完本文,你将能在 Hyperf 项目中直接写出高性能的协程化 HTTP 请求代码,并理解连接池解决 TIME-WAIT 问题与 TCP 连接复用背后的源码级原理。
组件定位与工作原理
hyperf/guzzle 是 Hyperf 官方提供的 Guzzle 协程化组件。其核心思路是:基于 Guzzle 做协程处理,通过 Swoole HTTP 客户端作为协程驱动替换进 Guzzle,从而实现 HTTP 客户端的协程化。
在 Hyperf(基于 Swoole 常驻内存 + 协程调度)环境中,如果直接使用 Guzzle 默认的 CurlHandler,每次请求都会触发同步阻塞的系统调用,导致 Worker 进程阻塞,协程的优势无从发挥。hyperf/guzzle提供的处理程序(Handler)接管了 Guzzle 的传输层,将网络收发切换到 Swoole 的协程 HTTP 客户端(Hyperf\Engine\Http\Client)上,使请求在发起 I/O 时自动让出协程,从而支撑高并发场景。
从当前仓库的 composer.json 可以看到组件运行前提:PHP>=8.2、guzzlehttp/guzzle: ^7.0、hyperf/engine: ^2.0,并建议安装ext-curl(CURL handler 支持)与hyperf/pool(连接池 handler 支持)。
安装与版本约束
composer require hyperf/guzzle该组件对 Guzzle 的依赖已从^6.3调整为^6.3 | ^7.0,默认即可安装^7.0版本(在当前仓库 3.2 分支的 composer.json 中依赖已直接声明为guzzlehttp/guzzle: ^7.0)。但以下组件会与^7.0冲突:
hyperf/metric:可通过主动固定其依赖的 prometheus 客户端版本来解决冲突:
composer require "promphp/prometheus_client_php:2.2.1"overtrue/flysystem-cos:由于其依赖的
guzzlehttp/guzzle-services尚不支持^7.0,暂时无法解决冲突。
快速开始:使用 CoroutineHandler 与 ClientFactory
只需将组件中的Hyperf\Guzzle\CoroutineHandler作为 Handler 注入 Guzzle 客户端,即可把请求转换为协程操作。为方便创建协程化 Guzzle 对象,组件提供了工厂类Hyperf\Guzzle\ClientFactory:
<?php use Hyperf\Guzzle\ClientFactory; class Foo { /** * @var \Hyperf\Guzzle\ClientFactory */ private $clientFactory; public function __construct(ClientFactory $clientFactory) { $this->clientFactory = $clientFactory; } public function bar() { // $options 等价于 GuzzleHttp\Client 构造函数的 $config 参数 $options = []; // $client 是一个协程化的 GuzzleHttp\Client 对象 $client = $this->clientFactory->create($options); } }从 ClientFactory::create() 的源码可以看到它的智能化逻辑:
- 仅在**运行于 Swoole 环境(
extension_loaded('swoole'))且当前处于协程上下文(Coroutine::inCoroutine())**时,才自动注入HandlerStack::create(new CoroutineHandler()); - 同时会检查
Runtime::getHookFlags()是否已开启SWOOLE_HOOK_NATIVE_CURL——如果 Swoole 的原生 Curl Hook 已生效,说明框架已通过 Hook 方式完成了协程化,则不再重复注入 Handler; - 创建客户端时优先通过
$container->make(Client::class, ['config' => $config])走依赖注入容器,以支持 AOP 切面。
这保证了在普通 CLI 脚本(非协程环境)或已开启 Curl Hook 的场景下不会产生冲突。
透传 Swoole 配置:swoole 配置项
有时我们想直接修改 Swoole HTTP 客户端的底层配置,组件也提供了相应配置项。但需要注意:该配置无法作用于 Curl Guzzle 客户端,请谨慎使用。
该配置会替换原配置。例如下方的 timeout 会被替换为 10。
<?php use GuzzleHttp\Client; use Hyperf\Guzzle\CoroutineHandler; use GuzzleHttp\HandlerStack; $client = new Client([ 'base_uri' => 'http://127.0.0.1:8080', 'handler' => HandlerStack::create(new CoroutineHandler()), 'timeout' => 5, 'swoole' => [ 'timeout' => 10, 'socket_buffer_size' => 1024 * 1024 * 2, ], ]); $response = $client->get('/');这一行为的依据在 CoroutineHandler::getSettings() 的源码末尾:当$options['swoole']存在且为数组时,执行$settings = array_replace($settings, $options['swoole']),即后写入的 swoole 配置直接覆盖此前由 timeout、proxy 等选项推导出的所有底层设置。组件测试 testSwooleSetting 也验证了swoole.timeout = 10会覆盖 Guzzle 层的timeout = 5。
socket_buffer_size用于调整 Swoole 协程 HTTP 客户端的 Socket 缓冲区大小,适合传输大体积响应的场景。
底层实现细节:CoroutineHandler 对请求选项的完整映射
CoroutineHandler 是整个组件的传输核心,它实现了 Guzzle 的 Handler 接口(__invoke(RequestInterface, array $options)),内部将 PSR-7 请求转换为Hyperf\Engine\Http\Client的调用。结合源码与 CoroutineHandlerTest 测试用例,梳理出以下关键行为:
默认端口:http→ 80,https→ 443(见 getPort()),URL 未显式指定端口时会自动补全;不支持的 scheme 会抛出InvalidArgumentException。
请求头重写:rewriteHeaders() 会强制移除Content-Length与Expect头——源码注释说明移除 Content-Length 是"未知原因下有时会导致 400",而Expect(100-continue)头不被 Swoole 协程 HTTP 客户端支持。测试 testExpect100Continue 验证了该行为。
认证信息:URL 中的user:password@用户信息会被转换为Authorization: Basic base64(...)头(initHeaders(),测试 testUserInfo)。
SSL 校验(verify选项):
verify => false:关闭服务端证书校验(ssl_verify_peer = false);verify => true:开启校验并允许自签名证书(ssl_allow_self_signed = true),同时设置ssl_host_name;verify => '/path/to/ca.pem':指定 CA 文件路径,若路径不存在会抛出InvalidArgumentException;若为目录则映射为ssl_capath,若为文件则映射为ssl_cafile。
超时(timeout):大于 0 时映射为底层timeout设置。
代理(proxy):支持字符串(如http://user:pass@127.0.0.1:8081)与按 scheme 区分的数组形式(['http' => ..., 'https' => ..., 'no' => ['.cn']]),映射为http_proxy_host、http_proxy_port、http_proxy_user、http_proxy_password;命中no直连白名单的主机不会走代理。相关场景均有测试覆盖(testProxy、testProxyArrayHttpScheme、testProxyArrayHostInNoproxy)。
客户端证书(ssl_key/cert):分别映射为ssl_key_file与ssl_cert_file,典型场景是调用微信支付等要求双向 TLS 的接口(测试 testSslKeyAndCert)。
延迟(delay):毫秒级usleep实现。
其他透传能力:支持 Guzzle 标准的sink(响应体写入文件/流,见 createSink())与on_stats回调(构造TransferStats上报传输耗时,测试 testRequestOptionOnStats)。连接失败时统一包装为ConnectException并以 rejected promise 返回,错误上下文携带errCode(测试 testCreatesErrorsWithContext)。
连接池:PoolHandler 与 TIME-WAIT 问题
Hyperf 不仅实现了Hyperf\Guzzle\CoroutineHandler,还基于Hyperf\Pool\SimplePool实现了Hyperf\Guzzle\PoolHandler。
为什么需要连接池
主机 TCP 连接数量存在上限。当并发超过上限时,请求无法正常建立;此外,TCP 连接结束后会出现TIME-WAIT状态,导致连接无法及时释放。因此我们需要一个连接池来维持这一阶段,最小化 TIME-WAIT 的影响,并让 TCP 连接得以复用。
连接池的接入方式
<?php use GuzzleHttp\Client; use Hyperf\Coroutine\Coroutine; use GuzzleHttp\HandlerStack; use Hyperf\Guzzle\PoolHandler; use Hyperf\Guzzle\RetryMiddleware; $handler = null; if (Coroutine::inCoroutine()) { $handler = make(PoolHandler::class, [ 'option' => [ 'max_connections' => 50, ], ]); } // 默认重试中间件 $retry = make(RetryMiddleware::class, [ 'retries' => 1, 'delay' => 10, ]); $stack = HandlerStack::create($handler); $stack->push($retry->getMiddleware(), 'retry'); $client = make(Client::class, [ 'config' => [ 'handler' => $stack, ], ]);从 PoolHandler 源码可看到连接池的关键设计:
- 按目标地址分池:getPoolName() 以
guzzle.handler.{host}.{port}.{scheme}作为池名,不同目标主机天然隔离,互不影响; - 连接复用:通过
PoolFactory::get()获取(或惰性创建)连接,请求完成后在finally中调用$connection->release()归还连接,即使请求抛异常也能正确回收; - 异常处理:请求失败时主动
$connection->close()关闭坏连接,避免把失效连接放回池中; - Cookie 持久化开关:构造参数
$isCookiePersistent默认true;置为false时每次请求前调用$client->setCookies([])清空 Cookie,适用于不希望跨请求保留 Cookie 的场景。
option数组支持Hyperf\Pool\Pool::initOption()定义的完整连接池参数,如min_connections(最小连接数)、max_connections(最大连接数)、wait_timeout(获取连接超时,秒)、max_idle_time(最大空闲时间,秒)。测试 testPoolHandler 验证了连续两次请求后连接池计数仍为 1,即第二次请求直接复用了第一次的连接。
一站式构建 HandlerStack:HandlerStackFactory 与 RetryMiddleware
上述"协程 Handler + 重试中间件 + 连接池"的组合,框架还提供了HandlerStackFactory便捷封装,一行即可创建出配置完整的$stack:
<?php use Hyperf\Guzzle\HandlerStackFactory; use GuzzleHttp\Client; $factory = new HandlerStackFactory(); $stack = $factory->create(); $client = make(Client::class, [ 'config' => [ 'handler' => $stack, ], ]);HandlerStackFactory 的默认行为(均可用参数覆盖):
- 默认连接池参数(第 25-30 行):
min_connections => 1、max_connections => 30、wait_timeout => 3.0、max_idle_time => 60; - 默认中间件:内置
retry中间件(RetryMiddleware,参数[1, 10],即最多重试 1 次、延迟 10ms); - 智能选型:在协程上下文(
Coroutine::inCoroutine())中,若容器中可用Hyperf\Pool\SimplePool\PoolFactory(即安装了hyperf/pool且容器为 Hyperf DI 容器),则自动使用PoolHandler;否则退化为CoroutineHandler。
重试逻辑位于 RetryMiddleware:isOk()判断响应状态码是否处于 200-299 区间,非 2xx 或未得到响应且未超过retries上限时触发重试,delay参数控制每次重试前的等待毫秒数。
替换第三方组件内的 GuzzleHttp\Client:ClassMap 方案
如果第三方组件没有提供可替换 Handler 的接口,我们还可以使用 Hyperf 注解扫描的ClassMap功能直接替换GuzzleHttp\Client类本身,达到客户端协程化的目的。
当然,也可以使用 SWOOLE_HOOK 实现同样的目的。
编写替换类
class_map/GuzzleHttp/Client.php:
<?php namespace GuzzleHttp; use GuzzleHttp\Psr7; use Hyperf\Guzzle\CoroutineHandler; use Hyperf\Coroutine\Coroutine; class Client implements ClientInterface { // 省略其余未修改的代码 public function __construct(array $config = []) { $inCoroutine = Coroutine::inCoroutine(); if (!isset($config['handler'])) { // 对应 Handler 可按需选择 CoroutineHandler 或 PoolHandler $config['handler'] = HandlerStack::create($inCoroutine ? new CoroutineHandler() : null); } elseif ($inCoroutine && $config['handler'] instanceof HandlerStack) { $config['handler']->setHandler(new CoroutineHandler()); } elseif (!is_callable($config['handler'])) { throw new \InvalidArgumentException('handler must be a callable'); } // Convert the base_uri to a UriInterface if (isset($config['base_uri'])) { $config['base_uri'] = Psr7\uri_for($config['base_uri']); } $this->configureDefaults($config); } }该替换类与组件源码的设计完全一致:在协程上下文内自动注入CoroutineHandler;若调用方已传入HandlerStack,则通过setHandler()将其底层替换为协程 Handler;传入非法 Handler 时抛出异常。
注册 ClassMap
config/autoload/annotations.php:
<?php declare(strict_types=1); use GuzzleHttp\Client; return [ 'scan' => [ // ... 'class_map' => [ Client::class => BASE_PATH . '/class_map/GuzzleHttp/Client.php', ], ], ];配置完成后,Hyperf 注解扫描器会把项目内所有对GuzzleHttp\Client的实例化替换为协程化版本,第三方 SDK(如各种云厂商 PHP SDK)的 HTTP 调用即可自动获得协程能力,无需改动其源码。
补充:RingPHP 兼容 Handler
在src/guzzle/src/RingPHP/目录下还提供了面向 Guzzle Ring(旧版 Guzzle 6 内部传输层接口)的 CoroutineHandler,它同样基于 Swoole 协程客户端实现,将 Ring 请求数组转换为协程调用。该 Handler 主要服务于仍依赖 Ring 接口的历史客户端库,帮助这类组件在 Hyperf 中实现协程化,与主CoroutineHandler是互补关系。
测试与验证
组件测试位于 src/guzzle/tests/Cases/,覆盖了本文所述的全部核心行为:协程超时与错误上下文(CoroutineHandlerTest.php)、swoole 配置覆盖、代理(字符串/数组/直连白名单)、SSL 证书与密钥、Basic 认证、on_stats统计、sink落盘、Expect/Content-Length 头重写、连接池复用(PoolHandlerTest.php)以及 HandlerStackFactoryTest.php。在你动手集成第三方 HTTP 依赖前,这些测试既是组件行为的权威参考,也可作为自定义 Handler 的编写范本。
- 后端
- Web框架
- 微服务
- RPC框架
- 异步编程
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
相关推荐
Hyperf Guzzle协程化:高性能HTTP客户端新体验
Hyperf Guzzle协程化:高性能HTTP客户端新体验 还在为传统HTTP客户端的性能瓶颈而烦恼?还在为高并发场景下的连接数限制而头疼?Hyperf Gu
后端Web框架微服务RPC框架异步编程超高效第三方API集成:Hyperf Guzzle协程客户端实战指南
超高效第三方API集成:Hyperf Guzzle协程客户端实战指南 还在为高并发API调用性能瓶颈发愁?Hyperf的Guzzle协程客户端让你轻松实现万级并
后端微服务终极指南:如何优化Kubernetes Python客户端连接池性能
终极指南:如何优化Kubernetes Python客户端连接池性能 Kubernetes Python客户端连接池是提升Kubernetes API调用性能的
后端云原生容器编排
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考