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 ) {
@@ -139,7 +141,7 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
139
141
140
142
$ waitFor = curl_getinfo ($ ch = $ response ->handle , CURLINFO_PRIVATE );
141
143
142
- if (\in_array ( $ waitFor, [ ' headers ' , ' destruct ' ], true ) ) {
144
+ if (' H ' === $ waitFor[ 0 ] || ' D ' === $ waitFor [ 0 ] ) {
143
145
try {
144
146
foreach (self ::stream ([$ response ]) as $ chunk ) {
145
147
if ($ chunk ->isFirst ()) {
@@ -153,16 +155,11 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
153
155
throw $ e ;
154
156
}
155
157
}
156
-
157
- curl_setopt ($ ch , CURLOPT_HEADERFUNCTION , null );
158
- curl_setopt ($ ch , CURLOPT_READFUNCTION , null );
159
- curl_setopt ($ ch , CURLOPT_INFILE , null );
160
158
};
161
159
162
160
// Schedule the request in a non-blocking way
163
161
$ multi ->openHandles [$ id ] = [$ ch , $ options ];
164
162
curl_multi_add_handle ($ multi ->handle , $ ch );
165
- self ::perform ($ multi );
166
163
}
167
164
168
165
/**
@@ -171,8 +168,6 @@ public function __construct(CurlClientState $multi, $ch, array $options = null,
171
168
public function getInfo (string $ type = null )
172
169
{
173
170
if (!$ info = $ this ->finalInfo ) {
174
- self ::perform ($ this ->multi );
175
-
176
171
$ info = array_merge ($ this ->info , curl_getinfo ($ this ->handle ));
177
172
$ info ['url ' ] = $ this ->info ['url ' ] ?? $ info ['url ' ];
178
173
$ info ['redirect_url ' ] = $ this ->info ['redirect_url ' ] ?? null ;
@@ -185,8 +180,9 @@ public function getInfo(string $type = null)
185
180
186
181
rewind ($ this ->debugBuffer );
187
182
$ info ['debug ' ] = stream_get_contents ($ this ->debugBuffer );
183
+ $ waitFor = curl_getinfo ($ this ->handle , CURLINFO_PRIVATE );
188
184
189
- if (! \in_array ( curl_getinfo ( $ this -> handle , CURLINFO_PRIVATE ), [ ' headers ' , ' content ' ], true ) ) {
185
+ if (' H ' !== $ waitFor [ 0 ] && ' C ' !== $ waitFor [ 0 ] ) {
190
186
curl_setopt ($ this ->handle , CURLOPT_VERBOSE , false );
191
187
rewind ($ this ->debugBuffer );
192
188
ftruncate ($ this ->debugBuffer , 0 );
@@ -197,17 +193,35 @@ public function getInfo(string $type = null)
197
193
return null !== $ type ? $ info [$ type ] ?? null : $ info ;
198
194
}
199
195
196
+ /**
197
+ * {@inheritdoc}
198
+ */
199
+ public function getContent (bool $ throw = true ): string
200
+ {
201
+ $ performing = self ::$ performing ;
202
+ self ::$ performing = $ performing || '_0 ' === curl_getinfo ($ this ->handle , CURLINFO_PRIVATE );
203
+
204
+ try {
205
+ return $ this ->doGetContent ($ throw );
206
+ } finally {
207
+ self ::$ performing = $ performing ;
208
+ }
209
+ }
210
+
200
211
public function __destruct ()
201
212
{
202
213
try {
203
214
if (null === $ this ->timeout ) {
204
215
return ; // Unused pushed response
205
216
}
206
217
207
- if ('content ' === $ waitFor = curl_getinfo ($ this ->handle , CURLINFO_PRIVATE )) {
218
+ $ waitFor = curl_getinfo ($ this ->handle , CURLINFO_PRIVATE );
219
+
220
+ if ('C ' === $ waitFor [0 ] || '_ ' === $ waitFor [0 ]) {
208
221
$ this ->close ();
209
- } elseif ('headers ' === $ waitFor ) {
210
- curl_setopt ($ this ->handle , CURLOPT_PRIVATE , 'destruct ' );
222
+ } elseif ('H ' === $ waitFor [0 ]) {
223
+ $ waitFor [0 ] = 'D ' ; // D = destruct
224
+ curl_setopt ($ this ->handle , CURLOPT_PRIVATE , $ waitFor );
211
225
}
212
226
213
227
$ this ->doDestruct ();
@@ -245,7 +259,7 @@ private function close(): void
245
259
unset($ this ->multi ->openHandles [$ this ->id ], $ this ->multi ->handlesActivity [$ this ->id ]);
246
260
curl_multi_remove_handle ($ this ->multi ->handle , $ this ->handle );
247
261
curl_setopt_array ($ this ->handle , [
248
- CURLOPT_PRIVATE => '' ,
262
+ CURLOPT_PRIVATE => '_0 ' ,
249
263
CURLOPT_NOPROGRESS => true ,
250
264
CURLOPT_PROGRESSFUNCTION => null ,
251
265
CURLOPT_HEADERFUNCTION => null ,
@@ -266,7 +280,7 @@ private static function schedule(self $response, array &$runningResponses): void
266
280
$ runningResponses [$ i ] = [$ response ->multi , [$ response ->id => $ response ]];
267
281
}
268
282
269
- if ('' === curl_getinfo ($ ch = $ response ->handle , CURLINFO_PRIVATE )) {
283
+ if ('_0 ' === curl_getinfo ($ ch = $ response ->handle , CURLINFO_PRIVATE )) {
270
284
// Response already completed
271
285
$ response ->multi ->handlesActivity [$ response ->id ][] = null ;
272
286
$ response ->multi ->handlesActivity [$ response ->id ][] = null !== $ response ->info ['error ' ] ? new TransportException ($ response ->info ['error ' ]) : null ;
@@ -294,8 +308,26 @@ private static function perform(CurlClientState $multi, array &$responses = null
294
308
while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec ($ multi ->handle , $ active ));
295
309
296
310
while ($ info = curl_multi_info_read ($ multi ->handle )) {
297
- $ multi ->handlesActivity [(int ) $ info ['handle ' ]][] = null ;
298
- $ 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 )));
311
+ $ result = $ info ['result ' ];
312
+ $ id = (int ) $ ch = $ info ['handle ' ];
313
+ $ waitFor = @curl_getinfo ($ ch , CURLINFO_PRIVATE ) ?: '_0 ' ;
314
+
315
+ if (\in_array ($ result , [\CURLE_SEND_ERROR , \CURLE_RECV_ERROR , /*CURLE_HTTP2*/ 16 , /*CURLE_HTTP2_STREAM*/ 92 ], true ) && $ waitFor [1 ] && 'C ' !== $ waitFor [0 ]) {
316
+ curl_multi_remove_handle ($ multi ->handle , $ ch );
317
+ $ waitFor [1 ] = (string ) ((int ) $ waitFor [1 ] - 1 ); // decrement the retry counter
318
+ curl_setopt ($ ch , CURLOPT_PRIVATE , $ waitFor );
319
+
320
+ if ('1 ' === $ waitFor [1 ]) {
321
+ curl_setopt ($ ch , CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1 );
322
+ }
323
+
324
+ if (0 === curl_multi_add_handle ($ multi ->handle , $ ch )) {
325
+ continue ;
326
+ }
327
+ }
328
+
329
+ $ multi ->handlesActivity [$ id ][] = null ;
330
+ $ 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 )));
299
331
}
300
332
} finally {
301
333
self ::$ performing = false ;
@@ -320,7 +352,9 @@ private static function select(CurlClientState $multi, float $timeout): int
320
352
*/
321
353
private static function parseHeaderLine ($ ch , string $ data , array &$ info , array &$ headers , ?array $ options , CurlClientState $ multi , int $ id , ?string &$ location , ?callable $ resolveRedirect , ?LoggerInterface $ logger , &$ content = null ): int
322
354
{
323
- if (!\in_array ($ waitFor = @curl_getinfo ($ ch , CURLINFO_PRIVATE ), ['headers ' , 'destruct ' ], true )) {
355
+ $ waitFor = @curl_getinfo ($ ch , CURLINFO_PRIVATE ) ?: '_0 ' ;
356
+
357
+ if ('H ' !== $ waitFor [0 ] && 'D ' !== $ waitFor [0 ]) {
324
358
return \strlen ($ data ); // Ignore HTTP trailers
325
359
}
326
360
@@ -381,14 +415,18 @@ private static function parseHeaderLine($ch, string $data, array &$info, array &
381
415
}
382
416
383
417
if ($ statusCode < 300 || 400 <= $ statusCode || null === $ location || curl_getinfo ($ ch , CURLINFO_REDIRECT_COUNT ) === $ options ['max_redirects ' ]) {
384
- // Headers and redirects completed, time to get the response's body
418
+ // Headers and redirects completed, time to get the response's content
385
419
$ multi ->handlesActivity [$ id ][] = new FirstChunk ();
386
420
387
- if ('destruct ' === $ waitFor ) {
388
- return 0 ;
421
+ if ('D ' === $ waitFor [0 ] || 'HEAD ' === $ info ['http_method ' ] || \in_array ($ statusCode , [204 , 304 ], true )) {
422
+ $ waitFor = '_0 ' ; // no content expected
423
+ $ multi ->handlesActivity [$ id ][] = null ;
424
+ $ multi ->handlesActivity [$ id ][] = null ;
425
+ } else {
426
+ $ waitFor [0 ] = 'C ' ; // C = content
389
427
}
390
428
391
- curl_setopt ($ ch , CURLOPT_PRIVATE , ' content ' );
429
+ curl_setopt ($ ch , CURLOPT_PRIVATE , $ waitFor );
392
430
393
431
try {
394
432
if (!$ content && $ options ['buffer ' ] instanceof \Closure && $ content = $ options ['buffer ' ]($ headers ) ?: null ) {
0 commit comments