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 28d6033

Browse filesBrowse files
committed
[HttpClient] Transfer timeout
1 parent a29aff0 commit 28d6033
Copy full SHA for 28d6033

File tree

7 files changed

+77
-31
lines changed
Filter options

7 files changed

+77
-31
lines changed

‎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 (null !== $options['transfer_timeout']) {
286+
$curlopts[CURLOPT_TIMEOUT_MS] = 1000 * $options['transfer_timeout'];
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['transfer_timeout'] = isset($options['transfer_timeout']) ? (float) $options['transfer_timeout'] : null;
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
+11-1Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,16 @@ public function request(string $method, string $url, array $options = []): Respo
129129
};
130130
}
131131

132+
$transferTimeout = $options['transfer_timeout'] ?: INF;
133+
132134
// 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) {
135+
$notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info, $transferTimeout) {
134136
$info['total_time'] = microtime(true) - $info['start_time'];
135137

138+
if ($info['total_time'] >= $transferTimeout) {
139+
throw new TransportException(sprintf('Timeout was reached for "%s".', implode('', $info['url'])));
140+
}
141+
136142
if (STREAM_NOTIFY_PROGRESS === $code) {
137143
$info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time'];
138144
$info['size_upload'] += $dlNow ? 0 : $info['size_body'];
@@ -166,6 +172,10 @@ public function request(string $method, string $url, array $options = []): Respo
166172
$options['request_headers'][] = 'user-agent: Symfony HttpClient/Native';
167173
}
168174

175+
if (null !== $options['transfer_timeout']) {
176+
$options['timeout'] = min($options['transfer_timeout'], $options['timeout']);
177+
}
178+
169179
$context = [
170180
'http' => [
171181
'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
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,15 @@ protected function getHttpClient(string $testCase): HttpClientInterface
123123
$body = ['<1>', '', '<2>'];
124124
$responses[] = new MockResponse($body, ['response_headers' => $headers]);
125125
break;
126+
127+
case 'testTransferTimeout':
128+
$mock = $this->getMockBuilder(ResponseInterface::class)->getMock();
129+
$mock->expects($this->any())
130+
->method('getContent')
131+
->willThrowException(new TransportException('Timeout was reached.'));
132+
133+
$responses[] = $mock;
134+
break;
126135
}
127136

128137
return new MockHttpClient($responses);

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

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/HttpClientInterface.php
+31-30Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,35 +26,36 @@
2626
interface HttpClientInterface
2727
{
2828
public const OPTIONS_DEFAULTS = [
29-
'auth_basic' => null, // array|string - an array containing the username as first value, and optionally the
30-
// password as the second one; or string like username:password - enabling HTTP Basic
31-
// authentication (RFC 7617)
32-
'auth_bearer' => null, // string - a token enabling HTTP Bearer authorization (RFC 6750)
33-
'query' => [], // string[] - associative array of query string values to merge with the request's URL
34-
'headers' => [], // iterable|string[]|string[][] - headers names provided as keys or as part of values
35-
'body' => '', // array|string|resource|\Traversable|\Closure - the callback SHOULD yield a string
36-
// smaller than the amount requested as argument; the empty string signals EOF; when
37-
// an array is passed, it is meant as a form payload of field names and values
38-
'json' => null, // array|\JsonSerializable - when set, implementations MUST set the "body" option to
39-
// the JSON-encoded value and set the "content-type" headers to a JSON-compatible
40-
// value if they are not defined - typically "application/json"
41-
'user_data' => null, // mixed - any extra data to attach to the request (scalar, callable, object...) that
42-
// MUST be available via $response->getInfo('user_data') - not used internally
43-
'max_redirects' => 20, // int - the maximum number of redirects to follow; a value lower or equal to 0 means
44-
// redirects should not be followed; "Authorization" and "Cookie" headers MUST
45-
// NOT follow except for the initial host name
46-
'http_version' => null, // string - defaults to the best supported version, typically 1.1 or 2.0
47-
'base_uri' => null, // string - the URI to resolve relative URLs, following rules in RFC 3986, section 2
48-
'buffer' => true, // bool - whether the content of the response should be buffered or not
49-
'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abort
50-
// the request; it MUST be called on DNS resolution, on arrival of headers and on
51-
// completion; it SHOULD be called on upload/download of data and at least 1/s
52-
'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution
53-
'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored
54-
'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached
55-
'timeout' => null, // float - the inactivity timeout - defaults to ini_get('default_socket_timeout')
56-
'bindto' => '0', // string - the interface or the local socket to bind to
57-
'verify_peer' => true, // see https://php.net/context.ssl for the following options
29+
'auth_basic' => null, // array|string - an array containing the username as first value, and optionally the
30+
// password as the second one; or string like username:password - enabling HTTP Basic
31+
// authentication (RFC 7617)
32+
'auth_bearer' => null, // string - a token enabling HTTP Bearer authorization (RFC 6750)
33+
'query' => [], // string[] - associative array of query string values to merge with the request's URL
34+
'headers' => [], // iterable|string[]|string[][] - headers names provided as keys or as part of values
35+
'body' => '', // array|string|resource|\Traversable|\Closure - the callback SHOULD yield a string
36+
// smaller than the amount requested as argument; the empty string signals EOF; when
37+
// an array is passed, it is meant as a form payload of field names and values
38+
'json' => null, // array|\JsonSerializable - when set, implementations MUST set the "body" option to
39+
// the JSON-encoded value and set the "content-type" headers to a JSON-compatible
40+
// value if they are not defined - typically "application/json"
41+
'user_data' => null, // mixed - any extra data to attach to the request (scalar, callable, object...) that
42+
// MUST be available via $response->getInfo('user_data') - not used internally
43+
'max_redirects' => 20, // int - the maximum number of redirects to follow; a value lower or equal to 0 means
44+
// redirects should not be followed; "Authorization" and "Cookie" headers MUST
45+
// NOT follow except for the initial host name
46+
'http_version' => null, // string - defaults to the best supported version, typically 1.1 or 2.0
47+
'base_uri' => null, // string - the URI to resolve relative URLs, following rules in RFC 3986, section 2
48+
'buffer' => true, // bool - whether the content of the response should be buffered or not
49+
'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abort
50+
// the request; it MUST be called on DNS resolution, on arrival of headers and on
51+
// completion; it SHOULD be called on upload/download of data and at least 1/s
52+
'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution
53+
'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored
54+
'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached
55+
'timeout' => null, // float - the inactivity timeout - defaults to ini_get('default_socket_timeout')
56+
'transfer_timeout' => null, // float|null - the total transfer timeout (including the inactivity timeout)
57+
'bindto' => '0', // string - the interface or the local socket to bind to
58+
'verify_peer' => true, // see https://php.net/context.ssl for the following options
5859
'verify_host' => true,
5960
'cafile' => null,
6061
'capath' => null,
@@ -64,7 +65,7 @@ interface HttpClientInterface
6465
'ciphers' => null,
6566
'peer_fingerprint' => null,
6667
'capture_peer_cert_chain' => false,
67-
'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular options
68+
'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular options
6869
];
6970

7071
/**

‎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 '/transfer-timeout':
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
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,4 +778,15 @@ public function testGzipBroken()
778778
$this->expectException(TransportExceptionInterface::class);
779779
$response->getContent();
780780
}
781+
782+
public function testTransferTimeout()
783+
{
784+
$client = $this->getHttpClient(__FUNCTION__);
785+
$response = $client->request('GET', 'http://localhost:8057/transfer-timeout', [
786+
'transfer_timeout' => 0.1,
787+
]);
788+
789+
$this->expectException(TransportExceptionInterface::class);
790+
$response->getContent();
791+
}
781792
}

0 commit comments

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