diff --git a/api/RestfulService.php b/api/RestfulService.php index 06700ac7f..4b7aadf20 100644 --- a/api/RestfulService.php +++ b/api/RestfulService.php @@ -101,17 +101,12 @@ class RestfulService extends ViewableData { * @todo Pass the response headers to RestfulService_Response * * This is a replacement of {@link connect()}. + * + * @return RestfulService_Response - If curl request produces error, the returned response's status code will be 500 */ public function request($subURL = '', $method = "GET", $data = null, $headers = null, $curlOptions = array()) { - $url = $this->baseURL . $subURL; // Url for the request - if($this->queryString) { - if(strpos($url, '?') !== false) { - $url .= '&' . $this->queryString; - } else { - $url .= '?' . $this->queryString; - } - } - $url = str_replace(' ', '%20', $url); // Encode spaces + + $url = $this->getAbsoluteRequestURL($subURL); $method = strtoupper($method); assert(in_array($method, array('GET','POST','PUT','DELETE','HEAD','OPTIONS'))); @@ -171,25 +166,53 @@ class RestfulService extends ViewableData { $responseBody = curl_exec($ch); $curlError = curl_error($ch); } + + $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if($curlError !== '' || $statusCode == 0) $statusCode = 500; - if($responseBody === false) { - user_error("Curl Error:" . $curlError, E_USER_WARNING); - return; - } - - $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $response = new RestfulService_Response($responseBody, curl_getinfo($ch, CURLINFO_HTTP_CODE)); - + $response = new RestfulService_Response($responseBody, $statusCode); curl_close($ch); - - // Serialise response object and write to cache - $store = serialize($response); - file_put_contents($cache_path,$store); + + if($curlError === '' && !$response->isError()) { + // Serialise response object and write to cache + $store = serialize($response); + file_put_contents($cache_path, $store); + } + else { + // In case of curl or/and http indicate error, populate response's cachedBody property + // with cached response body with the cache file exists + if (@file_exists($cache_path)) { + $store = file_get_contents($cache_path); + $cachedResponse = unserialize($store); + + $response->setCachedBody($cachedResponse->getBody()); + } + else { + $response->setCachedBody(false); + } + } } return $response; } + /** + * Returns a full request url + * @param string + */ + function getAbsoluteRequestURL($subURL) { + $url = $this->baseURL . $subURL; // Url for the request + if($this->queryString) { + if(strpos($url, '?') !== false) { + $url .= '&' . $this->queryString; + } else { + $url .= '?' . $this->queryString; + } + } + + return str_replace(' ', '%20', $url); // Encode spaces + } + /** * Gets attributes as an array, of a particular type of element. * Example : @@ -362,6 +385,12 @@ class RestfulService extends ViewableData { class RestfulService_Response extends SS_HTTPResponse { protected $simpleXML; + /** + * @var boolean It should be populated with cached content + * when a request referring to this response was unsuccessful + */ + protected $cachedBody = false; + function __construct($body, $statusCode = 200, $headers = null) { $this->setbody($body); $this->setStatusCode($statusCode); @@ -380,6 +409,20 @@ class RestfulService_Response extends SS_HTTPResponse { return $this->simpleXML; } + /** + * @return string + */ + function getCachedBody() { + return $this->cachedBody; + } + + /** + * @param string + */ + function setCachedBody($content) { + $this->cachedBody = $content; + } + /** * Return an array of xpath matches */ diff --git a/tests/api/RestfulServiceTest.php b/tests/api/RestfulServiceTest.php index 978cb59a5..5e62246d6 100644 --- a/tests/api/RestfulServiceTest.php +++ b/tests/api/RestfulServiceTest.php @@ -90,6 +90,41 @@ class RestfulServiceTest extends SapphireTest { $test1 = $connection->request('RestfulServiceTest_Controller/invalid?usetestmanifest=1&flush=1'); $test1->xpath("\\fail"); } + + function testHttpErrorWithoutCache() { + $connection = new RestfulService(Director::absoluteBaseURL(), 0); + $response = $connection->request('RestfulServiceTest_Controller/httpErrorWithoutCache?usetestmanifest=1&flush=1'); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertFalse($response->getCachedBody()); + $this->assertContains("HTTP Error", $response->getBody()); + + } + + function testHttpErrorWithCache() { + $subUrl = 'RestfulServiceTest_Controller/httpErrorWithCache?usetestmanifest=1&flush=1'; + $connection = new RestfulService(Director::absoluteBaseURL(), 0); + $this->createFakeCachedResponse($connection, $subUrl); + $response = $connection->request($subUrl); + + $this->assertEquals(400, $response->getStatusCode()); + $this->assertEquals("Cache response body",$response->getCachedBody()); + $this->assertContains("HTTP Error", $response->getBody()); + + } + + /** + * Simulate cached response file for testing error requests that are supposed to have cache files + */ + private function createFakeCachedResponse($connection, $subUrl) { + $fullUrl = $connection->getAbsoluteRequestURL($subUrl); + $cachedir = TEMP_FOLDER; // Default silverstripe cache + $cache_file = md5($fullUrl); // Encoded name of cache file + $cache_path = $cachedir."/xmlresponse_$cache_file"; + $cacheResponse = new RestfulService_Response("Cache response body"); + $store = serialize($cacheResponse); + file_put_contents($cache_path, $store); + } } class RestfulServiceTest_Controller extends Controller { @@ -134,6 +169,29 @@ XML; header('Content-type: text/xml'); echo $out; } + + public function httpErrorWithoutCache() { + $out = << + + HTTP Error + +XML; + + $this->response->setBody($out); + $this->response->setStatusCode(400); + $this->response->addHeader('Content-type', 'text/xml'); + + return $this->response; + } + + /** + * The body of this method is the same as self::httpErrorWithoutCache() + * but we need it for caching since caching using request url to determine path to cache file + */ + public function httpErrorWithCache() { + return $this->httpErrorWithoutCache(); + } } /**