Skip to content

Commit b772693

Browse files
authored
[10.x] Add global middleware to Http client (#47525)
* Add on request callback hook to the http client * Rename test * Support replacing and adding headers. * Rename method so it doesn't conflict * Support global guzzle middleware * Remove beforesending hook * Remove request header methods * lint * Add named methods
1 parent 9f366f6 commit b772693

File tree

3 files changed

+201
-3
lines changed

3 files changed

+201
-3
lines changed

src/Illuminate/Http/Client/Factory.php

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Http\Client;
44

55
use Closure;
6+
use GuzzleHttp\Middleware;
67
use GuzzleHttp\Promise\Create;
78
use GuzzleHttp\Promise\PromiseInterface;
89
use GuzzleHttp\Psr7\Response as Psr7Response;
@@ -28,6 +29,13 @@ class Factory
2829
*/
2930
protected $dispatcher;
3031

32+
/**
33+
* The middleware to apply to every request.
34+
*
35+
* @var array
36+
*/
37+
protected $globalMiddleware = [];
38+
3139
/**
3240
* The stub callables that will handle requests.
3341
*
@@ -76,6 +84,45 @@ public function __construct(Dispatcher $dispatcher = null)
7684
$this->stubCallbacks = collect();
7785
}
7886

87+
/**
88+
* Add middleware to apply to every request.
89+
*
90+
* @param callable $middleware
91+
* @return $this
92+
*/
93+
public function globalMiddleware($middleware)
94+
{
95+
$this->globalMiddleware[] = $middleware;
96+
97+
return $this;
98+
}
99+
100+
/**
101+
* Add request middleware to apply to every request.
102+
*
103+
* @param callable $middleware
104+
* @return $this
105+
*/
106+
public function globalRequestMiddleware($middleware)
107+
{
108+
$this->globalMiddleware[] = Middleware::mapRequest($middleware);
109+
110+
return $this;
111+
}
112+
113+
/**
114+
* Add response middleware to apply to every request.
115+
*
116+
* @param callable $middleware
117+
* @return $this
118+
*/
119+
public function globalResponseMiddleware($middleware)
120+
{
121+
$this->globalMiddleware[] = Middleware::mapResponse($middleware);
122+
123+
return $this;
124+
}
125+
79126
/**
80127
* Create a new response instance for use during stubbing.
81128
*
@@ -353,7 +400,7 @@ public function recorded($callback = null)
353400
*/
354401
protected function newPendingRequest()
355402
{
356-
return new PendingRequest($this);
403+
return new PendingRequest($this, $this->globalMiddleware);
357404
}
358405

359406
/**

src/Illuminate/Http/Client/PendingRequest.php

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use GuzzleHttp\Exception\RequestException;
1111
use GuzzleHttp\Exception\TransferException;
1212
use GuzzleHttp\HandlerStack;
13+
use GuzzleHttp\Middleware;
1314
use GuzzleHttp\UriTemplate\UriTemplate;
1415
use Illuminate\Contracts\Support\Arrayable;
1516
use Illuminate\Http\Client\Events\ConnectionFailed;
@@ -216,12 +217,13 @@ class PendingRequest
216217
* Create a new HTTP Client instance.
217218
*
218219
* @param \Illuminate\Http\Client\Factory|null $factory
220+
* @param array $middleware
219221
* @return void
220222
*/
221-
public function __construct(Factory $factory = null)
223+
public function __construct(Factory $factory = null, $middleware = [])
222224
{
223225
$this->factory = $factory;
224-
$this->middleware = new Collection;
226+
$this->middleware = new Collection($middleware);
225227

226228
$this->asJson();
227229

@@ -641,6 +643,32 @@ public function withMiddleware(callable $middleware)
641643
return $this;
642644
}
643645

646+
/**
647+
* Add new request middleware the client handler stack.
648+
*
649+
* @param callable $middleware
650+
* @return $this
651+
*/
652+
public function withRequestMiddleware(callable $middleware)
653+
{
654+
$this->middleware->push(Middleware::mapRequest($middleware));
655+
656+
return $this;
657+
}
658+
659+
/**
660+
* Add new response middleware the client handler stack.
661+
*
662+
* @param callable $middleware
663+
* @return $this
664+
*/
665+
public function withResponseMiddleware(callable $middleware)
666+
{
667+
$this->middleware->push(Middleware::mapResponse($middleware));
668+
669+
return $this;
670+
}
671+
644672
/**
645673
* Add a new "before sending" callback to the request.
646674
*

tests/Http/HttpClientTest.php

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Illuminate\Http\Client\ResponseSequence;
2121
use Illuminate\Http\Response as HttpResponse;
2222
use Illuminate\Support\Arr;
23+
use Illuminate\Support\Carbon;
2324
use Illuminate\Support\Collection;
2425
use Illuminate\Support\Fluent;
2526
use Illuminate\Support\Str;
@@ -28,6 +29,7 @@
2829
use OutOfBoundsException;
2930
use PHPUnit\Framework\AssertionFailedError;
3031
use PHPUnit\Framework\TestCase;
32+
use Psr\Http\Message\ResponseInterface;
3133
use RuntimeException;
3234
use Symfony\Component\VarDumper\VarDumper;
3335

@@ -2374,4 +2376,125 @@ public function testTheTransferStatsAreCustomizableOnFake(): void
23742376

23752377
$this->assertTrue($onStatsFunctionCalled);
23762378
}
2379+
2380+
public function testItCanAddGlobalMiddleware()
2381+
{
2382+
Carbon::setTestNow(now()->startOfDay());
2383+
$requests = [];
2384+
$responses = [];
2385+
$this->factory->fake(function ($r) use (&$requests) {
2386+
$requests[] = $r;
2387+
2388+
Carbon::setTestNow(now()->addSeconds(6 * count($requests)));
2389+
2390+
return $this->factory::response('expected content');
2391+
});
2392+
2393+
$this->factory->globalMiddleware(Middleware::mapRequest(function ($request) {
2394+
// Test manipulating headers on outgoing request...
2395+
return $request->withHeader('User-Agent', 'Laravel Framework/1.0')
2396+
->withAddedHeader('shared', 'global')
2397+
->withHeader('list', ['item-1', 'item-2'])
2398+
->withAddedHeader('list', ['item-3']);
2399+
}))->globalMiddleware(Middleware::mapResponse(function ($response) use (&$requests) {
2400+
// Test adding headers in incoming response..
2401+
return $response->withHeader('X-Count', (string) count($requests));
2402+
}))->globalMiddleware(function ($handler) {
2403+
// Test wrapping request in timing function...
2404+
return function ($request, $options) use ($handler) {
2405+
$startedAt = now();
2406+
2407+
return $handler($request, $options)->then(function (ResponseInterface $response) use ($startedAt) {
2408+
return $response->withHeader('X-Duration', "{$startedAt->diffInSeconds(now())} seconds");
2409+
});
2410+
};
2411+
});
2412+
$responses[] = $this->factory->post('http://forge.laravel.com');
2413+
$responses[] = $this->factory->withHeader('shared', 'local')->post('http://vapor.laravel.com');
2414+
2415+
$this->assertCount(2, $requests);
2416+
$this->assertCount(2, $responses);
2417+
2418+
$this->assertSame(['Laravel Framework/1.0'], $requests[0]->header('User-Agent'));
2419+
$this->assertSame(['item-1', 'item-2', 'item-3'], $requests[0]->header('list'));
2420+
$this->assertSame(['global'], $requests[0]->header('shared'));
2421+
$this->assertSame('1', $responses[0]->header('X-Count'));
2422+
$this->assertSame('6 seconds', $responses[0]->header('X-Duration'));
2423+
2424+
$this->assertSame(['Laravel Framework/1.0'], $requests[1]->header('User-Agent'));
2425+
$this->assertSame(['item-1', 'item-2', 'item-3'], $requests[1]->header('list'));
2426+
$this->assertSame(['local', 'global'], $requests[1]->header('shared'));
2427+
$this->assertSame('2', $responses[1]->header('X-Count'));
2428+
$this->assertSame('12 seconds', $responses[1]->header('X-Duration'));
2429+
}
2430+
2431+
public function testItCanAddGlobalRequestMiddleware()
2432+
{
2433+
$requests = [];
2434+
$this->factory->fake(function ($r) use (&$requests) {
2435+
$requests[] = $r;
2436+
2437+
return Factory::response('expected content');
2438+
});
2439+
2440+
$this->factory->globalRequestMiddleware(function ($request) {
2441+
return $request->withHeader('User-Agent', 'Laravel Framework/1.0');
2442+
});
2443+
$this->factory->post('http://forge.laravel.com');
2444+
$this->factory->post('http://laravel.com');
2445+
2446+
$this->assertSame(['Laravel Framework/1.0'], $requests[0]->header('User-Agent'));
2447+
$this->assertSame(['Laravel Framework/1.0'], $requests[1]->header('User-Agent'));
2448+
}
2449+
2450+
public function testItCanAddGlobalResponseMiddleware()
2451+
{
2452+
$responses = [];
2453+
$this->factory->fake(function ($r) use (&$request) {
2454+
return Factory::response('expected content');
2455+
});
2456+
2457+
$this->factory->globalResponseMiddleware(function ($response) {
2458+
return $response->withHeader('X-Foo', 'Bar');
2459+
});
2460+
$responses[] = $this->factory->post('http://forge.laravel.com');
2461+
$responses[] = $this->factory->post('http://laravel.com');
2462+
2463+
$this->assertSame('Bar', $responses[0]->header('X-Foo'));
2464+
$this->assertSame('Bar', $responses[1]->header('X-Foo'));
2465+
}
2466+
2467+
public function testItCanAddRequestMiddleware()
2468+
{
2469+
$requests = [];
2470+
$this->factory->fake(function ($r) use (&$requests) {
2471+
$requests[] = $r;
2472+
2473+
return Factory::response('expected content');
2474+
});
2475+
2476+
$this->factory->withRequestMiddleware(function ($request) {
2477+
return $request->withHeader('User-Agent', 'Laravel Framework/1.0');
2478+
})->post('http://forge.laravel.com');
2479+
$this->factory->post('http://laravel.com');
2480+
2481+
$this->assertSame(['Laravel Framework/1.0'], $requests[0]->header('User-Agent'));
2482+
$this->assertSame(['GuzzleHttp/7'], $requests[1]->header('User-Agent'));
2483+
}
2484+
2485+
public function testItCanAddResponseMiddleware()
2486+
{
2487+
$responses = [];
2488+
$this->factory->fake(function ($r) use (&$request) {
2489+
return Factory::response('expected content');
2490+
});
2491+
2492+
$responses[] = $this->factory->withResponseMiddleware(function ($response) {
2493+
return $response->withHeader('X-Foo', 'Bar');
2494+
})->post('http://forge.laravel.com');
2495+
$responses[] = $this->factory->post('http://laravel.com');
2496+
2497+
$this->assertSame('Bar', $responses[0]->header('X-Foo'));
2498+
$this->assertSame('', $responses[1]->header('X-Foo'));
2499+
}
23772500
}

0 commit comments

Comments
 (0)