Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow customizing the GuzzleSender handler stack #458

Open
epixian opened this issue Sep 19, 2024 · 0 comments
Open

Allow customizing the GuzzleSender handler stack #458

epixian opened this issue Sep 19, 2024 · 0 comments

Comments

@epixian
Copy link

epixian commented Sep 19, 2024

It would be nice to more easily customize the handler stack used by GuzzleSender. V3 only gives us the option to addMiddleware but not to splice or remove them.

One of the major headaches of dealing with Guzzle is that the httpErrors middleware is added to the stack as-is. That is, with a 120-char default length for any HTTP errors encountered during the request. Many APIs return way more than that in the error message.

Currently my workaround is to extend the GuzzleSender and override the createGuzzleClient method:

class GuzzleSenderNoTruncate extends GuzzleSender
{
    /**
     * Create a new Guzzle client
     */
    protected function createGuzzleClient(): Client
    {
        // We'll use HandlerStack::create as it will create a default
        // handler stack with the default Guzzle middleware like
        // http_errors, cookies etc.

        $this->handlerStack = HandlerStack::create();

        // Replace the httpErrors middleware
        $this->handlerStack->before(
            'http_errors',
            Middleware::httpErrors(new BodySummarizer(2000)),
            'http_errors_untruncated'
        );
        $this->handlerStack->remove('http_errors');

        // Now we'll return new Guzzle client with some default request
        // options configured. We'll also define the handler stack we
        // created above. Since it's a property, developers may
        // customise or add middleware to the handler stack.

        return new Client([
            RequestOptions::CRYPTO_METHOD => Config::$defaultTlsMethod,
            RequestOptions::CONNECT_TIMEOUT => Config::$defaultConnectionTimeout,
            RequestOptions::TIMEOUT => Config::$defaultRequestTimeout,
            RequestOptions::HTTP_ERRORS => true,
            'handler' => $this->handlerStack,
        ]);
    }
}

Unfortunately this also requires copying all of the original method's code. At the very least I'd like to mitigate this by moving the top part to a new method defaultHandlerStack():

class GuzzleSenderNoTruncate extends GuzzleSender
{
    /**
     * Create a new Guzzle client
     */
    protected function createGuzzleClient(): Client
    {
        $this->handlerStack = $this->defaultHandlerStack();

        // Now we'll return new Guzzle client with some default request
        // options configured. We'll also define the handler stack we
        // created above. Since it's a property, developers may
        // customise or add middleware to the handler stack.

        return new Client([
            RequestOptions::CRYPTO_METHOD => Config::$defaultTlsMethod,
            RequestOptions::CONNECT_TIMEOUT => Config::$defaultConnectionTimeout,
            RequestOptions::TIMEOUT => Config::$defaultRequestTimeout,
            RequestOptions::HTTP_ERRORS => true,
            'handler' => $this->handlerStack,
        ]);
    }

    /**
     * Get the default handler stack to be used by the Guzzle client.
     */
    protected function defaultHandlerStack(): HandlerStack|null
    {
        // We'll use HandlerStack::create as it will create a default
        // handler stack with the default Guzzle middleware like
        // http_errors, cookies etc.

        return HandlerStack::create();
    }
}

Then we could just set the defaultHandlerStack in our extending class.

use GuzzleHttp\BodySummarizer;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Saloon\Http\Senders\GuzzleSender;

class GuzzleSenderNoTruncate extends GuzzleSender
{
    protected function defaultHandlerStack(): HandlerStack
    {
        $handlerStack = parent::defaultHandlerStack();

        // Replace the httpErrors middleware
        $handlerStack->before(
            'http_errors',
            Middleware::httpErrors(new BodySummarizer(2000)),
            'http_errors_untruncated'
        );
        $handlerStack->remove('http_errors');

        return $handlerStack;
    }
}

Another option would be to also implement the underlying HandlerStack before, after, and remove methods on the GuzzleSender implementation, similar to how addMiddleware implements push, so we can customize the stack after it's instantiated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant