ENHANCEMENT: Restful service returns cached response on http and curl errors (from r108437)

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@112750 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2010-10-19 00:36:48 +00:00
parent 6a83da1455
commit d60ef78ba4
2 changed files with 122 additions and 21 deletions

View File

@ -101,17 +101,12 @@ class RestfulService extends ViewableData {
* @todo Pass the response headers to RestfulService_Response * @todo Pass the response headers to RestfulService_Response
* *
* This is a replacement of {@link connect()}. * 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()) { public function request($subURL = '', $method = "GET", $data = null, $headers = null, $curlOptions = array()) {
$url = $this->baseURL . $subURL; // Url for the request
if($this->queryString) { $url = $this->getAbsoluteRequestURL($subURL);
if(strpos($url, '?') !== false) {
$url .= '&' . $this->queryString;
} else {
$url .= '?' . $this->queryString;
}
}
$url = str_replace(' ', '%20', $url); // Encode spaces
$method = strtoupper($method); $method = strtoupper($method);
assert(in_array($method, array('GET','POST','PUT','DELETE','HEAD','OPTIONS'))); assert(in_array($method, array('GET','POST','PUT','DELETE','HEAD','OPTIONS')));
@ -171,25 +166,53 @@ class RestfulService extends ViewableData {
$responseBody = curl_exec($ch); $responseBody = curl_exec($ch);
$curlError = curl_error($ch); $curlError = curl_error($ch);
} }
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($curlError !== '' || $statusCode == 0) $statusCode = 500;
if($responseBody === false) { $response = new RestfulService_Response($responseBody, $statusCode);
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));
curl_close($ch); curl_close($ch);
// Serialise response object and write to cache if($curlError === '' && !$response->isError()) {
$store = serialize($response); // Serialise response object and write to cache
file_put_contents($cache_path,$store); $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; 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. * Gets attributes as an array, of a particular type of element.
* Example : <photo id="2636" owner="123" secret="ab128" server="2"> * Example : <photo id="2636" owner="123" secret="ab128" server="2">
@ -362,6 +385,12 @@ class RestfulService extends ViewableData {
class RestfulService_Response extends SS_HTTPResponse { class RestfulService_Response extends SS_HTTPResponse {
protected $simpleXML; 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) { function __construct($body, $statusCode = 200, $headers = null) {
$this->setbody($body); $this->setbody($body);
$this->setStatusCode($statusCode); $this->setStatusCode($statusCode);
@ -380,6 +409,20 @@ class RestfulService_Response extends SS_HTTPResponse {
return $this->simpleXML; return $this->simpleXML;
} }
/**
* @return string
*/
function getCachedBody() {
return $this->cachedBody;
}
/**
* @param string
*/
function setCachedBody($content) {
$this->cachedBody = $content;
}
/** /**
* Return an array of xpath matches * Return an array of xpath matches
*/ */

View File

@ -90,6 +90,41 @@ class RestfulServiceTest extends SapphireTest {
$test1 = $connection->request('RestfulServiceTest_Controller/invalid?usetestmanifest=1&flush=1'); $test1 = $connection->request('RestfulServiceTest_Controller/invalid?usetestmanifest=1&flush=1');
$test1->xpath("\\fail"); $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("<error>HTTP Error</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("<error>HTTP Error</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 { class RestfulServiceTest_Controller extends Controller {
@ -134,6 +169,29 @@ XML;
header('Content-type: text/xml'); header('Content-type: text/xml');
echo $out; echo $out;
} }
public function httpErrorWithoutCache() {
$out = <<<XML
<?xml version="1.0"?>
<test>
<error>HTTP Error</error>
</test>
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();
}
} }
/** /**