gmponos / guzzle-log-middleware

A Guzzle middleware to log request and responses automatically
MIT License
73 stars 24 forks source link

Can't access the response more than once? #37

Closed zack6849 closed 3 years ago

zack6849 commented 3 years ago

Hi There!

I'm using this in a laravel project, and am trying to make a handler that will log requests to the database, this is my current implementation, it doesn't save them to the db yet, but there's an issue:

If i access the responses body inside a handler, the actual client object requesting can't get the body.

https://gist.github.com/zack6849/c13509f85193203579f9c729ee2f3967

Output from the debugger on the above code:

screenshot

zack6849 commented 3 years ago

Interesting caveat, if I change the bit in the routes.php to use the same stringify method from guzzle it works, but if i change them both to $object->getBody()->getContents() they're still broken, eg:

screenshot

zack6849 commented 3 years ago

Sorry, I closed this since it wasn't really reproducible, i've made a repo with reproduciblity and the code

https://github.com/zack6849/guzzle-log

Right now, I can get the response from the client, but I can't get the request body.

Even more troubling, I CAN get the response body from the handler, but if I do that, it's not returned to the client

Relevant files: app/DatabaseHandler.php app/Providers/AppServiceProvider.php routes/web.php

zack6849 commented 3 years ago

Looks like this is stream shenanigans, the below works.

  public function log(
        LoggerInterface $logger,
        RequestInterface $request,
        ?ResponseInterface $response = null,
        ?Throwable $exception = null,
        ?TransferStats $stats = null,
        array $options = []
    ): void {
        //rewind, since guzzle read the request already, and doesn't rewind it.
        $request->getBody()->rewind();
        $req = ThirdPartyApiRequest::create([
            'method' => $request->getMethod(),
            'url' => $request->getUri(),
            'body' => $request->getBody()->getContents(),
            'headers' => json_encode($this->formatHeaders($request->getHeaders())),
        ]);
        $request->getBody()->rewind();
        if($response !== null){
            $response->getBody()->rewind();
            ThirdPartyApiResponse::create([
                'third_party_api_request_id' => $req->id,
                'status' => $response->getStatusCode(),
                'body' => $response->getBody()->getContents(),
                'headers' => json_encode($this->formatHeaders($response->getHeaders())),
            ]);
            //rewind for any future middlewares
            $response->getBody()->rewind();
        }
    }

Relevant stackoverflow answer: https://stackoverflow.com/questions/34910370/how-do-i-get-the-body-of-sent-data-with-guzzle-php/52009669#52009669

It hadn't occurred to me that I'd need to rewind streams my code hadn't read yet, but apparently I do.