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

[HttpClient] add "max_duration" option #32807

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,9 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode)
->floatNode('timeout')
->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.')
->end()
->floatNode('max_duration')
->info('The maximum execution time for the request+response as a whole.')
->end()
->scalarNode('bindto')
->info('A network interface name, IP address, a host name or a UNIX socket to bind to.')
->end()
Expand Down Expand Up @@ -1503,6 +1506,9 @@ private function addHttpClientSection(ArrayNodeDefinition $rootNode)
->floatNode('timeout')
->info('Defaults to "default_socket_timeout" ini parameter.')
->end()
->floatNode('max_duration')
->info('The maximum execution time for the request+response as a whole.')
->end()
->scalarNode('bindto')
->info('A network interface name, IP address, a host name or a UNIX socket to bind to.')
->end()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@
<xsd:attribute name="proxy" type="xsd:string" />
<xsd:attribute name="no-proxy" type="xsd:string" />
<xsd:attribute name="timeout" type="xsd:float" />
<xsd:attribute name="max-duration" type="xsd:float" />
<xsd:attribute name="bindto" type="xsd:string" />
<xsd:attribute name="verify-peer" type="xsd:boolean" />
<xsd:attribute name="verify-host" type="xsd:boolean" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
'resolve' => ['localhost' => '127.0.0.1'],
'proxy' => 'proxy.org',
'timeout' => 3.5,
'max_duration' => 10.1,
'bindto' => '127.0.0.1',
'verify_peer' => true,
'verify_host' => true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
proxy="proxy.org"
bindto="127.0.0.1"
timeout="3.5"
max-duration="10.1"
verify-peer="true"
max-redirects="2"
http-version="2.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ framework:
resolve: {'localhost': '127.0.0.1'}
proxy: proxy.org
timeout: 3.5
max_duration: 10.1
bindto: 127.0.0.1
verify_peer: true
verify_host: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,7 @@ public function testHttpClientFullDefaultOptions()
$this->assertSame(['localhost' => '127.0.0.1'], $defaultOptions['resolve']);
$this->assertSame('proxy.org', $defaultOptions['proxy']);
$this->assertSame(3.5, $defaultOptions['timeout']);
$this->assertSame(10.1, $defaultOptions['max_duration']);
$this->assertSame('127.0.0.1', $defaultOptions['bindto']);
$this->assertTrue($defaultOptions['verify_peer']);
$this->assertTrue($defaultOptions['verify_host']);
Expand Down
1 change: 1 addition & 0 deletions 1 src/Symfony/Component/HttpClient/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CHANGELOG
* added support for NTLM authentication
* added `$response->toStream()` to cast responses to regular PHP streams
* made `Psr18Client` implement relevant PSR-17 factories and have streaming responses
* added `max_duration` option

4.3.0
-----
Expand Down
4 changes: 4 additions & 0 deletions 4 src/Symfony/Component/HttpClient/CurlHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,10 @@ public function request(string $method, string $url, array $options = []): Respo
$curlopts[file_exists($options['bindto']) ? CURLOPT_UNIX_SOCKET_PATH : CURLOPT_INTERFACE] = $options['bindto'];
}

if (0 < $options['max_duration']) {
$curlopts[CURLOPT_TIMEOUT_MS] = 1000 * $options['max_duration'];
}

$ch = curl_init();

