From 046b72bdc9f903f8ad8d5821ec4200b1aeffaa05 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Thu, 22 Jun 2023 09:28:41 +1000 Subject: [PATCH 1/9] Add on request callback hook to the http client --- src/Illuminate/Http/Client/Factory.php | 26 +++++++++++++++++++++++++- tests/Http/HttpClientTest.php | 21 +++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index ab0c0c53a001..a909f4c4f3f5 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -28,6 +28,13 @@ class Factory */ protected $dispatcher; + /** + * The callbacks that should execute before every request is sent. + * + * @var array + */ + protected $beforeSendingCallbacks = []; + /** * The stub callables that will handle requests. * @@ -76,6 +83,19 @@ public function __construct(Dispatcher $dispatcher = null) $this->stubCallbacks = collect(); } + /** + * Add a new "before sending" callback to every request. + * + * @param callable $callback + * @return $this + */ + public function beforeSending($callback) + { + $this->beforeSendingCallbacks[] = $callback; + + return $this; + } + /** * Create a new response instance for use during stubbing. * @@ -353,7 +373,11 @@ public function recorded($callback = null) */ protected function newPendingRequest() { - return new PendingRequest($this); + return tap(new PendingRequest($this), function ($request) { + foreach ($this->beforeSendingCallbacks as $callback) { + $request->beforeSending($callback); + } + }); } /** diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index afd08b148bc4..9895aea9e07c 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -2316,4 +2316,25 @@ public function testTheTransferStatsAreCustomizableOnFake(): void $this->assertTrue($onStatsFunctionCalled); } + + public function testItCanMakeGlobalChangesToNewPendingRequests() + { + $requests = []; + $this->factory->fake(function ($r) use (&$requests) { + $requests[] = $r; + + return $this->factory::response('expected content'); + }); + + $this->factory->beforeSending(function (Request $request, array $options, PendingRequest $pending) { + return $request->toPsrRequest()->withHeader('User-Agent', 'Laravel Framework/1.0'); + }); + $response = $this->factory->post('http://forge.laravel.com'); + $response = $this->factory->post('http://vapor.laravel.com'); + + $this->assertSame('expected content', $response->body()); + $this->assertCount(2, $requests); + $this->assertSame(['Laravel Framework/1.0'], $requests[0]->header('User-Agent')); + $this->assertSame(['Laravel Framework/1.0'], $requests[1]->header('User-Agent')); + } } From 8143dc25ae6ee1a6dcbf9073e92557eda1ac97c4 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Thu, 22 Jun 2023 10:24:03 +1000 Subject: [PATCH 2/9] Rename test --- tests/Http/HttpClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 9895aea9e07c..e3d731f3dc01 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -2317,7 +2317,7 @@ public function testTheTransferStatsAreCustomizableOnFake(): void $this->assertTrue($onStatsFunctionCalled); } - public function testItCanMakeGlobalChangesToNewPendingRequests() + public function testItCanAddGlobalBeforeSendingCallbacks() { $requests = []; $this->factory->fake(function ($r) use (&$requests) { From 323a9993178c55c9d1abb902ddf5f2cb1090f805 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Thu, 22 Jun 2023 11:17:50 +1000 Subject: [PATCH 3/9] Support replacing and adding headers. --- src/Illuminate/Http/Client/Request.php | 48 ++++++++++++++++++++++++++ tests/Http/HttpClientTest.php | 18 +++++++--- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Http/Client/Request.php b/src/Illuminate/Http/Client/Request.php index 3c2d6a9bdb19..0e6599066661 100644 --- a/src/Illuminate/Http/Client/Request.php +++ b/src/Illuminate/Http/Client/Request.php @@ -122,6 +122,54 @@ public function headers() return $this->request->getHeaders(); } + /** + * Add the given headers to the request. + * + * @return $this + */ + public function withHeaders($headers) + { + foreach ($headers as $key => $value) { + $this->request = $this->request->withAddedHeader($key, $value); + } + + return $this; + } + + /** + * Add a given header to the request. + * + * @return $this + */ + public function withHeader($key, $value) + { + return $this->withHeaders([$key => $value]); + } + + /** + * Replace the given headers on the request. + * + * @return $this + */ + public function replaceHeaders($headers) + { + foreach ($headers as $key => $value) { + $this->request = $this->request->withHeader($key, $value); + } + + return $this; + } + + /** + * Replace a given header on the request. + * + * @return $this + */ + public function replaceHeader($key, $value) + { + return $this->replaceHeaders([$key => $value]); + } + /** * Get the body of the request. * diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index e3d731f3dc01..3ba74ffe7afd 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -2317,7 +2317,7 @@ public function testTheTransferStatsAreCustomizableOnFake(): void $this->assertTrue($onStatsFunctionCalled); } - public function testItCanAddGlobalBeforeSendingCallbacks() + public function testItCanAddGlobalHeadersBeforeSending() { $requests = []; $this->factory->fake(function ($r) use (&$requests) { @@ -2327,14 +2327,22 @@ public function testItCanAddGlobalBeforeSendingCallbacks() }); $this->factory->beforeSending(function (Request $request, array $options, PendingRequest $pending) { - return $request->toPsrRequest()->withHeader('User-Agent', 'Laravel Framework/1.0'); + return $request->replaceHeader('User-Agent', 'Laravel Framework/1.0') + ->withHeader('shared', 'global') + ->withHeader('list', ['item-1', 'item-2']) + ->withHeader('list', ['item-3']); }); - $response = $this->factory->post('http://forge.laravel.com'); - $response = $this->factory->post('http://vapor.laravel.com'); + $this->factory->post('http://forge.laravel.com'); + $this->factory->withHeader('shared', 'local')->post('http://vapor.laravel.com'); - $this->assertSame('expected content', $response->body()); $this->assertCount(2, $requests); + $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(['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')); } } From ff18acda86b62f6edff0c29b2eb894957f93ed4c Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Fri, 23 Jun 2023 09:34:47 +1000 Subject: [PATCH 4/9] Rename method so it doesn't conflict --- src/Illuminate/Http/Client/Factory.php | 2 +- tests/Http/HttpClientTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index a909f4c4f3f5..545c50957c93 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -89,7 +89,7 @@ public function __construct(Dispatcher $dispatcher = null) * @param callable $callback * @return $this */ - public function beforeSending($callback) + public function beforeSendingAll($callback) { $this->beforeSendingCallbacks[] = $callback; diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 3ba74ffe7afd..5e73a5dc2601 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -2317,7 +2317,7 @@ public function testTheTransferStatsAreCustomizableOnFake(): void $this->assertTrue($onStatsFunctionCalled); } - public function testItCanAddGlobalHeadersBeforeSending() + public function testItCanAddGlobalHeadersBeforeSendingAll() { $requests = []; $this->factory->fake(function ($r) use (&$requests) { @@ -2326,7 +2326,7 @@ public function testItCanAddGlobalHeadersBeforeSending() return $this->factory::response('expected content'); }); - $this->factory->beforeSending(function (Request $request, array $options, PendingRequest $pending) { + $this->factory->beforeSendingAll(function (Request $request, array $options, PendingRequest $pending) { return $request->replaceHeader('User-Agent', 'Laravel Framework/1.0') ->withHeader('shared', 'global') ->withHeader('list', ['item-1', 'item-2']) From 33dcef0b665777a66a4e262edd13175133ef8d8b Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Sat, 24 Jun 2023 13:55:41 +1000 Subject: [PATCH 5/9] Support global guzzle middleware --- src/Illuminate/Http/Client/Factory.php | 22 +++++++- src/Illuminate/Http/Client/PendingRequest.php | 5 +- tests/Http/HttpClientTest.php | 55 +++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index 545c50957c93..820d0d0220cf 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -35,6 +35,13 @@ class Factory */ protected $beforeSendingCallbacks = []; + /** + * The middleware to apply to every request. + * + * @var array + */ + protected $globalMiddleware = []; + /** * The stub callables that will handle requests. * @@ -96,6 +103,19 @@ public function beforeSendingAll($callback) return $this; } + /** + * Add middleware to apply to every request. + * + * @param callable $middleware + * @return $this + */ + public function globalMiddleware($middleware) + { + $this->globalMiddleware[] = $middleware; + + return $this; + } + /** * Create a new response instance for use during stubbing. * @@ -373,7 +393,7 @@ public function recorded($callback = null) */ protected function newPendingRequest() { - return tap(new PendingRequest($this), function ($request) { + return tap(new PendingRequest($this, $this->globalMiddleware), function ($request) { foreach ($this->beforeSendingCallbacks as $callback) { $request->beforeSending($callback); } diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 9aa08cf588b4..e7a2853eb725 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -216,12 +216,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(); diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 5e73a5dc2601..863e4898ba99 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,8 @@ use OutOfBoundsException; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use RuntimeException; use Symfony\Component\VarDumper\VarDumper; @@ -2345,4 +2348,56 @@ public function testItCanAddGlobalHeadersBeforeSendingAll() $this->assertSame(['item-1', 'item-2', 'item-3'], $requests[1]->header('list')); $this->assertSame(['local', 'global'], $requests[1]->header('shared')); } + + public function testItCanAddGlobalMiddleware() + { + Carbon::setTestNow(now()->startOfDay()); + $requests = []; + $responses = []; + $count = 0; + $this->factory->fake(function ($r) use (&$requests) { + $requests[] = $r; + + Carbon::setTestNow(now()->addSeconds(41)); + + 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 (&$count) { + // Test adding headers in incoming response.. + return $response->withHeader('X-Count', (string) ++$count); + }))->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())); + }); + }; + }); + $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('41', $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('41', $responses[1]->header('X-Duration')); + } } From 265baff92b899c91e578904adf4ab782403ab6f5 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Sat, 24 Jun 2023 14:11:34 +1000 Subject: [PATCH 6/9] Remove beforesending hook --- src/Illuminate/Http/Client/Factory.php | 26 +--------------- tests/Http/HttpClientTest.php | 42 ++++---------------------- 2 files changed, 7 insertions(+), 61 deletions(-) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index 820d0d0220cf..e026bf3d5413 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -28,13 +28,6 @@ class Factory */ protected $dispatcher; - /** - * The callbacks that should execute before every request is sent. - * - * @var array - */ - protected $beforeSendingCallbacks = []; - /** * The middleware to apply to every request. * @@ -90,19 +83,6 @@ public function __construct(Dispatcher $dispatcher = null) $this->stubCallbacks = collect(); } - /** - * Add a new "before sending" callback to every request. - * - * @param callable $callback - * @return $this - */ - public function beforeSendingAll($callback) - { - $this->beforeSendingCallbacks[] = $callback; - - return $this; - } - /** * Add middleware to apply to every request. * @@ -393,11 +373,7 @@ public function recorded($callback = null) */ protected function newPendingRequest() { - return tap(new PendingRequest($this, $this->globalMiddleware), function ($request) { - foreach ($this->beforeSendingCallbacks as $callback) { - $request->beforeSending($callback); - } - }); + return new PendingRequest($this, $this->globalMiddleware); } /** diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 863e4898ba99..1f213ef5a17b 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -2320,45 +2320,15 @@ public function testTheTransferStatsAreCustomizableOnFake(): void $this->assertTrue($onStatsFunctionCalled); } - public function testItCanAddGlobalHeadersBeforeSendingAll() - { - $requests = []; - $this->factory->fake(function ($r) use (&$requests) { - $requests[] = $r; - - return $this->factory::response('expected content'); - }); - - $this->factory->beforeSendingAll(function (Request $request, array $options, PendingRequest $pending) { - return $request->replaceHeader('User-Agent', 'Laravel Framework/1.0') - ->withHeader('shared', 'global') - ->withHeader('list', ['item-1', 'item-2']) - ->withHeader('list', ['item-3']); - }); - $this->factory->post('http://forge.laravel.com'); - $this->factory->withHeader('shared', 'local')->post('http://vapor.laravel.com'); - - $this->assertCount(2, $requests); - - $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(['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')); - } - public function testItCanAddGlobalMiddleware() { Carbon::setTestNow(now()->startOfDay()); $requests = []; $responses = []; - $count = 0; $this->factory->fake(function ($r) use (&$requests) { $requests[] = $r; - Carbon::setTestNow(now()->addSeconds(41)); + Carbon::setTestNow(now()->addSeconds(6 * count($requests))); return $this->factory::response('expected content'); }); @@ -2369,16 +2339,16 @@ public function testItCanAddGlobalMiddleware() ->withAddedHeader('shared', 'global') ->withHeader('list', ['item-1', 'item-2']) ->withAddedHeader('list', ['item-3']); - }))->globalMiddleware(Middleware::mapResponse(function ($response) use (&$count) { + }))->globalMiddleware(Middleware::mapResponse(function ($response) use (&$requests) { // Test adding headers in incoming response.. - return $response->withHeader('X-Count', (string) ++$count); + 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())); + return $response->withHeader('X-Duration', "{$startedAt->diffInSeconds(now())} seconds"); }); }; }); @@ -2392,12 +2362,12 @@ public function testItCanAddGlobalMiddleware() $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('41', $responses[0]->header('X-Duration')); + $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('41', $responses[1]->header('X-Duration')); + $this->assertSame('12 seconds', $responses[1]->header('X-Duration')); } } From 6f60a25dd995c26fab12c2a78bd41dd0ffb31a96 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Sat, 24 Jun 2023 14:12:32 +1000 Subject: [PATCH 7/9] Remove request header methods --- src/Illuminate/Http/Client/Request.php | 48 -------------------------- 1 file changed, 48 deletions(-) diff --git a/src/Illuminate/Http/Client/Request.php b/src/Illuminate/Http/Client/Request.php index 0e6599066661..3c2d6a9bdb19 100644 --- a/src/Illuminate/Http/Client/Request.php +++ b/src/Illuminate/Http/Client/Request.php @@ -122,54 +122,6 @@ public function headers() return $this->request->getHeaders(); } - /** - * Add the given headers to the request. - * - * @return $this - */ - public function withHeaders($headers) - { - foreach ($headers as $key => $value) { - $this->request = $this->request->withAddedHeader($key, $value); - } - - return $this; - } - - /** - * Add a given header to the request. - * - * @return $this - */ - public function withHeader($key, $value) - { - return $this->withHeaders([$key => $value]); - } - - /** - * Replace the given headers on the request. - * - * @return $this - */ - public function replaceHeaders($headers) - { - foreach ($headers as $key => $value) { - $this->request = $this->request->withHeader($key, $value); - } - - return $this; - } - - /** - * Replace a given header on the request. - * - * @return $this - */ - public function replaceHeader($key, $value) - { - return $this->replaceHeaders([$key => $value]); - } - /** * Get the body of the request. * From 68074e00cd1ef97a8a53871e402193af1312c338 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Sat, 24 Jun 2023 14:38:03 +1000 Subject: [PATCH 8/9] lint --- tests/Http/HttpClientTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 1f213ef5a17b..a691f94337cb 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -29,7 +29,6 @@ use OutOfBoundsException; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use RuntimeException; use Symfony\Component\VarDumper\VarDumper; From 60fceaeb61a51edd06818af5a20a6c176fd5829b Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Sun, 25 Jun 2023 08:06:38 +1000 Subject: [PATCH 9/9] Add named methods --- src/Illuminate/Http/Client/Factory.php | 27 +++++++ src/Illuminate/Http/Client/PendingRequest.php | 27 +++++++ tests/Http/HttpClientTest.php | 70 +++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index e026bf3d5413..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; @@ -96,6 +97,32 @@ public function 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. * diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index e7a2853eb725..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; @@ -627,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 a691f94337cb..51dc38a4c38d 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -2369,4 +2369,74 @@ public function testItCanAddGlobalMiddleware() $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')); + } }