请问如何监听GuzzleHttp的请求?

1. 运行环境#

1). 当前使用的 Laravel 版本?#

目前使用的是 laravel9.33

2). 当前使用的 php/php-fpm 版本?#

php 使用的是 8.1.19

php-fpm 版本:

3). 当前系统#

4). 业务环境#

5). 相关软件版本#

2. 问题描述?#

我在开发一个日志系统,需要捕获到项目里对外的 http 请求,目前项目中使用的是一个 guzzlehttp 的 fork 版本。

我本地尝试了一下,如果是通过 Illuminate\Support\Facades\Http 对象发起的 get/post 请求,那么可以通过监听 laravel 的 Illuminate\Http\Client\Events\RequestSending 事件来获取我想要的数据。

但是项目中都是通过
Http::baseUrl ($this->baseUriVia ())->timeout ($this->timeoutVia ())->setClient (new Client ()) 获取 client 对象。

然后这个 Client 类,继承了 guzzlehttpclient, class Client extends GuzzleHttpClient,在构造函数里做了一些定制化,然后 parent::__construct ($config)。

我发现通过这种方式发起的 get/post 请求,无法通过监听那个事件得到结果,也就是没有触发那个事件,现在很困扰,不知道该如何才能监听到。

3. 您期望得到的结果?#

4. 您实际得到的结果?#

《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 13

可类似封装一个,在中间件中记录日志,当然这个是指有良好的架构前提先进行的

public function request(string $url, string $method, Closure $callback): ApiResponse
    {
        $this->url = $url;
        $this->method = strtoupper($method);
        // 执行中间件
        $middlewares = $this->getMiddleware();
        $pipeline = app(Pipeline::class);
        $response = $pipeline->send($this)
            ->through($middlewares)
            ->then(function () use ($callback) {
                event(new ApiRequestStart($this));
                // 记录请求开始时间
                $this->start();
                // 请求接口
                $response = $this->fetch($this->getUrl(), $this->getMethod());
                // 记录请求结束时间
                $this->end();
                try {
                    // 处理请求后的接口, 该闭包一般用来格式化接口数据和判断接口返回是否正常
                    $response = $callback($response);
                } catch (Throwable $e) {
                    // 记录错误日志, 不在这抛出异常(因日志中间件会走不下去). 当全部中间件执行完后才由后面抛出
                    $response->setError()->setErrorCode($e->getCode())->setMessage($e->getMessage())->setException($e);
                } finally {
                    // 触发请求完成事件
                    event(new ApiRequestComplete($this, $response));
                }
                return $response;
            });

        // 有异常时直接抛出指定异常
        if ($response->getException()) {
            throw $response->getException();
        }

        // 其他出错时抛出(正常情况是不会走进这里的, 一般会在上面的$callback($response)把异常全部抛出来)
        if ($response->isError()) {
            throw new RequestException($response->getMessage(), $response->getErrorCode());
        }

        return $response;
    }
1年前 评论
Gaussen (楼主) 1年前

我们是在 guzzle 类上包了一层,然后在里面做的日志

1年前 评论
Gaussen (楼主) 1年前
Smilephp (作者) 1年前

GuzzleHttp 的自带的 Middleware 就可以实现:

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface;

$stack = new HandlerStack();
$stack->setHandler(Utils::chooseHandler());

$stack->push(Middleware::mapRequest(function (RequestInterface $r) {
    echo 'A';
    return $r;
}));

$stack->push(Middleware::mapRequest(function (RequestInterface $r) {
    echo 'B';
    return $r;
}));

$stack->push(Middleware::mapRequest(function (RequestInterface $r) {
    echo 'C';
    return $r;
}));

$client->request('GET', 'http://httpbin.org/');
// echoes 'ABC';

$stack->unshift(Middleware::mapRequest(function (RequestInterface $r) {
    echo '0';
    return $r;
}));

$client = new Client(['handler' => $stack]);
$client->request('GET', 'http://httpbin.org/');
// echoes '0ABC';

更多功能参考官方文档:docs.guzzlephp.org/en/stable/handl...

1年前 评论
Gaussen (楼主) 1年前
deatil 1年前
1年前 评论
Gaussen (楼主) 1年前

我明白 OP 希望在各个业务方不改动源码的情况下,监听 RequestSending 事件,那么让我们看看 RequestSending 事件是在哪里发出的,在 laravel 项目中全局搜索 RequestSending 方法,你会找到一个:$pendingRequest->dispatchRequestSendingEvent(); 的调用,这个调用被丢进了 PendingRequest 的 beforeSendingCallbacks 属性里面,作为 request 真正发起前的回调,那我们再找找这个 beforeSendingCallbacks 在哪里被使用了呢?跟踪这个属性,会发现他在一个 beforeSendingCallbacks 的方法里被调用了,一路往上跟踪,你会发现,这个方法最终会在 buildClient 时,作为 client 的 handlerStack 传入,至于 handlerStack 是什么,楼上已经有人给出解释了。事已至此,那么你就应该明白了,在不对 Client 干预的情况下,它压根不会发出事件,那么你无论如何努力也做不到了。

那么,在这种情况下,到底有没有办法能做到这个事情呢?其实也不是完全没有办法,PendingReuqest 这个类,在 laravel 的 Http Facade 里面是写死的,所以你没办法配置它,但是你可以 patch 它,你写一个 composer 包,把 PendingRequest 类的内容拷贝一份出来,修改他的 buildClient 的方法,在它返回你们自己的 client 时,应用上对应的 handlerStack (当然这里面有很多细节要处理),然后在 vendor publish 的时候,覆盖掉 vendor/laravel/framework/src/Illuminate/Http/Client/PendingRequest.php 即可。

以上内容仅是我个人摸鱼时间查看源码得出的理论方案,并未实践验证,具体是否可行可以写代码自行验证。

1年前 评论

GuzzleHttp 可以添加自带的日志记录中间件 Middleware::log ()

1年前 评论

我也同意上面提到的中间价。正常情况下你们的 client 应该复用。你们的 demo 貌似多次 new client ,所以出现这个问题。

1年前 评论