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

[10.x] Add global middleware to Http client #47525

Merged
merged 9 commits into from
Jun 26, 2023

Conversation

timacdonald
Copy link
Member

@timacdonald timacdonald commented Jun 21, 2023

💡 Updated!

This PR allows an application to add Guzzle middleware that is applied to every request made via the Http client. It also introduces some nicer named methods to add request / response only middleware.

Read more about Guzzle middleware in their docs.

As an example, an application may globally set a User-Agent header, to be a good citizen of the Internet. To do this, you may add a global "request" middleware:

// In a service provider...

use Illuminate\Support\Facades\Http;

Http::globalRequestMiddleware(
    fn ($request) => $request->withHeader('User-Agent', 'Laravel Framework/1.0')
);

This will mean that every outgoing request will now have the User-Agent: Laravel Framework/1.0 header.

Here is another example, showing the addition of a global "response" middleware.

// In a service provider...

use Illuminate\Support\Facades\Http;

Http::globalResponseMiddleware(
    fn ($response) => $response->withHeader('X-Finished-At', now()->toDateTimeString())
);

It is also possible, of course, to add a complete middleware that wraps both the request and response cycle. As an example, the following middleware will add a response header including the duration of the request in milliseconds. The value may also be logged or recorded elsewhere.

// In a service provider...

use Illuminate\Support\Facades\Http;

Http::globalMiddleware(function ($handler) {
    return function ($request, $options) use ($handler) {
        $startedAt = now();

        return $handler($request, $options)
            ->then(fn ($response) => $response->withHeader(
                'X-Duration', $startedAt->diffInMilliseconds(now())
            ));
    };
});

Something like the above would also be possible via the on_stats Guzzle option.

The global middleware is also useful for logging and inspection of outgoing requests or incoming responses, although this is also possible by listening to events, but now we have a unified way of doing this.

// In a service provider...

use RuntimeException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

Http::globalRequestMiddleware(function ($request) {
    if ($request->getUri()->getScheme() !== 'https') {
        throw new RuntimeException('Outgoing HTTP connections must use HTTPS.');
    }

    Log::info('Sending a HTTP request', ['url' => (string) $request->getUri()]);

    return $request;
});

I've also introduced named middleware methods for the request / response middleware to the PendingRequest class. These make adding middleware feel more at home in Laravel, IMO, when compared to having to reference the GuzzleHttp\Middleware class directly.

Here is a before and after of using the new named methods for a "request" middleware:

- use GuzzleHttp\Middleware;
use Illuminate\Support\Facades\Http;
 
- $response = Http::withMiddleware(Middleware::mapRequest(function ($request) {
+ $response = Http::withRequestMiddleware(function ($request) {
    return $request->withHeader('X-Example', 'Value');
- }))->get('http://example.com');
+ })->get('http://example.com');

You can see the currently documented way in the screenshot below. I feel these named methods greatly streamline the user-facing API.

Screen Shot 2023-06-25 at 1 21 31 pm

Here is the full API for adding middleware to Http requests after this PR:

// Global middleware...

Http::globalMiddleware(function ($handler) {
    // ...
});

// Global request middleware...

Http::globalRequestMiddleware(function ($request) {
    // ...
});

// Global response middleware...

Http::globalResponseMiddleware(function ($response) {
    // ...
});

// One-time middleware...

Http::withMiddleware(function ($handler) {
    // ...
})->get(/* ... */);

// One-time request middleware...

Http::withRequestMiddleware(function ($handler) {
    // ...
})->get(/* ... */);

// One-time response middleware...

Http::withResponseMiddleware(function ($handler) {
    // ...
})->get(/* ... */);

Similar PRs:

@timacdonald timacdonald force-pushed the http-on-request branch 2 times, most recently from 898e482 to 30552f8 Compare June 21, 2023 23:52
@timacdonald timacdonald changed the title [10.x] Add on request callback hook to the http client [10.x] Add global beforeSending hook to the Http factory Jun 21, 2023
@timacdonald timacdonald marked this pull request as ready for review June 22, 2023 00:09
@timacdonald timacdonald changed the title [10.x] Add global beforeSending hook to the Http factory [10.x] Add global beforeSending hook to the Http factory Jun 22, 2023
@timacdonald timacdonald marked this pull request as draft June 22, 2023 23:29
@timacdonald timacdonald marked this pull request as ready for review June 23, 2023 21:51
@timacdonald timacdonald reopened this Jun 23, 2023
@timacdonald timacdonald marked this pull request as draft June 23, 2023 23:08
@timacdonald timacdonald changed the title [10.x] Add global beforeSending hook to the Http factory [10.x] Add global middleware to Http client Jun 24, 2023
@timacdonald timacdonald marked this pull request as ready for review June 24, 2023 04:37
@timacdonald timacdonald marked this pull request as draft June 24, 2023 08:16
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

Successfully merging this pull request may close these issues.

2 participants