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 2d20fdd

Browse filesBrowse files
[HttpClient] improve handling of HTTP/2 PUSH
1 parent d26a656 commit 2d20fdd
Copy full SHA for 2d20fdd

File tree

4 files changed

+49
-42
lines changed
Filter options

4 files changed

+49
-42
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/CurlHttpClient.php
+37-27Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -105,35 +105,29 @@ public function request(string $method, string $url, array $options = []): Respo
105105
$host = parse_url($authority, PHP_URL_HOST);
106106
$url = implode('', $url);
107107

108+
if (!isset($options['normalized_headers']['user-agent'])) {
109+
$options['headers'][] = 'User-Agent: '.$options['normalized_headers']['user-agent'][] = 'Symfony HttpClient/Curl';
110+
}
111+
108112
if ($pushedResponse = $this->multi->pushedResponses[$url] ?? null) {
109113
unset($this->multi->pushedResponses[$url]);
110-
// Accept pushed responses only if their headers related to authentication match the request
111-
$expectedHeaders = ['authorization', 'cookie', 'x-requested-with', 'range'];
112-
foreach ($expectedHeaders as $k => $v) {
113-
$expectedHeaders[$k] = null;
114-
115-
foreach ($options['normalized_headers'][$v] ?? [] as $h) {
116-
$expectedHeaders[$k][] = substr($h, 2 + \strlen($v));
117-
}
118-
}
119114

120-
if ('GET' === $method && $expectedHeaders === $pushedResponse->headers && !$options['body']) {
121-
$this->logger && $this->logger->debug(sprintf('Connecting request to pushed response: "%s %s"', $method, $url));
115+
if (self::acceptPushForRequest($method, $options, $pushedResponse)) {
116+
$this->logger && $this->logger->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url));
122117

123118
// Reinitialize the pushed response with request's options
124119
$pushedResponse->response->__construct($this->multi, $url, $options, $this->logger);
125120

126121
return $pushedResponse->response;
127122
}
128123

129-
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response for "%s": authorization headers don\'t match the request', $url));
124+
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s".', $url));
130125
}
131126

132127
$this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url));
133128

134129
$curlopts = [
135130
CURLOPT_URL => $url,
136-
CURLOPT_USERAGENT => 'Symfony HttpClient/Curl',
137131
CURLOPT_TCP_NODELAY => true,
138132
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
139133
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
@@ -306,7 +300,7 @@ public function __destruct()
306300
$active = 0;
307301
while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active));
308302

