Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 85fc4fa

Browse files
committed
[HttpClient] Transfer timeout
1 parent a29aff0 commit 85fc4fa

File tree

9 files changed

+79
-3
lines changed

9 files changed

+79
-3
lines changed

src/Symfony/Component/HttpClient/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* added support for NTLM authentication
1010
* added `$response->toStream()` to cast responses to regular PHP streams
1111
* made `Psr18Client` implement relevant PSR-17 factories and have streaming responses
12+
* added `max_duration` option
1213

1314
4.3.0
1415
-----

src/Symfony/Component/HttpClient/CurlHttpClient.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ public function request(string $method, string $url, array $options = []): Respo
282282
$curlopts[file_exists($options['bindto']) ? CURLOPT_UNIX_SOCKET_PATH : CURLOPT_INTERFACE] = $options['bindto'];
283283
}
284284

285+
if (null !== $options['max_duration']) {
286+
$curlopts[CURLOPT_TIMEOUT_MS] = 1000 * $options['max_duration'];
287+
}
288+
285289
$ch = curl_init();
286290

287291
foreach ($curlopts as $opt => $value) {

src/Symfony/Component/HttpClient/HttpClientTrait.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ private static function prepareRequest(?string $method, ?string $url, array $opt
125125
$options['headers'] = $headers;
126126
$options['http_version'] = (string) ($options['http_version'] ?? '') ?: null;
127127
$options['timeout'] = (float) ($options['timeout'] ?? ini_get('default_socket_timeout'));
128+
$options['max_duration'] = isset($options['max_duration']) ? (float) $options['max_duration'] : null;
128129

129130
return [$url, $options];
130131
}

src/Symfony/Component/HttpClient/NativeHttpClient.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,25 @@ public function request(string $method, string $url, array $options = []): Respo
110110
'debug' => \extension_loaded('curl') ? '' : "* Enable the curl extension for better performance\n",
111111
];
112112

113+
$checkMaxDuration = null;
114+
if (null !== $maxDuration = $options['max_duration']) {
115+
$checkMaxDuration = static function () use ($maxDuration, &$info): void {
116+
if ($info['total_time'] >= $maxDuration) {
117+
throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url'])));
118+
}
119+
};
120+
}
121+
113122
if ($onProgress = $options['on_progress']) {
114123
// Memoize the last progress to ease calling the callback periodically when no network transfer happens
115124
$lastProgress = [0, 0];
116-
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info) {
125+
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info, $checkMaxDuration) {
126+
if ($checkMaxDuration) {
127+
$info['total_time'] = microtime(true) - $info['start_time'];
128+
129+
$checkMaxDuration();
130+
}
131+
117132
$progressInfo = $info;
118133
$progressInfo['url'] = implode('', $info['url']);
119134
unset($progressInfo['size_body']);
@@ -127,10 +142,12 @@ public function request(string $method, string $url, array $options = []): Respo
127142

128143
$onProgress($lastProgress[0], $lastProgress[1], $progressInfo);
129144
};
145+
} elseif ($checkMaxDuration) {
146+
$onProgress = $checkMaxDuration;
130147
}
131148

132149
// Always register a notification callback to compute live stats about the response
133-
$notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info) {
150+
$notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info, $checkMaxDuration) {
134151
$info['total_time'] = microtime(true) - $info['start_time'];
135152

136153
if (STREAM_NOTIFY_PROGRESS === $code) {
@@ -142,6 +159,10 @@ public function request(string $method, string $url, array $options = []): Respo
142159
$info['debug'] .= $info['request_header'];
143160
unset($info['request_header']);
144161
} else {
162+
if ($checkMaxDuration) {
163+
$checkMaxDuration();
164+
}
165+
145166
return;
146167
}
147168

@@ -166,6 +187,10 @@ public function request(string $method, string $url, array $options = []): Respo
166187
$options['request_headers'][] = 'user-agent: Symfony HttpClient/Native';
167188
}
168189

190+
if (null !== $options['max_duration']) {
191+
$options['timeout'] = min($options['max_duration'], $options['timeout']);
192+
}
193+
169194
$context = [
170195
'http' => [
171196
'protocol_version' => $options['http_version'] ?: '1.1',

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ protected function getHttpClient(string $testCase): HttpClientInterface
123123
$body = ['<1>', '', '<2>'];
124124
$responses[] = new MockResponse($body, ['response_headers' => $headers]);
125125
break;
126+
127+
case 'testMaxDuration':
128+
$mock = $this->getMockBuilder(ResponseInterface::class)->getMock();
129+
$mock->expects($this->any())
130+
->method('getContent')
131+
->willReturnCallback(static function (): void {
132+
usleep(100000);
133+
134+
throw new TransportException('Max duration was reached.');
135+
});
136+
137+
$responses[] = $mock;
138+
break;
126139
}
127140

128141
return new MockHttpClient($responses);

src/Symfony/Component/HttpClient/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"require": {
2323
"php": "^7.1.3",
2424
"psr/log": "^1.0",
25-
"symfony/http-client-contracts": "^1.1.4",
25+
"symfony/http-client-contracts": "^1.1.5",
2626
"symfony/polyfill-php73": "^1.11"
2727
},
2828
"require-dev": {

src/Symfony/Contracts/HttpClient/HttpClientInterface.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ interface HttpClientInterface
5353
'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored
5454
'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached
5555
'timeout' => null, // float - the inactivity timeout - defaults to ini_get('default_socket_timeout')
56+
'max_duration' => null, // float - the maximum execution time of the whole request (including the connection time)
5657
'bindto' => '0', // string - the interface or the local socket to bind to
5758
'verify_peer' => true, // see https://php.net/context.ssl for the following options
5859
'verify_host' => true,

src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@
132132
header('Content-Encoding: gzip');
133133
echo str_repeat('-', 1000);
134134
exit;
135+
136+
case '/max-duration':
137+
ignore_user_abort(false);
138+
while (true) {
139+
echo '<1>';
140+
@ob_flush();
141+
flush();
142+
usleep(500);
143+
}
144+
exit;
135145
}
136146

137147
header('Content-Type: application/json', true);

src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,4 +778,25 @@ public function testGzipBroken()
778778
$this->expectException(TransportExceptionInterface::class);
779779
$response->getContent();
780780
}
781+
782+
public function testMaxDuration()
783+
{
784+
$client = $this->getHttpClient(__FUNCTION__);
785+
$response = $client->request('GET', 'http://localhost:8057/max-duration', [
786+
'max_duration' => 0.1,
787+
]);
788+
789+
$start = microtime(true);
790+
791+
try {
792+
$response->getContent();
793+
} catch (TransportExceptionInterface $e) {
794+
$this->addToAssertionCount(1);
795+
}
796+
797+
$duration = microtime(true) - $start;
798+
799+
$this->assertGreaterThanOrEqual(0.1, $duration);
800+
$this->assertLessThan(0.2, $duration);
801+
}
781802
}

0 commit comments

Comments
 (0)