11
11
12
12
namespace Symfony \Component \HttpClient ;
13
13
14
+ use Psr \Log \LoggerAwareInterface ;
15
+ use Psr \Log \LoggerAwareTrait ;
14
16
use Psr \Log \LoggerInterface ;
15
- use Psr \Log \NullLogger ;
16
17
use Symfony \Component \HttpClient \Exception \TransportException ;
17
18
use Symfony \Component \HttpClient \Response \CurlResponse ;
18
19
use Symfony \Component \HttpClient \Response \ResponseStream ;
30
31
*
31
32
* @experimental in 4.3
32
33
*/
33
- final class CurlHttpClient implements HttpClientInterface
34
+ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
34
35
{
35
36
use HttpClientTrait;
37
+ use LoggerAwareTrait;
36
38
37
39
private $ defaultOptions = self ::OPTIONS_DEFAULTS ;
38
40
private $ multi ;
39
- private $ logger ;
40
41
41
42
/**
42
43
* @param array $defaultOptions Default requests' options
43
44
* @param int $maxHostConnections The maximum number of connections to a single host
44
45
*
45
46
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
46
47
*/
47
- public function __construct (array $ defaultOptions = [], LoggerInterface $ logger = null , int $ maxHostConnections = 6 )
48
+ public function __construct (array $ defaultOptions = [], int $ maxHostConnections = 6 )
48
49
{
49
- $ this ->logger = $ logger ?? new NullLogger ();
50
-
51
50
if ($ defaultOptions ) {
52
51
[, $ this ->defaultOptions ] = self ::prepareRequest (null , null , $ defaultOptions , self ::OPTIONS_DEFAULTS );
53
52
}
@@ -79,8 +78,10 @@ public function __construct(array $defaultOptions = [], LoggerInterface $logger
79
78
return ;
80
79
}
81
80
82
- curl_multi_setopt ($ mh , CURLMOPT_PUSHFUNCTION , static function ($ parent , $ pushed , array $ requestHeaders ) use ($ multi ) {
83
- return self ::handlePush ($ parent , $ pushed , $ requestHeaders , $ multi );
81
+ $ logger = &$ this ->logger ;
82
+
83
+ curl_multi_setopt ($ mh , CURLMOPT_PUSHFUNCTION , static function ($ parent , $ pushed , array $ requestHeaders ) use ($ multi , &$ logger ) {
84
+ return self ::handlePush ($ parent , $ pushed , $ requestHeaders , $ multi , $ logger );
84
85
});
85
86
}
86
87
@@ -91,7 +92,6 @@ public function __construct(array $defaultOptions = [], LoggerInterface $logger
91
92
*/
92
93
public function request (string $ method , string $ url , array $ options = []): ResponseInterface
93
94
{
94
- $ this ->logger ->notice ('Making a request ' , ['url ' => $ url , 'method ' => $ method , 'client ' => static ::class]);
95
95
[$ url , $ options ] = self ::prepareRequest ($ method , $ url , $ options , $ this ->defaultOptions );
96
96
$ scheme = $ url ['scheme ' ];
97
97
$ authority = $ url ['authority ' ];
@@ -109,14 +109,19 @@ public function request(string $method, string $url, array $options = []): Respo
109
109
];
110
110
111
111
if ('GET ' === $ method && !$ options ['body ' ] && $ expectedHeaders === $ pushedHeaders ) {
112
- $ this ->logger ->debug ('Creating pushed response ' );
112
+ $ this ->logger && $ this ->logger ->info (sprintf ('Connecting request to pushed response: %s %s ' , $ method , $ url ));
113
+
113
114
// Reinitialize the pushed response with request's options
114
- $ pushedResponse ->__construct ($ this ->multi , $ url , $ options );
115
+ $ pushedResponse ->__construct ($ this ->multi , $ url , $ options, $ this -> logger );
115
116
116
117
return $ pushedResponse ;
117
118
}
119
+
120
+ $ this ->logger && $ this ->logger ->info (sprintf ('Rejecting pushed response for "%s": authorization headers don \'t match the request ' , $ url ));
118
121
}
119
122
123
+ $ this ->logger && $ this ->logger ->info (sprintf ('Request: %s %s ' , $ method , $ url ));
124
+
120
125
$ curlopts = [
121
126
CURLOPT_URL => $ url ,
122
127
CURLOPT_USERAGENT => 'Symfony HttpClient/Curl ' ,
@@ -163,7 +168,7 @@ public function request(string $method, string $url, array $options = []): Respo
163
168
// DNS cache removals require curl 7.42 or higher
164
169
// On lower versions, we have to create a new multi handle
165
170
curl_multi_close ($ this ->multi ->handle );
166
- $ this ->multi ->handle = (new self ([], $ this -> logger ))->multi ->handle ;
171
+ $ this ->multi ->handle = (new self ())->multi ->handle ;
167
172
}
168
173
169
174
foreach ($ options ['resolve ' ] as $ host => $ ip ) {
@@ -262,7 +267,7 @@ public function request(string $method, string $url, array $options = []): Respo
262
267
}
263
268
}
264
269
265
- return new CurlResponse ($ this ->multi , $ ch , $ options , $ method , self ::createRedirectResolver ($ options , $ host ));
270
+ return new CurlResponse ($ this ->multi , $ ch , $ options , $ this -> logger , $ method , self ::createRedirectResolver ($ options , $ host ));
266
271
}
267
272
268
273
/**
@@ -289,17 +294,20 @@ public function __destruct()
289
294
}
290
295
}
291
296
292
- private static function handlePush ($ parent , $ pushed , array $ requestHeaders , \stdClass $ multi ): int
297
+ private static function handlePush ($ parent , $ pushed , array $ requestHeaders , \stdClass $ multi, ? LoggerInterface $ logger ): int
293
298
{
294
299
$ headers = [];
300
+ $ origin = curl_getinfo ($ parent , CURLINFO_EFFECTIVE_URL );
295
301
296
302
foreach ($ requestHeaders as $ h ) {
297
303
if (false !== $ i = strpos ($ h , ': ' , 1 )) {
298
304
$ headers [substr ($ h , 0 , $ i )] = substr ($ h , 1 + $ i );
299
305
}
300
306
}
301
307
302
- if ('GET ' !== $ headers [':method ' ] || isset ($ headers ['range ' ])) {
308
+ if (!isset ($ headers [':method ' ]) || !isset ($ headers [':scheme ' ]) || !isset ($ headers [':authority ' ]) || !isset ($ headers [':path ' ]) || 'GET ' !== $ headers [':method ' ] || isset ($ headers ['range ' ])) {
309
+ $ logger && $ logger ->info (sprintf ('Rejecting pushed response from "%s": pushed headers are invalid ' , $ origin ));
310
+
303
311
return CURL_PUSH_DENY ;
304
312
}
305
313
@@ -308,11 +316,16 @@ private static function handlePush($parent, $pushed, array $requestHeaders, \std
308
316
// curl before 7.65 doesn't validate the pushed ":authority" header,
309
317
// but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host,
310
318
// ignoring domains mentioned as alt-name in the certificate for now (same as curl).
311
- if (0 !== strpos (curl_getinfo ($ parent , CURLINFO_EFFECTIVE_URL ), $ url .'/ ' )) {
319
+ if (0 !== strpos ($ origin , $ url .'/ ' )) {
320
+ $ logger && $ logger ->info (sprintf ('Rejecting pushed response from "%s": server is not authoritative for "%s" ' , $ origin , $ url ));
321
+
312
322
return CURL_PUSH_DENY ;
313
323
}
314
324
315
- $ multi ->pushedResponses [$ url .$ headers [':path ' ]] = [
325
+ $ url .= $ headers [':path ' ];
326
+ $ logger && $ logger ->info (sprintf ('Queueing pushed response: %s ' , $ url ));
327
+
328
+ $ multi ->pushedResponses [$ url ] = [
316
329
new CurlResponse ($ multi , $ pushed ),
317
330
[
318
331
$ headers ['authorization ' ] ?? null ,
0 commit comments