diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index ab0c0c53a001..6c24ed4ea08e 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -3,6 +3,7 @@ namespace Illuminate\Http\Client; use Closure; +use GuzzleHttp\Middleware; use GuzzleHttp\Promise\Create; use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Psr7\Response as Psr7Response; @@ -28,6 +29,13 @@ class Factory */ protected $dispatcher; + /** + * The middleware to apply to every request. + * + * @var array + */ + protected $globalMiddleware = []; + /** * The stub callables that will handle requests. * @@ -76,6 +84,45 @@ public function __construct(Dispatcher $dispatcher = null) $this->stubCallbacks = collect(); } + /** + * Add middleware to apply to every request. + * + * @param callable $middleware + * @return $this + */ + public function globalMiddleware($middleware) + { + $this->globalMiddleware[] = $middleware; + + return $this; + } + + /** + * Add request middleware to apply to every request. + * + * @param callable $middleware + * @return $this + */ + public function globalRequestMiddleware($middleware) + { + $this->globalMiddleware[] = Middleware::mapRequest($middleware); + + return $this; + } + + /** + * Add response middleware to apply to every request. + * + * @param callable $middleware + * @return $this + */ + public function globalResponseMiddleware($middleware) + { + $this->globalMiddleware[] = Middleware::mapResponse($middleware); + + return $this; + } + /** * Create a new response instance for use during stubbing. * @@ -353,7 +400,7 @@ public function recorded($callback = null) */ protected function newPendingRequest() { - return new PendingRequest($this); + return new PendingRequest($this, $this->globalMiddleware); } /** diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 9aa08cf588b4..094ad3d3cba2 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -10,6 +10,7 @@ use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\TransferException; use GuzzleHttp\HandlerStack; +use GuzzleHttp\Middleware; use GuzzleHttp\UriTemplate\UriTemplate; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Http\Client\Events\ConnectionFailed; @@ -216,12 +217,13 @@ class PendingRequest * Create a new HTTP Client instance. * * @param \Illuminate\Http\Client\Factory|null $factory + * @param array $middleware * @return void */ - public function __construct(Factory $factory = null) + public function __construct(Factory $factory = null, $middleware = []) { $this->factory = $factory; - $this->middleware = new Collection; + $this->middleware = new Collection($middleware); $this->asJson(); @@ -626,6 +628,32 @@ public function withMiddleware(callable $middleware) return $this; } + /** + * Add new request middleware the client handler stack. + * + * @param callable $middleware + * @return $this + */ + public function withRequestMiddleware(callable $middleware) + { + $this->middleware->push(Middleware::mapRequest($middleware)); + + return $this; + } + + /** + * Add new response middleware the client handler stack. + * + * @param callable $middleware + * @return $this + */ + public function withResponseMiddleware(callable $middleware) + { + $this->middleware->push(Middleware::mapResponse($middleware)); + + return $this; + } + /** * Add a new "before sending" callback to the request. * diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index afd08b148bc4..51dc38a4c38d 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -20,6 +20,7 @@ use Illuminate\Http\Client\ResponseSequence; use Illuminate\Http\Response as HttpResponse; use Illuminate\Support\Arr; +use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Illuminate\Support\Fluent; use Illuminate\Support\Str; @@ -28,6 +29,7 @@ use OutOfBoundsException; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ResponseInterface; use RuntimeException; use Symfony\Component\VarDumper\VarDumper; @@ -2316,4 +2318,125 @@ public function testTheTransferStatsAreCustomizableOnFake(): void $this->assertTrue($onStatsFunctionCalled); } + + public function testItCanAddGlobalMiddleware() + { + Carbon::setTestNow(now()->startOfDay()); + $requests = []; + $responses = []; + $this->factory->fake(function ($r) use (&$requests) { + $requests[] = $r; + + Carbon::setTestNow(now()->addSeconds(6 * count($requests))); + + return $this->factory::response('expected content'); + }); + + $this->factory->globalMiddleware(Middleware::mapRequest(function ($request) { + // Test manipulating headers on outgoing request... + return $request->withHeader('User-Agent', 'Laravel Framework/1.0') + ->withAddedHeader('shared', 'global') + ->withHeader('list', ['item-1', 'item-2']) + ->withAddedHeader('list', ['item-3']); + }))->globalMiddleware(Middleware::mapResponse(function ($response) use (&$requests) { + // Test adding headers in incoming response.. + return $response->withHeader('X-Count', (string) count($requests)); + }))->globalMiddleware(function ($handler) { + // Test wrapping request in timing function... + return function ($request, $options) use ($handler) { + $startedAt = now(); + + return $handler($request, $options)->then(function (ResponseInterface $response) use ($startedAt) { + return $response->withHeader('X-Duration', "{$startedAt->diffInSeconds(now())} seconds"); + }); + }; + }); + $responses[] = $this->factory->post('http://forge.laravel.com'); + $responses[] = $this->factory->withHeader('shared', 'local')->post('http://vapor.laravel.com'); + + $this->assertCount(2, $requests); + $this->assertCount(2, $responses); + + $this->assertSame(['Laravel Framework/1.0'], $requests[0]->header('User-Agent')); + $this->assertSame(['item-1', 'item-2', 'item-3'], $requests[0]->header('list')); + $this->assertSame(['global'], $requests[0]->header('shared')); + $this->assertSame('1', $responses[0]->header('X-Count')); + $this->assertSame('6 seconds', $responses[0]->header('X-Duration')); + + $this->assertSame(['Laravel Framework/1.0'], $requests[1]->header('User-Agent')); + $this->assertSame(['item-1', 'item-2', 'item-3'], $requests[1]->header('list')); + $this->assertSame(['local', 'global'], $requests[1]->header('shared')); + $this->assertSame('2', $responses[1]->header('X-Count')); + $this->assertSame('12 seconds', $responses[1]->header('X-Duration')); + } + + public function testItCanAddGlobalRequestMiddleware() + { + $requests = []; + $this->factory->fake(function ($r) use (&$requests) { + $requests[] = $r; + + return Factory::response('expected content'); + }); + + $this->factory->globalRequestMiddleware(function ($request) { + return $request->withHeader('User-Agent', 'Laravel Framework/1.0'); + }); + $this->factory->post('http://forge.laravel.com'); + $this->factory->post('http://laravel.com'); + + $this->assertSame(['Laravel Framework/1.0'], $requests[0]->header('User-Agent')); + $this->assertSame(['Laravel Framework/1.0'], $requests[1]->header('User-Agent')); + } + + public function testItCanAddGlobalResponseMiddleware() + { + $responses = []; + $this->factory->fake(function ($r) use (&$request) { + return Factory::response('expected content'); + }); + + $this->factory->globalResponseMiddleware(function ($response) { + return $response->withHeader('X-Foo', 'Bar'); + }); + $responses[] = $this->factory->post('http://forge.laravel.com'); + $responses[] = $this->factory->post('http://laravel.com'); + + $this->assertSame('Bar', $responses[0]->header('X-Foo')); + $this->assertSame('Bar', $responses[1]->header('X-Foo')); + } + + public function testItCanAddRequestMiddleware() + { + $requests = []; + $this->factory->fake(function ($r) use (&$requests) { + $requests[] = $r; + + return Factory::response('expected content'); + }); + + $this->factory->withRequestMiddleware(function ($request) { + return $request->withHeader('User-Agent', 'Laravel Framework/1.0'); + })->post('http://forge.laravel.com'); + $this->factory->post('http://laravel.com'); + + $this->assertSame(['Laravel Framework/1.0'], $requests[0]->header('User-Agent')); + $this->assertSame(['GuzzleHttp/7'], $requests[1]->header('User-Agent')); + } + + public function testItCanAddResponseMiddleware() + { + $responses = []; + $this->factory->fake(function ($r) use (&$request) { + return Factory::response('expected content'); + }); + + $responses[] = $this->factory->withResponseMiddleware(function ($response) { + return $response->withHeader('X-Foo', 'Bar'); + })->post('http://forge.laravel.com'); + $responses[] = $this->factory->post('http://laravel.com'); + + $this->assertSame('Bar', $responses[0]->header('X-Foo')); + $this->assertSame('', $responses[1]->header('X-Foo')); + } }