Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bc7bead

Browse filesBrowse files
committed
[HttpClient] Transfer timeout
1 parent 40fe161 commit bc7bead
Copy full SHA for bc7bead

File tree

10 files changed

+71
-3
lines changed
Filter options

10 files changed

+71
-3
lines changed

‎src/Symfony/Component/HttpClient/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/CHANGELOG.md
+1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/CurlHttpClient.php
+4Lines 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 (0 < $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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/HttpClientTrait.php
+1Lines 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'] : 0;
128129

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

‎src/Symfony/Component/HttpClient/NativeHttpClient.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/NativeHttpClient.php
+17-1Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,12 @@ public function request(string $method, string $url, array $options = []): Respo
113113
if ($onProgress = $options['on_progress']) {
114114
// Memoize the last progress to ease calling the callback periodically when no network transfer happens
115115
$lastProgress = [0, 0];
116-
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info) {
116+
$maxDuration = 0 < $options['max_duration'] ? $options['max_duration'] : INF;
117+
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info, $maxDuration) {
118+
if ($info['total_time'] >= $maxDuration) {
119+
throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url'])));
120+
}
121+
117122
$progressInfo = $info;
118123
$progressInfo['url'] = implode('', $info['url']);
119124
unset($progressInfo['size_body']);
@@ -127,6 +132,13 @@ public function request(string $method, string $url, array $options = []): Respo
127132

128133
$onProgress($lastProgress[0], $lastProgress[1], $progressInfo);
129134
};
135+
} elseif (0 < $options['max_duration']) {
136+
$maxDuration = $options['max_duration'];
137+
$onProgress = static function () use (&$info, $maxDuration): void {
138+
if ($info['total_time'] >= $maxDuration) {
139+
throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url'])));
140+
}
141+
};
130142
}
131143

132144
// Always register a notification callback to compute live stats about the response
@@ -166,6 +178,10 @@ public function request(string $method, string $url, array $options = []): Respo
166178
$options['request_headers'][] = 'user-agent: Symfony HttpClient/Native';
167179
}
168180

181+
if (0 < $options['max_duration']) {
182+
$options['timeout'] = min($options['max_duration'], $options['timeout']);
183+
}
184+
169185
$context = [
170186
'http' => [
171187
'protocol_version' => $options['http_version'] ?: '1.1',

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php
+13Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/composer.json
+1-1Lines 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.6",
2626
"symfony/polyfill-php73": "^1.11"
2727
},
2828
"require-dev": {

‎src/Symfony/Contracts/HttpClient/HttpClientInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/HttpClientInterface.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ 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' => 0, // float - the maximum execution time for the request+response as a whole;
57+
// a value lower than or equal to 0 means it is unlimited
5658
'bindto' => '0', // string - the interface or the local socket to bind to
5759
'verify_peer' => true, // see https://php.net/context.ssl for the following options
5860
'verify_host' => true,

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

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php
+10Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
+21Lines 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
}

‎src/Symfony/Contracts/HttpClient/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"minimum-stability": "dev",
2828
"extra": {
2929
"branch-alias": {
30-
"dev-master": "1.2-dev"
30+
"dev-master": "1.1-dev"
3131
}
3232
}
3333
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.