Remi
Remi

Reputation: 23

PHP - Guzzle Middleware

I'm using the Pole Emploi's API,but I encounter 401 error 25 minutes later, when my token expires.

I looked for a way to get a new token and retry the request, but no way for me to understand how Middlewares work, and if I should use a middleware for my needings.

On Guzzle's docs this is written :

Middleware functions return a function that accepts the next handler to invoke. This returned function then returns another function that acts as a composed handler-- it accepts a request and options, and returns a promise that is fulfilled with a response. Your composed middleware can modify the request, add custom request options, and modify the promise returned by the downstream handler.

And this is an example code from the docs :

use Psr\Http\Message\RequestInterface;

function my_middleware()
{
    return function (callable $handler) {
        return function (RequestInterface $request, array $options) use ($handler) {
            return $handler($request, $options);
        };
    };
}

So I think I need to manage the "promise" to see if its HTTP code is 401, and then get a new token and retry the request ?

I'm lost, so I would appreciate if someone can explain me the logic of this with different words maybe :)

Thank you in advance.

Upvotes: 0

Views: 6382

Answers (2)

Einar-Johan
Einar-Johan

Reputation: 61

It doesn't need to be that difficult, add a handler that takes care of the job, in combination with cache that expires.

If you don't use cache then I guess you could probably save it to a file along with a timestamp for expiration that you check against when fetching it.

class AuthenticationHandler
{
    private $username;
    private $password;
    private $token_name = 'access_token';

    public function __construct($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
    }

    public function __invoke(callable $handler)
    {
        return function (RequestInterface $request, array $options) use ($handler) {
            if (is_null($token = Cache::get($this->token_name))) {
                $response = $this->getJWT();
                Cache::put($this->token_name, $token = $response->access_token, floor($response->expires_in));
            }

            return $handler(
                $request->withAddedHeader('Authorization', 'Bearer '.$token)
                    ->withAddedHeader('Api-Key', $this->api_key), $options
                );
        };
    }

    private function getJWT()
    {
        $response = (new Client)->request('POST', 'new/token/url', [
            'form_params' => [
                'grant_type' => 'client_credentials',
                'username' => $this->username,
                'password' => $this->password,
            ],
        ]);
        return json_decode($response->getBody());
    }
}

Then use it:

$stack = HandlerStack::create(new CurlHandler());
$stack->push(new AuthenticationHandler('username', 'password'));
$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.com',
    'handler' => $stack,
]);

Now you will always have a valid token, and you will never have to worry about it ever again.

Upvotes: 6

ncla
ncla

Reputation: 811

I wouldn't recommend doing this as it can become hell to debug your application and as far as I am aware Guzzle doesn't really allow access to the client from middleware. Regardless you can use Promises to get around. If I were you I would refresh token before other requests, or refresh periodically. It might be fine if you are firing requests one by one, but in a Pool it will become a nightmare because you can end up having script fetch token too often and then some request ends up with out-dated token.

Anyway here is a rough example:

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;

function my_middleware()
{
    return function (callable $handler) {
        return function (RequestInterface $request, array $options) use ($handler) {
            /**
             * @var $promise \GuzzleHttp\Promise\Promise
             */
            $promise = $handler($request, $options);

            return $promise->then(
                function (ResponseInterface $response) use ($request, $options) {
                    if ($response->getStatusCode() === 404) {
                        var_dump($response->getStatusCode());
                        var_dump(strlen($response->getBody()));

                        // Pretend we are getting new token key here
                        $client = new Client();
                        $key = $client->get('https://www.iana.org/domains/reserved');

                        // Then we modify the failed request. For your case you use ->withHeader() to change the
                        // Authorization header with your token.
                        $uri = $request->getUri();
                        $uri = $uri->withHost('google.com')->withPath('/');

                        // New instance of Request
                        $request = $request->withUri($uri);

                        // Send the request again with our new header/URL/whatever
                        return $client->sendAsync($request, $options);
                    }

                    return $response;
                }
            );
        };
    };
}

$handlerStack = HandlerStack::create();
$handlerStack->push(my_middleware());

$client = new Client([
    'base_uri' => 'https://example.org',
    'http_errors' => false,
    'handler' => $handlerStack
]);

$options = [];

$response = $client->request('GET', '/test', $options);

var_dump($response->getStatusCode());
var_dump(strlen($response->getBody()));
echo $response->getBody();

Upvotes: 2

Related Questions