From 571066a606e746de095afab295903e7018e56b6e Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:41:46 +0800 Subject: [PATCH 1/6] Refactor GuzzleHttpClientAspect --- .../src/Aspect/GuzzleHttpClientAspect.php | 155 ++++++++++++++++++ src/telescope/src/Aspect/HttpClientAspect.php | 136 +-------------- src/telescope/src/ConfigProvider.php | 8 +- 3 files changed, 161 insertions(+), 138 deletions(-) create mode 100644 src/telescope/src/Aspect/GuzzleHttpClientAspect.php diff --git a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php new file mode 100644 index 000000000..cf8d6dc8d --- /dev/null +++ b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php @@ -0,0 +1,155 @@ +telescopeConfig->isEnable('guzzle') + || ! TelescopeContext::getBatchId() + ) { + return $proceedingJoinPoint->process(); + } + + $instance = $proceedingJoinPoint->getInstance(); + $arguments = $proceedingJoinPoint->arguments; + $options = $arguments['keys']['options'] ?? []; + $guzzleConfig = (fn () => $this->config ?? [])->call($instance); + + if ( + ($options['no_telescope_aspect'] ?? null) === true + || ($guzzleConfig['no_telescope_aspect'] ?? null) === true + ) { + return $proceedingJoinPoint->process(); + } + + // Add or override the on_stats option to record the request duration. + $onStats = $options['on_stats'] ?? null; + $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($onStats) { + $request = $stats->getRequest(); + $result = $stats->getResponse(); + $method = $request->getMethod(); + $duration = $stats->getTransferTime(); + $uri = $request->getUri()->__toString(); + $headers = $request->getHeaders(); + $response = [ + 'status' => $result->getStatusCode(), + 'reason' => $result->getReasonPhrase(), + 'headers' => $result->getHeaders(), + 'body' => $this->getResponsePayload($result), + ]; + $result->getBody()->rewind(); + + Telescope::recordClientRequest(IncomingEntry::make([ + 'method' => $method, + 'uri' => $uri, + 'headers' => $headers, + 'response_status' => $response['status'], + 'response_headers' => $response['headers'], + 'response' => $response, + 'duration' => $duration * 1000, + ])); + + if (is_callable($onStats)) { + $onStats($stats); + } + }; + + return $proceedingJoinPoint->process(); + } + + public function getResponsePayload(ResponseInterface $response) + { + $stream = $response->getBody(); + try { + if ($stream->isSeekable()) { + $stream->rewind(); + } + + $content = $stream->getContents(); + } catch (Throwable $e) { + return 'Purged By Hyperf Telescope: ' . $e->getMessage(); + } + + if (is_string($content)) { + if (! $this->contentWithinLimits($content)) { + return 'Purged By Hyperf Telescope'; + } + if ( + is_array(json_decode($content, true)) + && json_last_error() === JSON_ERROR_NONE + ) { + return $this->contentWithinLimits($content) /* @phpstan-ignore-line */ + ? $this->hideParameters(json_decode($content, true), Telescope::$hiddenResponseParameters) + : 'Purged By Hyperf Telescope'; + } + if (Str::startsWith(strtolower($response->getHeaderLine('content-type') ?: ''), 'text/plain')) { + return $this->contentWithinLimits($content) ? $content : 'Purged By Hyperf Telescope'; /* @phpstan-ignore-line */ + } + if (Str::contains($response->getHeaderLine('content-type'), 'application/grpc') !== false) { + return TelescopeContext::getGrpcResponsePayload() ?: 'Purged By Hyperf Telescope'; + } + } + + if (empty($content)) { + return 'Empty Response'; + } + + return 'HTML Response'; + } + + protected function contentWithinLimits(string $content): bool + { + $limit = 64; + return mb_strlen($content) / 1000 <= $limit; + } + + /** + * Hide the given parameters. + */ + protected function hideParameters(array $data, array $hidden): array + { + foreach ($hidden as $parameter) { + if (Arr::get($data, $parameter)) { + Arr::set($data, $parameter, '********'); + } + } + + return $data; + } +} diff --git a/src/telescope/src/Aspect/HttpClientAspect.php b/src/telescope/src/Aspect/HttpClientAspect.php index 84605a977..356577a76 100644 --- a/src/telescope/src/Aspect/HttpClientAspect.php +++ b/src/telescope/src/Aspect/HttpClientAspect.php @@ -11,141 +11,9 @@ namespace FriendsOfHyperf\Telescope\Aspect; -use FriendsOfHyperf\Telescope\IncomingEntry; -use FriendsOfHyperf\Telescope\Telescope; -use FriendsOfHyperf\Telescope\TelescopeConfig; -use FriendsOfHyperf\Telescope\TelescopeContext; -use GuzzleHttp\Client; -use Hyperf\Collection\Arr; -use Hyperf\Di\Aop\AbstractAspect; -use Hyperf\Di\Aop\ProceedingJoinPoint; -use Hyperf\Stringable\Str; -use Psr\Http\Message\ResponseInterface; -use Throwable; - /** - * @property array $config + * @deprecated since v3.1, use `GuzzleHttpClientAspect` instead, will be removed in v3.2. */ -class HttpClientAspect extends AbstractAspect +class HttpClientAspect extends GuzzleHttpClientAspect { - public array $classes = [ - Client::class . '::request', - Client::class . '::requestAsync', - ]; - - public function __construct(protected TelescopeConfig $telescopeConfig) - { - } - - public function process(ProceedingJoinPoint $proceedingJoinPoint) - { - if ( - ! $this->telescopeConfig->isEnable('guzzle') - || ! TelescopeContext::getBatchId() - ) { - return $proceedingJoinPoint->process(); - } - - $startTime = microtime(true); - $instance = $proceedingJoinPoint->getInstance(); - $arguments = $proceedingJoinPoint->arguments; - $options = $arguments['keys']['options'] ?? []; - $guzzleConfig = (fn () => $this->config ?? [])->call($instance); - - if (($options['no_telescope_aspect'] ?? null) === true || ($guzzleConfig['no_telescope_aspect'] ?? null) === true) { - return $proceedingJoinPoint->process(); - } - - // Disable the aspect for the requestAsync method. - if ($proceedingJoinPoint->methodName == 'request') { - $proceedingJoinPoint->arguments['keys']['options']['no_telescope_aspect'] = true; - } - - $arguments = $proceedingJoinPoint->arguments; - $method = $arguments['keys']['method'] ?? 'GET'; - $uri = $arguments['keys']['uri'] ?? ''; - $headers = $options['headers'] ?? []; - $result = $proceedingJoinPoint->process(); - $response = []; - - if ($result instanceof ResponseInterface) { - $response['status'] = $result->getStatusCode(); - $response['reason'] = $result->getReasonPhrase(); - $response['headers'] = $result->getHeaders(); - $response['body'] = $this->getResponsePayload($result); - $result->getBody()->rewind(); - } - - Telescope::recordClientRequest(IncomingEntry::make([ - 'method' => $method, - 'uri' => $uri, - 'headers' => $headers, - 'response_status' => $response['status'] ?? 0, - 'response_headers' => $response['headers'] ?? '', - 'response' => $response, - 'duration' => floor((microtime(true) - $startTime) * 1000), - ])); - - return $result; - } - - public function getResponsePayload(ResponseInterface $response) - { - $stream = $response->getBody(); - try { - if ($stream->isSeekable()) { - $stream->rewind(); - } - - $content = $stream->getContents(); - } catch (Throwable $e) { - return 'Purged By Hyperf Telescope: ' . $e->getMessage(); - } - - if (is_string($content)) { - if (! $this->contentWithinLimits($content)) { - return 'Purged By Hyperf Telescope'; - } - if ( - is_array(json_decode($content, true)) - && json_last_error() === JSON_ERROR_NONE - ) { - return $this->contentWithinLimits($content) /* @phpstan-ignore-line */ - ? $this->hideParameters(json_decode($content, true), Telescope::$hiddenResponseParameters) - : 'Purged By Hyperf Telescope'; - } - if (Str::startsWith(strtolower($response->getHeaderLine('content-type') ?: ''), 'text/plain')) { - return $this->contentWithinLimits($content) ? $content : 'Purged By Hyperf Telescope'; /* @phpstan-ignore-line */ - } - if (Str::contains($response->getHeaderLine('content-type'), 'application/grpc') !== false) { - return TelescopeContext::getGrpcResponsePayload() ?: 'Purged By Hyperf Telescope'; - } - } - - if (empty($content)) { - return 'Empty Response'; - } - - return 'HTML Response'; - } - - protected function contentWithinLimits(string $content): bool - { - $limit = 64; - return mb_strlen($content) / 1000 <= $limit; - } - - /** - * Hide the given parameters. - */ - protected function hideParameters(array $data, array $hidden): array - { - foreach ($hidden as $parameter) { - if (Arr::get($data, $parameter)) { - Arr::set($data, $parameter, '********'); - } - } - - return $data; - } } diff --git a/src/telescope/src/ConfigProvider.php b/src/telescope/src/ConfigProvider.php index 50b9f98ee..8d908b7c5 100644 --- a/src/telescope/src/ConfigProvider.php +++ b/src/telescope/src/ConfigProvider.php @@ -19,16 +19,16 @@ public function __invoke(): array return [ 'aspects' => [ - Aspect\CoroutineAspect::class, Aspect\CacheAspect::class, + Aspect\CoroutineAspect::class, Aspect\EventAspect::class, Aspect\GrpcClientAspect::class, - Aspect\HttpClientAspect::class, + Aspect\GrpcCoreMiddlewareAspect::class, + Aspect\GuzzleHttpClientAspect::class, Aspect\LogAspect::class, Aspect\RedisAspect::class, - Aspect\RpcAspect::class, Aspect\RequestDispatcherAspect::class, - Aspect\GrpcCoreMiddlewareAspect::class, + Aspect\RpcAspect::class, ], 'commands' => [ Command\ClearCommand::class, From 561df7adb1843c1a1e8cb325e6932f243a643eaf Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:46:49 +0800 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20GuzzleHttpClien?= =?UTF-8?q?tAspect=20=E4=B8=AD=E7=9A=84=E5=93=8D=E5=BA=94=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Aspect/GuzzleHttpClientAspect.php | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php index cf8d6dc8d..58194064e 100644 --- a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php +++ b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php @@ -62,26 +62,25 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $onStats = $options['on_stats'] ?? null; $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($onStats) { $request = $stats->getRequest(); - $result = $stats->getResponse(); + $response = $stats->getResponse(); $method = $request->getMethod(); $duration = $stats->getTransferTime(); $uri = $request->getUri()->__toString(); $headers = $request->getHeaders(); - $response = [ - 'status' => $result->getStatusCode(), - 'reason' => $result->getReasonPhrase(), - 'headers' => $result->getHeaders(), - 'body' => $this->getResponsePayload($result), + $data = [ + 'status' => $response->getStatusCode(), + 'reason' => $response->getReasonPhrase(), + 'headers' => $response->getHeaders(), + 'body' => $this->getResponsePayload($response), ]; - $result->getBody()->rewind(); Telescope::recordClientRequest(IncomingEntry::make([ 'method' => $method, 'uri' => $uri, 'headers' => $headers, - 'response_status' => $response['status'], - 'response_headers' => $response['headers'], - 'response' => $response, + 'response_status' => $data['status'], + 'response_headers' => $data['headers'], + 'response_data' => $data, 'duration' => $duration * 1000, ])); @@ -104,6 +103,10 @@ public function getResponsePayload(ResponseInterface $response) $content = $stream->getContents(); } catch (Throwable $e) { return 'Purged By Hyperf Telescope: ' . $e->getMessage(); + } finally { + if ($stream->isSeekable()) { + $stream->rewind(); + } } if (is_string($content)) { From 4df511061882757531db8b46e8cd2137cf7a4207 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:47:18 +0800 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=20GuzzleHttpClien?= =?UTF-8?q?tAspect=20=E4=B8=AD=E7=9A=84=E5=93=8D=E5=BA=94=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Aspect/GuzzleHttpClientAspect.php | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php index 58194064e..e3413e229 100644 --- a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php +++ b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php @@ -101,6 +101,30 @@ public function getResponsePayload(ResponseInterface $response) } $content = $stream->getContents(); + + if (is_string($content)) { + if (! $this->contentWithinLimits($content)) { + return 'Purged By Hyperf Telescope'; + } + if ( + is_array(json_decode($content, true)) + && json_last_error() === JSON_ERROR_NONE + ) { + return $this->contentWithinLimits($content) /* @phpstan-ignore-line */ + ? $this->hideParameters(json_decode($content, true), Telescope::$hiddenResponseParameters) + : 'Purged By Hyperf Telescope'; + } + if (Str::startsWith(strtolower($response->getHeaderLine('content-type') ?: ''), 'text/plain')) { + return $this->contentWithinLimits($content) ? $content : 'Purged By Hyperf Telescope'; /* @phpstan-ignore-line */ + } + if (Str::contains($response->getHeaderLine('content-type'), 'application/grpc') !== false) { + return TelescopeContext::getGrpcResponsePayload() ?: 'Purged By Hyperf Telescope'; + } + } + + if (empty($content)) { + return 'Empty Response'; + } } catch (Throwable $e) { return 'Purged By Hyperf Telescope: ' . $e->getMessage(); } finally { @@ -109,30 +133,6 @@ public function getResponsePayload(ResponseInterface $response) } } - if (is_string($content)) { - if (! $this->contentWithinLimits($content)) { - return 'Purged By Hyperf Telescope'; - } - if ( - is_array(json_decode($content, true)) - && json_last_error() === JSON_ERROR_NONE - ) { - return $this->contentWithinLimits($content) /* @phpstan-ignore-line */ - ? $this->hideParameters(json_decode($content, true), Telescope::$hiddenResponseParameters) - : 'Purged By Hyperf Telescope'; - } - if (Str::startsWith(strtolower($response->getHeaderLine('content-type') ?: ''), 'text/plain')) { - return $this->contentWithinLimits($content) ? $content : 'Purged By Hyperf Telescope'; /* @phpstan-ignore-line */ - } - if (Str::contains($response->getHeaderLine('content-type'), 'application/grpc') !== false) { - return TelescopeContext::getGrpcResponsePayload() ?: 'Purged By Hyperf Telescope'; - } - } - - if (empty($content)) { - return 'Empty Response'; - } - return 'HTML Response'; } From 32fc8412a6694bb5da5ef3b37ad2941fbdcd7061 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:51:38 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=20GuzzleHttpClien?= =?UTF-8?q?tAspect=20=E4=B8=AD=E7=9A=84=E8=AF=B7=E6=B1=82=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Aspect/GuzzleHttpClientAspect.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php index e3413e229..2240ed99a 100644 --- a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php +++ b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php @@ -39,6 +39,7 @@ public function __construct(protected TelescopeConfig $telescopeConfig) public function process(ProceedingJoinPoint $proceedingJoinPoint) { + // If the guzzle aspect is disabled or the batch id is not set, we will not record the request. if ( ! $this->telescopeConfig->isEnable('guzzle') || ! TelescopeContext::getBatchId() @@ -47,10 +48,10 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } $instance = $proceedingJoinPoint->getInstance(); - $arguments = $proceedingJoinPoint->arguments; - $options = $arguments['keys']['options'] ?? []; + $options = $proceedingJoinPoint->arguments['keys']['options'] ?? []; $guzzleConfig = (fn () => $this->config ?? [])->call($instance); + // If the no_telescope_aspect option is set to true, we will not record the request. if ( ($options['no_telescope_aspect'] ?? null) === true || ($guzzleConfig['no_telescope_aspect'] ?? null) === true @@ -63,10 +64,6 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($onStats) { $request = $stats->getRequest(); $response = $stats->getResponse(); - $method = $request->getMethod(); - $duration = $stats->getTransferTime(); - $uri = $request->getUri()->__toString(); - $headers = $request->getHeaders(); $data = [ 'status' => $response->getStatusCode(), 'reason' => $response->getReasonPhrase(), @@ -75,13 +72,13 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) ]; Telescope::recordClientRequest(IncomingEntry::make([ - 'method' => $method, - 'uri' => $uri, - 'headers' => $headers, + 'method' => $request->getMethod(), + 'uri' => $request->getUri()->__toString(), + 'headers' => $request->getHeaders(), 'response_status' => $data['status'], 'response_headers' => $data['headers'], 'response_data' => $data, - 'duration' => $duration * 1000, + 'duration' => $stats->getTransferTime() * 1000, ])); if (is_callable($onStats)) { From 4ced0c75daf606801f7a9fbdb2b55f1afd04b382 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:52:20 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20GuzzleHttpClien?= =?UTF-8?q?tAspect=20=E4=B8=AD=E7=9A=84=E5=AE=9E=E4=BE=8B=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/telescope/src/Aspect/GuzzleHttpClientAspect.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php index 2240ed99a..f19506368 100644 --- a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php +++ b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php @@ -47,9 +47,8 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) return $proceedingJoinPoint->process(); } - $instance = $proceedingJoinPoint->getInstance(); $options = $proceedingJoinPoint->arguments['keys']['options'] ?? []; - $guzzleConfig = (fn () => $this->config ?? [])->call($instance); + $guzzleConfig = (fn () => $this->config ?? [])->call($proceedingJoinPoint->getInstance()); // If the no_telescope_aspect option is set to true, we will not record the request. if ( From 388180c74882d631c54954196acd2b20d9efa968 Mon Sep 17 00:00:00 2001 From: Deeka Wong <8337659+huangdijia@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:37:39 +0800 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=E6=8D=95=E8=8E=B7=20GuzzleHttpClien?= =?UTF-8?q?tAspect=20=E4=B8=AD=E7=9A=84=E5=BC=82=E5=B8=B8=E4=BB=A5?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E8=AF=B7=E6=B1=82=E4=B8=AD=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Aspect/GuzzleHttpClientAspect.php | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php index f19506368..fcf7a5908 100644 --- a/src/telescope/src/Aspect/GuzzleHttpClientAspect.php +++ b/src/telescope/src/Aspect/GuzzleHttpClientAspect.php @@ -61,24 +61,28 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) // Add or override the on_stats option to record the request duration. $onStats = $options['on_stats'] ?? null; $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($onStats) { - $request = $stats->getRequest(); - $response = $stats->getResponse(); - $data = [ - 'status' => $response->getStatusCode(), - 'reason' => $response->getReasonPhrase(), - 'headers' => $response->getHeaders(), - 'body' => $this->getResponsePayload($response), - ]; - - Telescope::recordClientRequest(IncomingEntry::make([ - 'method' => $request->getMethod(), - 'uri' => $request->getUri()->__toString(), - 'headers' => $request->getHeaders(), - 'response_status' => $data['status'], - 'response_headers' => $data['headers'], - 'response_data' => $data, - 'duration' => $stats->getTransferTime() * 1000, - ])); + try { + $request = $stats->getRequest(); + $response = $stats->getResponse(); + $data = [ + 'status' => $response->getStatusCode(), + 'reason' => $response->getReasonPhrase(), + 'headers' => $response->getHeaders(), + 'body' => $this->getResponsePayload($response), + ]; + + Telescope::recordClientRequest(IncomingEntry::make([ + 'method' => $request->getMethod(), + 'uri' => $request->getUri()->__toString(), + 'headers' => $request->getHeaders(), + 'response_status' => $data['status'], + 'response_headers' => $data['headers'], + 'response_data' => $data, + 'duration' => $stats->getTransferTime() * 1000, + ])); + } catch (Throwable $e) { + // We will catch the exception to prevent the request from being interrupted. + } if (is_callable($onStats)) { $onStats($stats);