From 08509cada3c92d9773d58f32ff90d70847f60bf7 Mon Sep 17 00:00:00 2001 From: Makis Voultepsis Date: Sun, 22 Nov 2015 20:34:08 +0200 Subject: [PATCH] ref zf2#7498 timeout exceptions for curl timeout errors ref zf2#7498 code cleanup, extra tests and comments removing unused variable --- src/Client/Adapter/Curl.php | 16 ++++- .../Exception/ConnectTimeoutException.php | 19 +++++ test/Client/CurlTest.php | 69 +++++++++++++++++++ test/Client/_files/testTimeout.php | 12 ++++ 4 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/Client/Adapter/Exception/ConnectTimeoutException.php create mode 100644 test/Client/_files/testTimeout.php diff --git a/src/Client/Adapter/Curl.php b/src/Client/Adapter/Curl.php index 267f0c86f2..a36bae126a 100644 --- a/src/Client/Adapter/Curl.php +++ b/src/Client/Adapter/Curl.php @@ -247,6 +247,10 @@ public function connect($host, $port = 80, $secure = false) * to wrong host, no PUT file defined, unsupported method, or unsupported * cURL option. * @throws AdapterException\InvalidArgumentException if $method is currently not supported + * @throws AdapterException\TimeoutException when a request exceeds the CURLOPT_TIMEOUT + * setting before it completes. + * @throws AdapterException\ConnectTimeoutException when a request exceeds the + * CURLOPT_CONNECTTIMEOUT setting when trying to connect to a host:port. */ public function write($method, $uri, $httpVersion = 1.1, $headers = [], $body = '') { @@ -425,7 +429,17 @@ public function write($method, $uri, $httpVersion = 1.1, $headers = [], $body = $request .= $body; if (empty($this->response)) { - throw new AdapterException\RuntimeException("Error in cURL request: " . curl_error($this->curl)); + $errorMessage = "Error in cURL request: " . curl_error($this->curl); + preg_match('/: (.*?) timed out/', $errorMessage, $matches); + $type = isset($matches[1]) ? $matches[1] : null; + switch ($type) { + case 'Operation': + throw new AdapterException\TimeoutException($errorMessage); + case 'Connection': + throw new AdapterException\ConnectTimeoutException($errorMessage); + default: + throw new AdapterException\RuntimeException($errorMessage); + } } // separating header from body because it is dangerous to accidentially replace strings in the body diff --git a/src/Client/Adapter/Exception/ConnectTimeoutException.php b/src/Client/Adapter/Exception/ConnectTimeoutException.php new file mode 100644 index 0000000000..acb144f9db --- /dev/null +++ b/src/Client/Adapter/Exception/ConnectTimeoutException.php @@ -0,0 +1,19 @@ +client->send(); $this->assertEquals('foo=bar', $this->client->getResponse()->getBody()); } + + public function testCurlThrowsTimeoutExceptionWhenTimeoutErrorOccursForValidAddress() + { + $timeout = 1; + $timeoutInMs = $timeout * 1000 + 1; + $this->client->setUri($this->baseuri . "testTimeout.php?timeout=$timeout"); + $adapter = new Adapter\Curl(); + $adapter->setOptions([ + 'curloptions' => [CURLOPT_TIMEOUT => $timeout] + ]); + $this->client->setAdapter($adapter); + $this->client->setMethod('GET'); + $this->setExpectedException(Adapter\Exception\TimeoutException::CLASS, "Error in cURL request: Operation timed out after $timeoutInMs milliseconds with 0 bytes received"); + $this->client->send(); + } + + public function testCurlThrowsConnectTimeoutExceptionWhenNonRoutableAddressIsUsed() + { + $timeoutInMs = 1001; + $this->client->setUri('http://10.0.0.0'); + $adapter = new Adapter\Curl(); + $adapter->setOptions([ + 'curloptions' => [CURLOPT_CONNECTTIMEOUT_MS => $timeoutInMs - 1] + ]); + $this->client->setAdapter($adapter); + $this->client->setMethod('GET'); + $this->setExpectedException(Adapter\Exception\ConnectTimeoutException::CLASS, "Error in cURL request: Connection timed out after $timeoutInMs milliseconds"); + $this->client->send(); + } + + public function testCurlThrowsRuntimeExceptionWhenNonTimeoutErrorOccurs() + { + $this->client->setUri($this->baseuri . "testHttpAuth.php"); + $adapter = new Adapter\Curl(); + $adapter->setOptions([ + 'curloptions' => [CURLOPT_FAILONERROR => true] + ]); + $this->client->setAdapter($adapter); + $this->client->setMethod('GET'); + $this->setExpectedException(Adapter\Exception\RuntimeException::CLASS, "Error in cURL request: The requested URL returned error: 401 Unauthorized"); + $this->client->send(); + } + + public function testCurlRetrievesResponseWhenTimeoutExceptionDoesNotOccur() + { + $timeout = 1; + $this->client->setUri($this->baseuri . "testTimeout.php?timeout=$timeout"); + $adapter = new Adapter\Curl(); + $adapter->setOptions([ + 'curloptions' => [CURLOPT_TIMEOUT => $timeout + 1] + ]); + $this->client->setAdapter($adapter); + $this->client->setMethod('GET'); + $this->client->send(); + $this->assertEquals($timeout, $this->client->getResponse()->getBody()); + } + + public function testCurlRetrievesResponseWhenConnectTimeoutExceptionDoesNotOccur() + { + $this->client->setUri($this->baseuri . "testSimpleRequests.php"); + $adapter = new Adapter\Curl(); + $adapter->setOptions([ + 'curloptions' => [CURLOPT_CONNECTTIMEOUT_MS => 1000] + ]); + $this->client->setAdapter($adapter); + $this->client->setMethod('GET'); + $this->client->send(); + $this->assertEquals('Success' . PHP_EOL, $this->client->getResponse()->getBody()); + } } diff --git a/test/Client/_files/testTimeout.php b/test/Client/_files/testTimeout.php new file mode 100644 index 0000000000..0ab5bfd7cd --- /dev/null +++ b/test/Client/_files/testTimeout.php @@ -0,0 +1,12 @@ +