Skip to content

Commit 1222ed8

Browse files
[12.x] Add ability to run callbacks after building an Http response (#58088)
* runAfterResponseCallbacks * Update PendingRequest.php * style * callable type * fix runBeforeSendingCallback types * can mutate response * tests * remove comment * formatting --------- Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 00ed662 commit 1222ed8

File tree

2 files changed

+105
-7
lines changed

2 files changed

+105
-7
lines changed

src/Illuminate/Http/Client/PendingRequest.php

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,13 @@ class PendingRequest
165165
*/
166166
protected $beforeSendingCallbacks;
167167

168+
/**
169+
* The callbacks that should execute after the Laravel Response is built.
170+
*
171+
* @var \Illuminate\Support\Collection<int, (callable(\Illuminate\Http\Client\Response): \Illuminate\Http\Client\Response|null)>
172+
*/
173+
protected $afterResponseCallbacks;
174+
168175
/**
169176
* The stub callables that will handle requests.
170177
*
@@ -268,6 +275,8 @@ public function __construct(?Factory $factory = null, $middleware = [])
268275

269276
$pendingRequest->dispatchRequestSendingEvent();
270277
}]);
278+
279+
$this->afterResponseCallbacks = new Collection();
271280
}
272281

273282
/**
@@ -738,6 +747,19 @@ public function beforeSending($callback)
738747
});
739748
}
740749

750+
/**
751+
* Add a new callback to execute after the response is built.
752+
*
753+
* @param (callable(\Illuminate\Http\Client\Response): \Illuminate\Http\Client\Response|null) $callback
754+
* @return $this
755+
*/
756+
public function afterResponse(callable $callback)
757+
{
758+
$this->afterResponseCallbacks[] = $callback;
759+
760+
return $this;
761+
}
762+
741763
/**
742764
* Throw an exception if a server or client error occurs.
743765
*
@@ -1004,10 +1026,11 @@ public function send(string $method, string $url, array $options = [])
10041026

10051027
return retry($this->tries ?? 1, function ($attempt) use ($method, $url, $options, &$shouldRetry) {
10061028
try {
1007-
return tap($this->newResponse($this->sendRequest($method, $url, $options)), function ($response) use ($attempt, &$shouldRetry) {
1029+
return tap($this->newResponse($this->sendRequest($method, $url, $options)), function (&$response) use ($attempt, &$shouldRetry) {
10081030
$this->populateResponse($response);
10091031

10101032
$this->dispatchResponseReceivedEvent($response);
1033+
$response = $this->runAfterResponseCallbacks($response);
10111034

10121035
if ($response->successful()) {
10131036
return;
@@ -1150,10 +1173,12 @@ protected function makePromise(string $method, string $url, array $options = [],
11501173
{
11511174
return $this->promise = $this->sendRequest($method, $url, $options)
11521175
->then(function (MessageInterface $message) {
1153-
return tap($this->newResponse($message), function ($response) {
1154-
$this->populateResponse($response);
1155-
$this->dispatchResponseReceivedEvent($response);
1156-
});
1176+
$response = $this->newResponse($message);
1177+
1178+
$this->populateResponse($response);
1179+
$this->dispatchResponseReceivedEvent($response);
1180+
1181+
return $this->runAfterResponseCallbacks($response);
11571182
})
11581183
->otherwise(function (OutOfBoundsException|TransferException|StrayRequestException $e) {
11591184
if ($e instanceof StrayRequestException) {
@@ -1520,9 +1545,9 @@ protected function sinkStubHandler($sink)
15201545
/**
15211546
* Execute the "before sending" callbacks.
15221547
*
1523-
* @param \GuzzleHttp\Psr7\RequestInterface $request
1548+
* @param \Psr\Http\Message\RequestInterface $request
15241549
* @param array $options
1525-
* @return \GuzzleHttp\Psr7\RequestInterface
1550+
* @return \Psr\Http\Message\RequestInterface
15261551
*/
15271552
public function runBeforeSendingCallbacks($request, array $options)
15281553
{
@@ -1579,6 +1604,25 @@ protected function newResponse($response)
15791604
});
15801605
}
15811606