foreach ($curlopts as $opt => $value) {
Expand Down
1 change: 1 addition & 0 deletions 1 src/Symfony/Component/HttpClient/HttpClientTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ private static function prepareRequest(?string $method, ?string $url, array $opt
// Finalize normalization of options
$options['http_version'] = (string) ($options['http_version'] ?? '') ?: null;
$options['timeout'] = (float) ($options['timeout'] ?? ini_get('default_socket_timeout'));
$options['max_duration'] = isset($options['max_duration']) ? (float) $options['max_duration'] : 0;

return [$url, $options];
}
Expand Down
18 changes: 17 additions & 1 deletion 18 src/Symfony/Component/HttpClient/NativeHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ public function request(string $method, string $url, array $options = []): Respo
if ($onProgress = $options['on_progress']) {
// Memoize the last progress to ease calling the callback periodically when no network transfer happens
$lastProgress = [0, 0];
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info) {
$maxDuration = 0 < $options['max_duration'] ? $options['max_duration'] : INF;
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info, $maxDuration) {
if ($info['total_time'] >= $maxDuration) {
throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url'])));
}

$progressInfo = $info;
$progressInfo['url'] = implode('', $info['url']);
unset($progressInfo['size_body']);
Expand All @@ -127,6 +132,13 @@ public function request(string $method, string $url, array $options = []): Respo

$onProgress($lastProgress[0], $lastProgress[1], $progressInfo);
};
} elseif (0 < $options['max_duration']) {
$maxDuration = $options['max_duration'];
$onProgress = static function () use (&$info, $maxDuration): void {
if ($info['total_time'] >= $maxDuration) {
throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url'])));
}
};
}

// Always register a notification callback to compute live stats about the response
Expand Down Expand Up @@ -166,6 +178,10 @@ public function request(string $method, string $url, array $options = []): Respo
$options['headers'][] = 'User-Agent: Symfony HttpClient/Native';
}

if (0 < $options['max_duration']) {
$options['timeout'] = min($options['max_duration'], $options['timeout']);
}

$context = [
'http' => [
'protocol_version' => $options['http_version'] ?: '1.1',
Expand Down
13 changes: 13 additions & 0 deletions 13 src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ protected function getHttpClient(string $testCase): HttpClientInterface
$body = ['<1>', '', '<2>'];
$responses[] = new MockResponse($body, ['response_headers' => $headers]);
break;

case 'testMaxDuration':
$mock = $this->getMockBuilder(ResponseInterface::class)->getMock();
$mock->expects($this->any())
->method('getContent')
->willReturnCallback(static function (): void {
usleep(100000);

throw new TransportException('Max duration was reached.');
});

$responses[] = $mock;
break;
}

return new MockHttpClient($responses);
Expand Down
2 changes: 1 addition & 1 deletion 2 src/Symfony/Component/HttpClient/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"require": {
"php": "^7.1.3",
"psr/log": "^1.0",
"symfony/http-client-contracts": "^1.1.4",
"symfony/http-client-contracts": "^1.1.6",
"symfony/polyfill-php73": "^1.11"
},
"require-dev": {
Expand Down
2 changes: 2 additions & 0 deletions 2 src/Symfony/Contracts/HttpClient/HttpClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ interface HttpClientInterface
'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored
'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached
'timeout' => null, // float - the idle timeout - defaults to ini_get('default_socket_timeout')
'max_duration' => 0, // float - the maximum execution time for the request+response as a whole;
// a value lower than or equal to 0 means it is unlimited
'bindto' => '0', // string - the interface or the local socket to bind to
'verify_peer' => true, // see https://php.net/context.ssl for the following options
'verify_host' => true,
Expand Down
10 changes: 10 additions & 0 deletions 10 src/Symfony/Contracts/HttpClient/Test/Fixtures/web/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@
header('Content-Encoding: gzip');
echo str_repeat('-', 1000);
exit;

case '/max-duration':
ignore_user_abort(false);
while (true) {
echo '<1>';
@ob_flush();
flush();
usleep(500);
}
exit;
}

header('Content-Type: application/json', true);
Expand Down
21 changes: 21 additions & 0 deletions 21 src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -778,4 +778,25 @@ public function testGzipBroken()
$this->expectException(TransportExceptionInterface::class);
$response->getContent();
}

public function testMaxDuration()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/max-duration', [
'max_duration' => 0.1,
]);

$start = microtime(true);

try {
$response->getContent();
} catch (TransportExceptionInterface $e) {
$this->addToAssertionCount(1);
}

$duration = microtime(true) - $start;

$this->assertGreaterThanOrEqual(0.1, $duration);
$this->assertLessThan(0.2, $duration);
}
}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.