8000 bug #46382 [HttpClient] Honor "max_duration" when replacing requests … · symfony/symfony@d638e0a · GitHub
[go: up one dir, main page]

Skip to content

Commit d638e0a

Browse files
bug #46382 [HttpClient] Honor "max_duration" when replacing requests with async decorators (nicolas-grekas)
This PR was merged into the 5.4 branch. Discussion ---------- [HttpClient] Honor "max_duration" when replacing requests with async decorators | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Fix #46316 | License | MIT | Doc PR | - Instead of #46330 Commits ------- c81d116 [HttpClient] Honor "max_duration" when replacing requests with async decorators
2 parents 3051dff + c81d116 commit d638e0a

File tree

7 files changed

+40
-1
lines changed

7 files changed

+40
-1
lines changed

src/Symfony/Component/HttpClient/Response/AmpResponse.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public function __construct(AmpClientState $multi, Request $request, array $opti
8787
$info['upload_content_length'] = -1.0;
8888
$info['download_content_length'] = -1.0;
8989
$info['user_data'] = $options['user_data'];
90+
$info['max_duration'] = $options['max_duration'];
9091
$info['debug'] = '';
9192

9293
$onProgress = $options['on_progress'] ?? static function () {};

src/Symfony/Component/HttpClient/Response/AsyncContext.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\HttpClient\Chunk\DataChunk;
1515
use Symfony\Component\HttpClient\Chunk\LastChunk;
16+
use Symfony\Component\HttpClient\Exception\TransportException;
1617
use Symfony\Contracts\HttpClient\ChunkInterface;
1718
use Symfony\Contracts\HttpClient\HttpClientInterface;
1819
use Symfony\Contracts\HttpClient\ResponseInterface;
@@ -152,13 +153,18 @@ public function getResponse(): ResponseInterface
152153
*/
153154
public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface
154155
{
155-
$this->info['previous_info'][] = $this->response->getInfo();
156+
$this->info['previous_info'][] = $info = $this->response->getInfo();
156157
if (null !== $onProgress = $options['on_progress'] ?? null) {
157158
$thisInfo = &$this->info;
158159
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
159160
$onProgress($dlNow, $dlSize, $thisInfo + $info);
160161
};
161162
}
163+
if (0 < ($info['max_duration'] ?? 0) && 0 < ($info['total_time'] ?? 0)) {
164+
if (0 >= $options['max_duration'] = $info['max_duration'] - $info['total_time']) {
165+
throw new TransportException(sprintf('Max duration was reached for "%s".', $info['url']));
166+
}
167+
}
162168

163169
return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options);
164170
}

src/Symfony/Component/HttpClient/Response/AsyncResponse.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ public function __construct(HttpClientInterface $client, string $method, string
8585
if (\array_key_exists('user_data', $options)) {
8686
$this->info['user_data'] = $options['user_data'];
8787
}
88+
if (\array_key_exists('max_duration', $options)) {
89+
$this->info['max_duration'] = $options['max_duration'];
90+
}
8891
}
8992

9093
public function getStatusCode(): int

src/Symfony/Component/HttpClient/Response/CurlResponse.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
6565
$this->timeout = $options['timeout'] ?? null;
6666
$this->info['http_method'] = $method;
6767
$this->info['user_data'] = $options['user_data'] ?? null;
68+
$this->info['max_duration'] = $options['max_duration'] ?? null;
6869
$this->info['start_time'] = $this->info['start_time'] ?? microtime(true);
6970
$info = &$this->info;
7071
$headers = &$this->headers;

src/Symfony/Component/HttpClient/Response/MockResponse.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ public static function fromRequest(string $method, string $url, array $options,
140140
$response->info['http_method'] = $method;
141141
$response->info['http_code'] = 0;
142142
$response->info['user_data'] = $options['user_data'] ?? null;
143+
$response->info['max_duration'] = $options['max_duration'] ?? null;
143144
$response->info['url'] = $url;
144145

145146
if ($mock instanceof self) {
@@ -285,6 +286,7 @@ private static function readResponse(self $response, array $options, ResponseInt
285286
$response->info = [
286287
'start_time' => $response->info['start_time'],
287288
'user_data' => $response->info['user_data'],
289+
'max_duration' => $response->info['max_duration'],
288290
'http_code' => $response->info['http_code'],
289291
] + $info + $response->info;
290292

src/Symfony/Component/HttpClient/Response/NativeResponse.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public function __construct(NativeClientState $multi, $context, string $url, arr
5959
$this->buffer = fopen('php://temp', 'w+');
6060

6161
$info['user_data'] = $options['user_data'];
62+
$info['max_duration'] = $options['max_duration'];
6263
++$multi->responseCount;
6364

6465
$this->initializer = static function (self $response) {

src/Symfony/Component/HttpClient/Tests/AsyncDecoratorTraitTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\HttpClient\AsyncDecoratorTrait;
1515
use Symfony\Component\HttpClient\DecoratorTrait;
16+
use Symfony\Component\HttpClient\Exception\TransportException;
1617
use Symfony\Component\HttpClient\HttpClient;
1718
use Symfony\Component\HttpClient\Response\AsyncContext;
1819
use Symfony\Component\HttpClient\Response\AsyncResponse;
@@ -339,4 +340,28 @@ public function request(string $method, string $url, array $options = []): Respo
339340
$this->expectExceptionMessage('Instance of "Symfony\Component\HttpClient\Response\NativeResponse" is already consumed and cannot be managed by "Symfony\Component\HttpClient\Response\AsyncResponse". A decorated client should not call any of the response\'s methods in its "request()" method.');
340341
$response->getStatusCode();
341342
}
343+
344+
public function testMaxDuration()
345+
{
346+
$sawFirst = false;
347+
$client = $this->getHttpClient(__FUNCTION__, function (ChunkInterface $chunk, AsyncContext $context) use (&$sawFirst) {
348+
try {
349+
if (!$chunk->isFirst() || !$sawFirst) {
350+
$sawFirst = $sawFirst || $chunk->isFirst();
351+
yield $chunk;
352+
}
353+
} catch (TransportExceptionInterface $e) {
354+
$context->getResponse()->cancel();
355+
$context->replaceRequest('GET', 'http://localhost:8057/timeout-body', ['timeout' => 0.4]);
356+
}
357+
});
358+
359+
$response = $client->request('GET', 'http://localhost:8057/timeout-body', ['max_duration' => 0.75, 'timeout' => 0.4]);
360+
361+
$this->assertSame(0.75, $response->getInfo('max_duration'));
362+
363+
$this->expectException(TransportException::class);
364+
$this->expectExceptionMessage('Max duration was reached for "http://localhost:8057/timeout-body".');
365+
$response->getContent();
366+
}
342367
}

0 commit comments

Comments
 (0)
0