1607+
/**
1608+
* Execute the "after response" callbacks.
1609+
*
1610+
* @param \Illuminate\Http\Client\Response $response
1611+
* @return \Illuminate\Http\Client\Response
1612+
*/
1613+
protected function runAfterResponseCallbacks(Response $response)
1614+
{
1615+
foreach ($this->afterResponseCallbacks as $callback) {
1616+
$returnedResponse = $callback($response);
1617+
1618+
if ($returnedResponse instanceof Response) {
1619+
$response = $returnedResponse;
1620+
}
1621+
}
1622+
1623+
return $response;
1624+
}
1625+
15821626
/**
15831627
* Register a stub callable that will intercept requests and be able to return stub responses.
15841628
*

tests/Http/HttpClientTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4239,6 +4239,60 @@ public static function methodsReceivingArrayableDataProvider()
42394239
'delete' => ['delete'],
42404240
];
42414241
}
4242+
4243+
public function testAfterResponse()
4244+
{
4245+
$this->factory->fake([
4246+
'http://200.com*' => $this->factory::response('OK'),
4247+
]);
4248+
4249+
$response = $this->factory
4250+
->afterResponse(fn (Response $response): TestResponse => new TestResponse($response->toPsrResponse()))
4251+
->afterResponse(fn () => 'abc')
4252+
->afterResponse(function ($r) {
4253+
$this->assertInstanceOf(TestResponse::class, $r);
4254+
})
4255+
->afterResponse(fn (Response $r) => new Response($r->toPsrResponse()->withBody(Utils::streamFor(strtolower($r->body())))))
4256+
->get('http://200.com');
4257+
4258+
$this->assertInstanceOf(Response::class, $response);
4259+
$this->assertSame('ok', $response->body());
4260+
}
4261+
4262+
public function testAfterResponseWithThrows()
4263+
{
4264+
$this->factory->fake([
4265+
'http://500.com*' => $this->factory::response('oh no', 500),
4266+
]);
4267+
4268+
try {
4269+
$this->factory->throw()
4270+
->afterResponse(fn ($response) => new TestResponse($response->toPsrResponse()))
4271+
->post('http://500.com');
4272+
} catch (RequestException $e) {
4273+
$this->assertInstanceOf(TestResponse::class, $e->response);
4274+
}
4275+
}
4276+
4277+
public function testAfterResponseWithAsync()
4278+
{
4279+
$this->factory->fake([
4280+
'http://200.com*' => $this->factory::response('OK', 200),
4281+
'http://401.com*' => $this->factory::response('Unauthorized.', 401),
4282+
]);
4283+
4284+
$o = $this->factory->pool(function (Pool $pool): void {
4285+
$pool->as('200')->afterResponse(fn (Response $response) => new TestResponse($response->toPsrResponse()))->get('http://200.com');
4286+
$pool->as('401-throwing')->throw()->afterResponse(fn (Response $response) => new TestResponse($response->toPsrResponse()))->get('http://401.com');
4287+
$pool->as('401-response')->afterResponse(fn (Response $response) => new TestResponse($response->toPsrResponse()->withBody(Utils::streamFor('different'))))->get('http://401.com');
4288+
}, 0);
4289+
4290+
$this->assertInstanceOf(TestResponse::class, $o['200']);
4291+
$this->assertInstanceOf(TestResponse::class, $o['401-response']);
4292+
$this->assertEquals('different', $o['401-response']->body());
4293+
$this->assertInstanceOf(RequestException::class, $o['401-throwing']);
4294+
$this->assertInstanceOf(TestResponse::class, $o['401-throwing']->response);
4295+
}
42424296
}
42434297

42444298
class CustomFactory extends Factory

0 commit comments

Comments
 (0)