Merge pull request #2437 from hafriedlander/fix/restfulservice_proxies

FIX Improve recent RestfulService fix for proxies
This commit is contained in:
Simon Welsh 2013-09-19 22:02:50 -07:00
commit 4f7dc653d1
2 changed files with 27 additions and 75 deletions

View File

@ -224,8 +224,11 @@ class RestfulService extends ViewableData {
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
if(!ini_get('open_basedir')) curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1); if(!ini_get('open_basedir')) curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
//include headers in the response
curl_setopt($ch, CURLOPT_HEADER, true);
// Write headers to a temporary file
$headerfd = tmpfile();
curl_setopt($ch, CURLOPT_WRITEHEADER, $headerfd);
// Add headers // Add headers
if($this->customHeaders) { if($this->customHeaders) {
@ -260,8 +263,13 @@ class RestfulService extends ViewableData {
curl_setopt_array($ch, $curlOptions); curl_setopt_array($ch, $curlOptions);
// Run request // Run request
$rawResponse = curl_exec($ch); $body = curl_exec($ch);
$response = $this->extractResponse($ch, $rawResponse);
rewind($headerfd);
$headers = stream_get_contents($headerfd);
fclose($headerfd);
$response = $this->extractResponse($ch, $headers, $body);
curl_close($ch); curl_close($ch);
return $response; return $response;
@ -308,36 +316,26 @@ class RestfulService extends ViewableData {
} }
/** /**
* Build the response from raw data. The response could have multiple redirection * Extracts the response body and headers from a full curl response
* and proxy connect headers, so we are only interested in the last header before the body.
* *
* @param curl_handle $ch The curl handle for the request * @param curl_handle $ch The curl handle for the request
* @param string $rawResponse The raw response text * @param string $rawResponse The raw response text
* *
* @return RestfulService_Response The response object * @return RestfulService_Response The response object
*/ */
protected function extractResponse($ch, $rawResponse) { protected function extractResponse($ch, $rawHeaders, $rawBody) {
//get the status code //get the status code
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
//get a curl error if there is one //get a curl error if there is one
$curlError = curl_error($ch); $curlError = curl_error($ch);
//normalise the status code //normalise the status code
if(curl_error($ch) !== '' || $statusCode == 0) $statusCode = 500; if(curl_error($ch) !== '' || $statusCode == 0) $statusCode = 500;
//parse the headers
// Parse the headers and body from the response. $parts = array_filter(explode("\r\n\r\n", $rawHeaders));
// We cannot rely on CURLINFO_HEADER_SIZE here, it's miscalculated when connecting via $lastHeaders = array_pop($parts);
// a proxy (see http://sourceforge.net/p/curl/bugs/1204/). This is fixed in curl 7.30.0. $headers = $this->parseRawHeaders($lastHeaders);
$headerParts = array(); //return the response object
$parts = explode("\r\n\r\n", $rawResponse); return new RestfulService_Response($rawBody, $statusCode, $headers);
while (isset($parts[0])) {
if (strpos($parts[0], 'HTTP/')===0) $headerParts[] = array_shift($parts);
else break; // We have reached the body.
}
$lastHeader = array_pop($headerParts);
$body = implode("\r\n\r\n", $parts);
$parsedHeader = $this->parseRawHeaders($lastHeader);
return new RestfulService_Response($body, $statusCode, $parsedHeader);
} }
/** /**

View File

@ -198,7 +198,7 @@ class RestfulServiceTest extends SapphireTest {
public function testExtractResponseRedirectionAndProxy() { public function testExtractResponseRedirectionAndProxy() {
// This is an example of real raw response for a request via a proxy that gets redirected. // This is an example of real raw response for a request via a proxy that gets redirected.
$rawResponse = $rawHeaders =
"HTTP/1.0 200 Connection established\r\n" . "HTTP/1.0 200 Connection established\r\n" .
"\r\n" . "\r\n" .
"HTTP/1.1 301 Moved Permanently\r\n" . "HTTP/1.1 301 Moved Permanently\r\n" .
@ -220,8 +220,8 @@ class RestfulServiceTest extends SapphireTest {
"X-Frame-Options: SAMEORIGIN\r\n" . "X-Frame-Options: SAMEORIGIN\r\n" .
"Cache-Control: no-cache, max-age=0, must-revalidate, no-transform\r\n" . "Cache-Control: no-cache, max-age=0, must-revalidate, no-transform\r\n" .
"Vary: Accept-Encoding\r\n" . "Vary: Accept-Encoding\r\n" .
"\r\n" . "\r\n"
"<!doctype html></html>"; ;
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse'); $headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
$headerFunction->setAccessible(true); $headerFunction->setAccessible(true);
@ -230,10 +230,10 @@ class RestfulServiceTest extends SapphireTest {
$response = $headerFunction->invoke( $response = $headerFunction->invoke(
new RestfulService(Director::absoluteBaseURL(),0), new RestfulService(Director::absoluteBaseURL(),0),
$ch, $ch,
$rawResponse $rawHeaders,
''
); );
$this->assertEquals($response->getBody(), '<!doctype html></html>', 'Body is correctly extracted.');
$this->assertEquals( $this->assertEquals(
$response->getHeaders(), $response->getHeaders(),
array( array(
@ -250,53 +250,7 @@ class RestfulServiceTest extends SapphireTest {
); );
} }
public function testExtractResponseNewlinesInBody() {
$rawResponse =
"HTTP/1.1 200 OK\r\n" .
"Server: nginx\r\n" .
"\r\n" .
"<!doctype html>\r\n" .
"\r\n" .
"</html>";
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
$headerFunction->setAccessible(true);
$ch = curl_init();
$response = $headerFunction->invoke(
new RestfulService(Director::absoluteBaseURL(),0),
$ch,
$rawResponse
);
$this->assertEquals($response->getBody(), "<!doctype html>\r\n\r\n</html>", 'Body is correctly extracted.');
$this->assertEquals($response->getHeaders(), array('Server' => "nginx"), 'Headers are correctly extracted.');
}
public function testExtractResponseNoBody() {
// For example a response to HEAD request.
$rawResponse =
"HTTP/1.1 200 OK\r\n" .
"Server: nginx";
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
$headerFunction->setAccessible(true);
$ch = curl_init();
$response = $headerFunction->invoke(
new RestfulService(Director::absoluteBaseURL(),0),
$ch,
$rawResponse
);
$this->assertEquals($response->getBody(), "", 'Body is correctly extracted.');
$this->assertEquals($response->getHeaders(), array('Server' => "nginx"), 'Headers are correctly extracted.');
}
public function testExtractResponseNoHead() { public function testExtractResponseNoHead() {
// Malformed response.
$rawResponse = "I am a malformed response";
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse'); $headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
$headerFunction->setAccessible(true); $headerFunction->setAccessible(true);
@ -304,10 +258,10 @@ class RestfulServiceTest extends SapphireTest {
$response = $headerFunction->invoke( $response = $headerFunction->invoke(
new RestfulService(Director::absoluteBaseURL(),0), new RestfulService(Director::absoluteBaseURL(),0),
$ch, $ch,
$rawResponse '',
''
); );
$this->assertEquals($response->getBody(), "I am a malformed response", 'Body is correctly extracted.');
$this->assertEquals($response->getHeaders(), array(), 'Headers are correctly extracted.'); $this->assertEquals($response->getHeaders(), array(), 'Headers are correctly extracted.');
} }
} }