Guzzle HTTP 客户端

hyperf/guzzle 组件基于 Guzzle 进行协程处理,通过 Swoole HTTP 客户端作为协程驱动替换到 Guzzle 内,以达到 HTTP 客户端的协程化。

安装

  1. composer require hyperf/guzzle

使用

只需要该组件内的 Hyperf\Guzzle\CoroutineHandler 作为处理器设置到 Guzzle 客户端内即可转为协程化运行,为了方便创建协程的 Guzzle 对象,我们提供了一个工厂类 Hyperf\Guzzle\ClientFactory 来便捷的创建客户端,代码示例如下:

  1. <?php
  2. use Hyperf\Guzzle\ClientFactory;
  3. class Foo
  4. {
  5. private ClientFactory $clientFactory;
  6. public function __construct(ClientFactory $clientFactory)
  7. {
  8. $this->clientFactory = $clientFactory;
  9. }
  10. public function bar()
  11. {
  12. // $options 等同于 GuzzleHttp\Client 构造函数的 $config 参数
  13. $options = [];
  14. // $client 为协程化的 GuzzleHttp\Client 对象
  15. $client = $this->clientFactory->create($options);
  16. }
  17. }

使用 ^7.0 版本

组件对 Guzzle 的依赖已经由 ^6.3 改为 ^6.3 | ^7.0,默认情况下已经可以安装 ^7.0 版本,但以下组件会与 ^7.0 冲突。

  • hyperf/metric

可以主动执行以下操作,解决冲突

  1. composer require "promphp/prometheus_client_php:2.2.1"
  • overtrue/flysystem-cos

因为依赖库依赖了 guzzlehttp/guzzle-services,而其不支持 ^7.0,故暂时无法解决。

使用 Swoole 配置

有时候我们想直接修改 Swoole 配置,所以我们也提供了相关配置项,不过这项配置在 Curl Guzzle 客户端 中是无法生效的,所以谨慎使用。

这项配置会替换原来的配置,比如以下 timeout 会被 10 替换。

  1. <?php
  2. use GuzzleHttp\Client;
  3. use Hyperf\Guzzle\CoroutineHandler;
  4. use GuzzleHttp\HandlerStack;
  5. $client = new Client([
  6. 'base_uri' => 'http://127.0.0.1:8080',
  7. 'handler' => HandlerStack::create(new CoroutineHandler()),
  8. 'timeout' => 5,
  9. 'swoole' => [
  10. 'timeout' => 10,
  11. 'socket_buffer_size' => 1024 * 1024 * 2,
  12. ],
  13. ]);
  14. $response = $client->get('/');

连接池

Hyperf 除了实现了 Hyperf\Guzzle\CoroutineHandler 外,还基于 Hyperf\Pool\SimplePool 实现了 Hyperf\Guzzle\PoolHandler

原因

简单来说,主机 TCP 连接数 是有上限的,当我们并发大到超过这个上限值时,就导致请求无法正常建立。另外,TCP 连接结束后还会有一个 TIME-WAIT 阶段,所以也无法实时释放连接。这就导致了实际并发可能远低于 TCP 上限值。所以,我们需要一个连接池来维持这个阶段,尽量减少 TIME-WAIT 造成的影响,让 TCP 连接进行复用。

使用

  1. <?php
  2. use GuzzleHttp\Client;
  3. use Hyperf\Utils\Coroutine;
  4. use GuzzleHttp\HandlerStack;
  5. use Hyperf\Guzzle\PoolHandler;
  6. use Hyperf\Guzzle\RetryMiddleware;
  7. $handler = null;
  8. if (Coroutine::inCoroutine()) {
  9. $handler = make(PoolHandler::class, [
  10. 'option' => [
  11. 'max_connections' => 50,
  12. ],
  13. ]);
  14. }
  15. // 默认的重试Middleware
  16. $retry = make(RetryMiddleware::class, [
  17. 'retries' => 1,
  18. 'delay' => 10,
  19. ]);
  20. $stack = HandlerStack::create($handler);
  21. $stack->push($retry->getMiddleware(), 'retry');
  22. $client = make(Client::class, [
  23. 'config' => [
  24. 'handler' => $stack,
  25. ],
  26. ]);

另外,框架还提供了 HandlerStackFactory 来方便创建上述的 $stack

  1. <?php
  2. use Hyperf\Guzzle\HandlerStackFactory;
  3. use GuzzleHttp\Client;
  4. $factory = new HandlerStackFactory();
  5. $stack = $factory->create();
  6. $client = make(Client::class, [
  7. 'config' => [
  8. 'handler' => $stack,
  9. ],
  10. ]);

使用 ClassMap 替换 GuzzleHttp\Client

如果第三方组件并没有提供可以替换 Handler 的接口,我们也可以通过 ClassMap 功能,直接替换 Client 来达到将客户端协程化的目的。

当然,也可以使用 SWOOLE_HOOK 达到相同的目的。

代码示例如下:

class_map/GuzzleHttp/Client.php

  1. <?php
  2. namespace GuzzleHttp;
  3. use GuzzleHttp\Psr7;
  4. use Hyperf\Guzzle\CoroutineHandler;
  5. use Hyperf\Utils\Coroutine;
  6. class Client implements ClientInterface
  7. {
  8. // 代码省略其他不变的代码
  9. public function __construct(array $config = [])
  10. {
  11. $inCoroutine = Coroutine::inCoroutine();
  12. if (!isset($config['handler'])) {
  13. // 对应的 Handler 可以按需选择 CoroutineHandler 或 PoolHandler
  14. $config['handler'] = HandlerStack::create($inCoroutine ? new CoroutineHandler() : null);
  15. } elseif ($inCoroutine && $config['handler'] instanceof HandlerStack) {
  16. $config['handler']->setHandler(new CoroutineHandler());
  17. } elseif (!is_callable($config['handler'])) {
  18. throw new \InvalidArgumentException('handler must be a callable');
  19. }
  20. // Convert the base_uri to a UriInterface
  21. if (isset($config['base_uri'])) {
  22. $config['base_uri'] = Psr7\uri_for($config['base_uri']);
  23. }
  24. $this->configureDefaults($config);
  25. }
  26. }

config/autoload/annotations.php

  1. <?php
  2. declare(strict_types=1);
  3. use GuzzleHttp\Client;
  4. return [
  5. 'scan' => [
  6. // ...
  7. 'class_map' => [
  8. Client::class => BASE_PATH . '/class_map/GuzzleHttp/Client.php',
  9. ],
  10. ],
  11. ];