25
25
*/
26
26
final class CurlResponse implements ResponseInterface
27
27
{
28
- use ResponseTrait;
28
+ use ResponseTrait {
29
+ getContent as private doGetContent;
30
+ }
29
31
30
32
private static $ performing = false ;
31
33
private $ multi ;
@@ -60,7 +62,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
60
62
61
63
if (!$ info ['response_headers ' ]) {
62
64
// Used to keep track of what we're waiting for
63
- curl_setopt ($ ch , CURLOPT_PRIVATE , ' headers ' );
65
+ curl_setopt ($ ch , CURLOPT_PRIVATE , \in_array ( $ method , [ ' GET ' , ' HEAD ' , ' OPTIONS ' , ' TRACE ' ], true ) && 1.0 < ( float ) ( $ options [ ' http_version ' ] ?? 1.1 ) ? ' H2 ' : ' H0 ' ); // H = headers + retry counter
64
66
}
65
67
66
68
if (null === $ content = &$ this ->content ) {
@@ -119,7 +121,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
119
121
120
122
$ waitFor = curl_getinfo ($ ch = $ response ->handle , CURLINFO_PRIVATE );
121
123
122
- if (\in_array ( $ waitFor, [ ' headers ' , ' destruct ' ], true ) ) {
124
+ if (' H ' === $ waitFor[ 0 ] || ' D ' === $ waitFor [ 0 ] ) {
123
125
try {
124
126
foreach (self ::stream ([$ response ]) as $ chunk ) {
125
127
if ($ chunk ->isFirst ()) {
@@ -133,10 +135,6 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
133
135
throw $ e ;
134
136
}
135
137
}
136
-
137
- curl_setopt ($ ch , CURLOPT_HEADERFUNCTION , null );
138
- curl_setopt ($ ch , CURLOPT_READFUNCTION , null );
139
- curl_setopt ($ ch , CURLOPT_INFILE , null );
140
138
};
141
139
142
140
// Schedule the request in a non-blocking way
@@ -150,8 +148,6 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
150
148
public function getInfo (string $ type = null )
151
149
{
152
150
if (!$ info = $ this ->finalInfo ) {
153
- self ::perform ($ this ->multi );
154
-
155
151
$ info = array_merge ($ this ->info , curl_getinfo ($ this ->handle ));
156
152
$ info ['url ' ] = $ this ->info ['url ' ] ?? $ info ['url ' ];
157
153
$ info ['redirect_url ' ] = $ this ->info ['redirect_url ' ] ?? null ;
@@ -164,8 +160,9 @@ public function getInfo(string $type = null)
164
160
165
161
rewind ($ this ->debugBuffer );
166
162
$ info ['debug ' ] = stream_get_contents ($ this ->debugBuffer );
163
+ $ waitFor = curl_getinfo ($ this ->handle , CURLINFO_PRIVATE );
167
164
168
- if (! \in_array ( curl_getinfo ( $ this -> handle , CURLINFO_PRIVATE ), [ ' headers ' , ' content ' ], true ) ) {
165
+ if (' H ' !== $ waitFor [ 0 ] && ' C ' !== $ waitFor [ 0 ] ) {
169
166
curl_setopt ($ this ->handle , CURLOPT_VERBOSE , false );
170
167
rewind ($ this ->debugBuffer );
171
168
ftruncate ($ this ->debugBuffer , 0 );
@@ -176,17 +173,35 @@ public function getInfo(string $type = null)
176
173
return null !== $ type ? $ info [$ type ] ?? null : $ info ;
177
174
}
178
175
176
+ /**
177
+ * {@inheritdoc}
178
+ */
179
+ public function getContent (bool $ throw = true ): string
180
+ {
181
+ $ performing = self ::$ performing ;
182
+ self ::$ performing = $ performing || '_0 ' === curl_getinfo ($ this ->handle , CURLINFO_PRIVATE );
183
+
184
+ try {
185
+ return $ this ->doGetContent ($ throw );
186
+ } finally {
187
+ self ::$ performing = $ performing ;
188
+ }
189
+ }
190
+
179
191
public function __destruct ()
180
192
{
181
193
try {
182
194
if (null === $ this ->timeout ) {
183
195
return ; // Unused pushed response
184
196
}
185
197
186
- if ('content ' === $ waitFor = curl_getinfo ($ this ->handle , CURLINFO_PRIVATE )) {
198
+ $ waitFor = curl_getinfo ($ this ->handle , CURLINFO_PRIVATE );
199
+
200
+ if ('C ' === $ waitFor [0 ] || '_ ' === $ waitFor [0 ]) {
187
201
$ this ->close ();
188
- } elseif ('headers ' === $ waitFor ) {
189
- curl_setopt ($ this ->handle , CURLOPT_PRIVATE , 'destruct ' );
202
+ } elseif ('H ' === $ waitFor [0 ]) {
203
+ $ waitFor [0 ] = 'D ' ; // D = destruct
204
+ curl_setopt ($ this ->handle , CURLOPT_PRIVATE , $ waitFor );
190
205
}
191
206
192
207
$ this ->doDestruct ();
@@ -217,7 +232,7 @@ private function close(): void
217
232
unset($ this ->multi ->openHandles [$ this ->id ], $ this ->multi ->handlesActivity [$ this ->id ]);
218
233
curl_multi_remove_handle ($ this ->multi ->handle , $ this ->handle );
219
234
curl_setopt_array ($ this ->handle , [
220
- CURLOPT_PRIVATE => '' ,
235
+ CURLOPT_PRIVATE => '_0 ' ,
221
236
CURLOPT_NOPROGRESS => true ,
222
237
CURLOPT_PROGRESSFUNCTION => null ,
223
238
CURLOPT_HEADERFUNCTION => null ,
@@ -238,7 +253,7 @@ private static function schedule(self $response, array &$runningResponses): void
238
253
$ runningResponses [$ i ] = [$ response ->multi , [$ response ->id => $ response ]];
239
254
}
240
255
241
- if ('' === curl_getinfo ($ ch = $ response ->handle , CURLINFO_PRIVATE )) {
256
+ if ('_0 ' === curl_getinfo ($ ch = $ response ->handle , CURLINFO_PRIVATE )) {
242
257
// Response already completed
243
258
$ response ->multi ->handlesActivity [$ response ->id ][] = null ;
244
259
$ response ->multi ->handlesActivity [$ response ->id ][] = null !== $ response ->info ['error ' ] ? new TransportException ($ response ->info ['error ' ]) : null ;
@@ -260,8 +275,26 @@ private static function perform(CurlClientState $multi, array &$responses = null
260
275
while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec ($ multi ->handle , $ active ));
261
276
262
277
while ($ info = curl_multi_info_read ($ multi ->handle )) {
263
- $ multi ->handlesActivity [(int ) $ info ['handle ' ]][] = null ;
264
- $ multi ->handlesActivity [(int ) $ info ['handle ' ]][] = \in_array ($ info ['result ' ], [\CURLE_OK , \CURLE_TOO_MANY_REDIRECTS ], true ) || (\CURLE_WRITE_ERROR === $ info ['result ' ] && 'destruct ' === @curl_getinfo ($ info ['handle ' ], CURLINFO_PRIVATE )) ? null : new TransportException (sprintf ('%s for "%s". ' , curl_strerror ($ info ['result ' ]), curl_getinfo ($ info ['handle ' ], CURLINFO_EFFECTIVE_URL )));
278
+ $ result = $ info ['result ' ];
279
+ $ id = (int ) $ ch = $ info ['handle ' ];
280
+ $ waitFor = @curl_getinfo ($ ch , CURLINFO_PRIVATE ) ?: '_0 ' ;
281
+
282
+ if (\in_array ($ result , [\CURLE_SEND_ERROR , \CURLE_RECV_ERROR , /*CURLE_HTTP2*/ 16 , /*CURLE_HTTP2_STREAM*/ 92 ], true ) && $ waitFor [1 ] && 'C ' !== $ waitFor [0 ]) {
283
+ curl_multi_remove_handle ($ multi ->handle , $ ch );
284
+ $ waitFor [1 ] = (string ) ((int ) $ waitFor [1 ] - 1 ); // decrement the retry counter
285
+ curl_setopt ($ ch , CURLOPT_PRIVATE , $ waitFor );
286
+
287
+ if ('1 ' === $ waitFor [1 ]) {
288
+ curl_setopt ($ ch , CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1 );
289
+ }
290
+
291
+ if (0 === curl_multi_add_handle ($ multi ->handle , $ ch )) {
292
+ continue ;
293
+ }
294
+ }
295
+
296
+ $ multi ->handlesActivity [$ id ][] = null ;
297
+ $ multi ->handlesActivity [$ id ][] = \in_array ($ result , [\CURLE_OK , \CURLE_TOO_MANY_REDIRECTS ], true ) || '_0 ' === $ waitFor || curl_getinfo ($ ch , CURLINFO_SIZE_DOWNLOAD ) === curl_getinfo ($ ch , CURLINFO_CONTENT_LENGTH_DOWNLOAD ) ? null : new TransportException (sprintf ('%s for "%s". ' , curl_strerror ($ result ), curl_getinfo ($ ch , CURLINFO_EFFECTIVE_URL )));
265
298
}
266
299
} finally {
267
300
self ::$ performing = false ;
@@ -286,7 +319,9 @@ private static function select(CurlClientState $multi, float $timeout): int
286
319
*/
287
320
private static function parseHeaderLine ($ ch , string $ data , array &$ info , array &$ headers , ?array $ options , CurlClientState $ multi , int $ id , ?string &$ location , ?callable $ resolveRedirect , ?LoggerInterface $ logger ): int
288
321
{
289
- if (!\in_array ($ waitFor = @curl_getinfo ($ ch , CURLINFO_PRIVATE ), ['headers ' , 'destruct ' ], true )) {
322
+ $ waitFor = @curl_getinfo ($ ch , CURLINFO_PRIVATE ) ?: '_0 ' ;
323
+
324
+ if ('H ' !== $ waitFor [0 ] && 'D ' !== $ waitFor [0 ]) {
290
325
return \strlen ($ data ); // Ignore HTTP trailers
291
326
}
292
327
@@ -347,14 +382,18 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
347
382
}
348
383
349
384
if ($ statusCode < 300 || 400 <= $ statusCode || null === $ location || curl_getinfo ($ ch , CURLINFO_REDIRECT_COUNT ) === $ options ['max_redirects ' ]) {
350
- // Headers and redirects completed, time to get the response's body
385
+ // Headers and redirects completed, time to get the response's content
351
386
$ multi ->handlesActivity [$ id ][] = new FirstChunk ();
352
387
353
- if ('destruct ' === $ waitFor ) {
354
- return 0 ;
388
+ if ('D ' === $ waitFor [0 ] || 'HEAD ' === $ info ['http_method ' ] || \in_array ($ statusCode , [204 , 304 ], true )) {
389
+ $ waitFor = '_0 ' ; // no content expected
390
+ $ multi ->handlesActivity [$ id ][] = null ;
391
+ $ multi ->handlesActivity [$ id ][] = null ;
392
+ } else {
393
+ $ waitFor [0 ] = 'C ' ; // C = content
355
394
}
356
395
357
- curl_setopt ($ ch , CURLOPT_PRIVATE , ' content ' );
396
+ curl_setopt ($ ch , CURLOPT_PRIVATE , $ waitFor );
358
397
} elseif (null !== $ info ['redirect_url ' ] && $ logger ) {
359
398
$ logger ->info (sprintf ('Redirecting: "%s %s" ' , $ info ['http_code ' ], $ info ['redirect_url ' ]));
360
399
}
0 commit comments