请问如何监听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. 您实际得到的结果?

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《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;
    }
4周前 评论
Gaussen (楼主) 3周前

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

4周前 评论
Gaussen (楼主) 3周前
Smilephp (作者) 3周前

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...

4周前 评论
Gaussen (楼主) 3周前
deatil 3周前
3周前 评论
Gaussen (楼主) 3周前

我明白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即可。

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

3周前 评论

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

3周前 评论

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

2周前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!