DEV Community

Roman
Roman

Posted on

8 1

How to create robust HTTP clients with Guzzle

I never was a big fan of Guzzle until recently when I finally learned about Guzzle middleware and some basic config.

Here is how you can step up your Guzzle game and build robust HTTP clients.

First of all, start from setting timeouts. Timeouts are not limited by default, and this can cause headaches when debugging issues it can cause.

I remember spending 2 days trying to understand why some jobs randomly time-outed. It wasn't fun when I noticed some requests stuck!

use GuzzleHttp\Client;

$client = new Client([
    'timeout' => 15,
    'connect_timeout' => 15,
]);
Enter fullscreen mode Exit fullscreen mode

So Guzzle's middleware stack. Guzzle passes any request through the middleware stack. It allows you to add your middleware and do something for every request (or every response): add the API key to headers, handle errors, and even retry failed requests.

To start you need to create HandlerStack. The simplest way to do it is to call the static method create on HandlerStack class. You will add middleware to the stack.

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;

$stack = HandlerStack::create();

// ...

$client = new Client([
    'base_uri' => 'https://api.some.com',
    'timeout' => 15,
    'connect_timeout' => 15,
    'handler' => $stack,
]);
Enter fullscreen mode Exit fullscreen mode

Guzzle's middleware on its own is pretty tricky: a function that returns a function. But thankfully, they've added sugar for it, so it looks readable.

Let's start from Middleware::mapRequest middleware. It allows you to modify a request. I use this middleware to add the API key to every request, either in headers or in the query string. It helps to reduce the amount of boilerplate code I write when making requests.

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

$stack->push(function (RequestInterface $request) {
    $token = config('services.some.api_token');

    return $request->withHeader('Authorization', "Basic {$token}");
});
Enter fullscreen mode Exit fullscreen mode

Middleware::mapResponse allows you to do something with the response. I use it to do basic error handling. This way, I can avoid excessive try/catch usage or wrapping my entire client in a separate method.

use GuzzleHttp\Middleware;
use Psr\Http\Message\ResponseInterface;

$stack->push(Middleware::mapResponse(function (ResponseInterface $response) {
    if (in_array($response->getStatusCode(), [401, 403])) {
        // Some logic to hanle the error. For example, save it into DB.
    }
    return $response;
}));
Enter fullscreen mode Exit fullscreen mode

And my favorite Middleware::retry. It allows you to specify when and how to retry failed requests. For example, when you get 50x errors, connection timeouts, etc you would probably want to try again before giving up.

use Exception;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\ConnectException;

$retryDecider = function (
    $retries, 
    RequestInterface $request, 
    ResponseInterface $response = null, 
    Exception $exception = null
) {
    if ($retries >= 5) {
        return false;
    }
    if ($exception instanceof ConnectException) {
        return true;
    }
    if ($response && $response->getStatusCode() >= 500) {
        return true;
    }
    return false;
};

$retryDelay = function ($numberOfRetries) {
    return 1000 * $numberOfRetries;
};

$stack->push(Middleware::retry($retryDecider, $retryDelay));
Enter fullscreen mode Exit fullscreen mode

Retry middleware also allows you to set the delay between requests so you can configure something like exponential back-off pretty easily. On the screenshot in the previous tweet delay between requests will be a bit bigger between every failed request.

At this point, you have a robust Guzzle HTTP client that is enjoyable to use. It will automatically retry failed requests, add authorization to the request, or do whatever you need!

Heroku

Built for developers, by developers.

Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly — using the tools and languages you already love!

Learn More

Top comments (0)

Jetbrains image

Build Secure, Ship Fast

Discover best practices to secure CI/CD without slowing down your pipeline.

Read more

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay