diff --git a/Dockerfile b/Dockerfile index efc11656f..f6a095230 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ RUN set -xe \ && composer require guzzlehttp/guzzle -FROM php:7.2 +FROM php:7.3 RUN mkdir /guzzle diff --git a/docs/request-options.rst b/docs/request-options.rst index 7fbeeaa3e..47f5c66a7 100644 --- a/docs/request-options.rst +++ b/docs/request-options.rst @@ -737,7 +737,7 @@ progress The function accepts the following positional arguments: -- the total number of bytes expected to be downloaded +- the total number of bytes expected to be downloaded, zero if unknown - the number of bytes downloaded so far - the total number of bytes expected to be uploaded - the number of bytes uploaded so far diff --git a/docs/testing.rst b/docs/testing.rst index 5ac06691b..fd84d2cdb 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -32,7 +32,7 @@ a response or exception by shifting return values off of a queue. // Create a mock and queue two responses. $mock = new MockHandler([ - new Response(200, ['X-Foo' => 'Bar']), + new Response(200, ['X-Foo' => 'Bar'], 'Hello, World'), new Response(202, ['Content-Length' => 0]), new RequestException('Error Communicating with Server', new Request('GET', 'test')) ]); @@ -41,8 +41,11 @@ a response or exception by shifting return values off of a queue. $client = new Client(['handler' => $handlerStack]); // The first request is intercepted with the first response. - echo $client->request('GET', '/')->getStatusCode(); + $response = $client->request('GET', '/'); + echo $response->getStatusCode(); //> 200 + echo $response->getBody(); + //> Hello, World // The second request is intercepted with the second response. echo $client->request('GET', '/')->getStatusCode(); //> 202 diff --git a/src/Exception/GuzzleException.php b/src/Exception/GuzzleException.php index 510778f6e..c232fa637 100644 --- a/src/Exception/GuzzleException.php +++ b/src/Exception/GuzzleException.php @@ -1,13 +1,20 @@ handle); + $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME); $stats = new TransferStats( $easy->request, $easy->response, @@ -136,7 +140,9 @@ private static function finishError( $ctx = [ 'errno' => $easy->errno, 'error' => curl_error($easy->handle), + 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME), ] + curl_getinfo($easy->handle); + $ctx[self::CURL_VERSION_STR] = curl_version()['version']; $factory->release($easy); // Retry when nothing is present or when curl failed to rewind. @@ -172,13 +178,22 @@ private static function createRejection(EasyHandle $easy, array $ctx) ) ); } - - $message = sprintf( - 'cURL error %s: %s (%s)', - $ctx['errno'], - $ctx['error'], - 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' - ); + if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) { + $message = sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + } else { + $message = sprintf( + 'cURL error %s: %s (%s) for %s', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html', + $easy->request->getUri() + ); + } // Create a connection exception if it was a specific error code. $error = isset($connectionErrors[$easy->errno]) diff --git a/src/Handler/CurlMultiHandler.php b/src/Handler/CurlMultiHandler.php index 7097835d1..d8297623c 100644 --- a/src/Handler/CurlMultiHandler.php +++ b/src/Handler/CurlMultiHandler.php @@ -88,7 +88,7 @@ public function tick() { // Add any delayed handles if needed. if ($this->delays) { - $currentTime = microtime(true); + $currentTime = \GuzzleHttp\_current_time(); foreach ($this->delays as $id => $delay) { if ($currentTime >= $delay) { unset($this->delays[$id]); @@ -140,7 +140,7 @@ private function addRequest(array $entry) if (empty($easy->options['delay'])) { curl_multi_add_handle($this->_mh, $easy->handle); } else { - $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000); + $this->delays[$id] = \GuzzleHttp\_current_time() + ($easy->options['delay'] / 1000); } } @@ -192,7 +192,7 @@ private function processMessages() private function timeToNext() { - $currentTime = microtime(true); + $currentTime = \GuzzleHttp\_current_time(); $nextTime = PHP_INT_MAX; foreach ($this->delays as $time) { if ($time < $nextTime) { diff --git a/src/Handler/StreamHandler.php b/src/Handler/StreamHandler.php index 741e02d2a..0dedd7da4 100644 --- a/src/Handler/StreamHandler.php +++ b/src/Handler/StreamHandler.php @@ -33,7 +33,7 @@ public function __invoke(RequestInterface $request, array $options) usleep($options['delay'] * 1000); } - $startTime = isset($options['on_stats']) ? microtime(true) : null; + $startTime = isset($options['on_stats']) ? \GuzzleHttp\_current_time() : null; try { // Does not support the expect header. @@ -82,7 +82,7 @@ private function invokeStats( $stats = new TransferStats( $request, $response, - microtime(true) - $startTime, + \GuzzleHttp\_current_time() - $startTime, $error, [] ); diff --git a/src/functions.php b/src/functions.php index 5081bb5fa..3c472bc6a 100644 --- a/src/functions.php +++ b/src/functions.php @@ -331,3 +331,15 @@ function json_encode($value, $options = 0, $depth = 512) return $json; } + +/** + * Wrapper for the hrtime() or microtime() functions + * (depending on the PHP version, one of the two is used) + * + * @return float|mixed UNIX timestamp + * @internal + */ +function _current_time() +{ + return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true); +} diff --git a/tests/Cookie/CookieJarTest.php b/tests/Cookie/CookieJarTest.php index 8416c9a24..f0fad497d 100644 --- a/tests/Cookie/CookieJarTest.php +++ b/tests/Cookie/CookieJarTest.php @@ -1,6 +1,9 @@ "fpc=foobar; expires=Fri, 02-Mar-2019 02:17:40 GMT; path=;" + 'Set-Cookie' => "fpc=foobar; expires={$this->futureExpirationDate()}; path=;" )); $request = new Request('GET', 'http://www.example.com'); $this->jar->extractCookies($request, $response); @@ -396,11 +399,11 @@ public function testCookiePathWithEmptySetCookiePath($uriPath, $cookiePath) $response = (new Response(200)) ->withAddedHeader( 'Set-Cookie', - "foo=bar; expires=Fri, 02-Mar-2019 02:17:40 GMT; domain=www.example.com; path=;" + "foo=bar; expires={$this->futureExpirationDate()}; domain=www.example.com; path=;" ) ->withAddedHeader( 'Set-Cookie', - "bar=foo; expires=Fri, 02-Mar-2019 02:17:40 GMT; domain=www.example.com; path=foobar;" + "bar=foo; expires={$this->futureExpirationDate()}; domain=www.example.com; path=foobar;" ) ; $request = (new Request('GET', $uriPath))->withHeader('Host', 'www.example.com'); @@ -409,4 +412,9 @@ public function testCookiePathWithEmptySetCookiePath($uriPath, $cookiePath) $this->assertSame($cookiePath, $this->jar->toArray()[0]['Path']); $this->assertSame($cookiePath, $this->jar->toArray()[1]['Path']); } + + private function futureExpirationDate() + { + return (new DateTimeImmutable)->add(new DateInterval('P1D'))->format(DateTime::COOKIE); + } } diff --git a/tests/Handler/CurlFactoryTest.php b/tests/Handler/CurlFactoryTest.php index af52c58d2..d1e4e9980 100644 --- a/tests/Handler/CurlFactoryTest.php +++ b/tests/Handler/CurlFactoryTest.php @@ -685,6 +685,7 @@ public function testInvokesOnStatsOnSuccess() (string) $gotStats->getRequest()->getUri() ); $this->assertGreaterThan(0, $gotStats->getTransferTime()); + $this->assertArrayHasKey('appconnect_time', $gotStats->getHandlerStats()); } public function testInvokesOnStatsOnError() @@ -711,6 +712,7 @@ public function testInvokesOnStatsOnError() ); $this->assertInternalType('float', $gotStats->getTransferTime()); $this->assertInternalType('int', $gotStats->getHandlerErrorData()); + $this->assertArrayHasKey('appconnect_time', $gotStats->getHandlerStats()); } public function testRewindsBodyIfPossible() diff --git a/tests/Handler/CurlHandlerTest.php b/tests/Handler/CurlHandlerTest.php index 25c499020..6a4d233a0 100644 --- a/tests/Handler/CurlHandlerTest.php +++ b/tests/Handler/CurlHandlerTest.php @@ -47,9 +47,9 @@ public function testDoesSleep() Server::enqueue([$response]); $a = new CurlHandler(); $request = new Request('GET', Server::$url); - $s = microtime(true); + $s = \GuzzleHttp\_current_time(); $a($request, ['delay' => 0.1])->wait(); - $this->assertGreaterThan(0.0001, microtime(true) - $s); + $this->assertGreaterThan(0.0001, \GuzzleHttp\_current_time() - $s); } public function testCreatesCurlErrorsWithContext() diff --git a/tests/Handler/CurlMultiHandlerTest.php b/tests/Handler/CurlMultiHandlerTest.php index 6c987afb8..03043f5ff 100644 --- a/tests/Handler/CurlMultiHandlerTest.php +++ b/tests/Handler/CurlMultiHandlerTest.php @@ -68,10 +68,10 @@ public function testDelaysConcurrently() Server::flush(); Server::enqueue([new Response()]); $a = new CurlMultiHandler(); - $expected = microtime(true) + (100 / 1000); + $expected = \GuzzleHttp\_current_time() + (100 / 1000); $response = $a(new Request('GET', Server::$url), ['delay' => 100]); $response->wait(); - $this->assertGreaterThanOrEqual($expected, microtime(true)); + $this->assertGreaterThanOrEqual($expected, \GuzzleHttp\_current_time()); } public function testUsesTimeoutEnvironmentVariables() diff --git a/tests/Handler/StreamHandlerTest.php b/tests/Handler/StreamHandlerTest.php index 6c10ec3f0..f62994896 100644 --- a/tests/Handler/StreamHandlerTest.php +++ b/tests/Handler/StreamHandlerTest.php @@ -506,9 +506,9 @@ public function testDoesSleep() Server::enqueue([$response]); $a = new StreamHandler(); $request = new Request('GET', Server::$url); - $s = microtime(true); + $s = \GuzzleHttp\_current_time(); $a($request, ['delay' => 0.1])->wait(); - $this->assertGreaterThan(0.0001, microtime(true) - $s); + $this->assertGreaterThan(0.0001, \GuzzleHttp\_current_time() - $s); } /** diff --git a/tests/functionsTest.php b/tests/functionsTest.php index fd65d41b6..bf5c8d8b8 100644 --- a/tests/functionsTest.php +++ b/tests/functionsTest.php @@ -128,6 +128,11 @@ public function testDecodesJsonAndThrowsOnError() { \GuzzleHttp\json_decode('{{]]'); } + + public function testCurrentTime() + { + $this->assertGreaterThan(0, GuzzleHttp\_current_time()); + } } final class StrClass