309-
foreach ($this->multi->openHandles as $ch) {
303+
foreach ($this->multi->openHandles as [$ch]) {
310304
curl_setopt($ch, CURLOPT_VERBOSE, false);
311305
}
312306
}
@@ -318,17 +312,17 @@ private static function handlePush($parent, $pushed, array $requestHeaders, Curl
318312

319313
foreach ($requestHeaders as $h) {
320314
if (false !== $i = strpos($h, ':', 1)) {
321-
$headers[substr($h, 0, $i)] = substr($h, 1 + $i);
315+
$headers[substr($h, 0, $i)][] = substr($h, 1 + $i);
322316
}
323317
}
324318

325-
if (!isset($headers[':method']) || !isset($headers[':scheme']) || !isset($headers[':authority']) || !isset($headers[':path']) || 'GET' !== $headers[':method'] || isset($headers['range'])) {
319+
if (!isset($headers[':method']) || !isset($headers[':scheme']) || !isset($headers[':authority']) || !isset($headers[':path'])) {
326320
$logger && $logger->debug(sprintf('Rejecting pushed response from "%s": pushed headers are invalid', $origin));
327321

328322
return CURL_PUSH_DENY;
329323
}
330324

331-
$url = $headers[':scheme'].'://'.$headers[':authority'];
325+
$url = $headers[':scheme'][0].'://'.$headers[':authority'][0];
332326

333327
if ($maxPendingPushes <= \count($multi->pushedResponses)) {
334328
$logger && $logger->debug(sprintf('Rejecting pushed response from "%s" for "%s": the queue is full', $origin, $url));
@@ -345,22 +339,38 @@ private static function handlePush($parent, $pushed, array $requestHeaders, Curl
345339
return CURL_PUSH_DENY;
346340
}
347341

348-
$url .= $headers[':path'];
342+
$url .= $headers[':path'][0];
349343
$logger && $logger->debug(sprintf('Queueing pushed response: "%s"', $url));
350344

351-
$multi->pushedResponses[$url] = new PushedResponse(
352-
new CurlResponse($multi, $pushed),
353-
[
354-
$headers['authorization'] ?? null,
355-
$headers['cookie'] ?? null,
356-
$headers['x-requested-with'] ?? null,
357-
null,
358-
]
359-
);
345+
$multi->pushedResponses[$url] = new PushedResponse(new CurlResponse($multi, $pushed), $headers, $multi->openHandles[(int) $parent][1] ?? []);
360346

361347
return CURL_PUSH_OK;
362348
}
363349

350+
/**
351+
* Accepts pushed responses only if their headers related to authentication match the request.
352+
*/
353+
private static function acceptPushForRequest(string $method, array $options, PushedResponse $pushedResponse): bool
354+
{
355+
if ($options['body'] || $method !== $pushedResponse->headers[':method'][0]) {
356+
return false;
357+
}
358+
359+
foreach (['proxy', 'no_proxy', 'bindto'] as $k) {
360+
if ($options[$k] !== $pushedResponse->parentOptions[$k]) {
361+
return false;
362+
}
363+
}
364+
365+
foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) {
366+
if (($pushedResponse->headers[$k] ?? null) !== ($options['normalized_headers'][$k] ?? null)) {
367+
return false;
368+
}
369+
}
370+
371+
return true;
372+
}
373+
364374
/**
365375
* Wraps the request's body callback to allow it to return strings longer than curl requested.
366376
*/

‎src/Symfony/Component/HttpClient/Internal/PushedResponse.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Internal/PushedResponse.php
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@
2222
*/
2323
final class PushedResponse
2424
{
25-
/** @var CurlResponse */
2625
public $response;
2726

2827
/** @var string[] */
2928
public $headers;
3029

31-
public function __construct(CurlResponse $response, array $headers)
30+
public $parentOptions = [];
31+
32+
public function __construct(CurlResponse $response, array $headers, array $parentOptions)
3233
{
3334
$this->response = $response;
3435
$this->headers = $headers;
36+
$this->parentOptions = $parentOptions;
3537
}
3638
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Response/CurlResponse.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
140140
};
141141

142142
// Schedule the request in a non-blocking way
143-
$multi->openHandles[$id] = $ch;
143+
$multi->openHandles[$id] = [$ch, $options];
144144
curl_multi_add_handle($multi->handle, $ch);
145145
self::perform($multi);
146146
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Tests/CurlHttpClientTest.php
+7-12Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,19 @@ public function log($level, $message, array $context = [])
5050
$client = new CurlHttpClient();
5151
$client->setLogger($logger);
5252

53-
$index = $client->request('GET', 'https://http2-push.io');
53+
$index = $client->request('GET', 'https://http2.akamai.com/');
5454
$index->getContent();
5555

56-
$css = $client->request('GET', 'https://http2-push.io/css/style.css');
57-
$js = $client->request('GET', 'https://http2-push.io/js/http2-push.js');
56+
$css = $client->request('GET', 'https://http2.akamai.com/resources/push.css');
5857

5958
$css->getHeaders();
60-
$js->getHeaders();
6159

6260
$expected = [
63-
'Request: "GET https://http2-push.io/"',
64-
'Queueing pushed response: "https://http2-push.io/css/style.css"',
65-
'Queueing pushed response: "https://http2-push.io/js/http2-push.js"',
66-
'Response: "200 https://http2-push.io/"',
67-
'Connecting request to pushed response: "GET https://http2-push.io/css/style.css"',
68-
'Connecting request to pushed response: "GET https://http2-push.io/js/http2-push.js"',
69-
'Response: "200 https://http2-push.io/css/style.css"',
70-
'Response: "200 https://http2-push.io/js/http2-push.js"',
61+
'Request: "GET https://http2.akamai.com/"',
62+
'Queueing pushed response: "https://http2.akamai.com/resources/push.css"',
63+
'Response: "200 https://http2.akamai.com/"',
64+
'Accepting pushed response: "GET https://http2.akamai.com/resources/push.css"',
65+
'Response: "200 https://http2.akamai.com/resources/push.css"',
7166
];
7267
$this->assertSame($expected, $logger->logs);
7368
}

0 commit comments

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