mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
RFC #5347 remove RestfulService from SS4. Rebuild OEmbed on using curl
This commit is contained in:
parent
bccd08211f
commit
c0de5dd11e
@ -1,656 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* RestfulService class allows you to consume various RESTful APIs.
|
|
||||||
* Through this you could connect and aggregate data of various web services.
|
|
||||||
* For more info visit wiki documentation - http://doc.silverstripe.org/doku.php?id=restfulservice
|
|
||||||
*
|
|
||||||
* @package framework
|
|
||||||
* @subpackage integration
|
|
||||||
*/
|
|
||||||
class RestfulService extends ViewableData implements Flushable {
|
|
||||||
|
|
||||||
protected $baseURL;
|
|
||||||
protected $queryString;
|
|
||||||
protected $errorTag;
|
|
||||||
protected $checkErrors;
|
|
||||||
protected $cache_expire;
|
|
||||||
protected $authUsername, $authPassword;
|
|
||||||
protected $customHeaders = array();
|
|
||||||
protected $proxy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @config
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $default_proxy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @config
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private static $default_curl_options = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @config
|
|
||||||
* @var bool Flushes caches if set to true. This is set by {@link flush()}
|
|
||||||
*/
|
|
||||||
private static $flush = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered early in the request when someone requests a flush.
|
|
||||||
*/
|
|
||||||
public static function flush() {
|
|
||||||
self::$flush = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set a curl option that will be applied to all requests as default
|
|
||||||
* {@see http://php.net/manual/en/function.curl-setopt.php#refsect1-function.curl-setopt-parameters}
|
|
||||||
*
|
|
||||||
* @deprecated 4.0 Use the "RestfulService.default_curl_options" config setting instead
|
|
||||||
* @param int $option The cURL opt Constant
|
|
||||||
* @param mixed $value The cURL opt value
|
|
||||||
*/
|
|
||||||
public static function set_default_curl_option($option, $value) {
|
|
||||||
Deprecation::notice('4.0', 'Use the "RestfulService.default_curl_options" config setting instead');
|
|
||||||
Config::inst()->update('RestfulService', 'default_curl_options', array($option => $value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set many defauly curl options at once
|
|
||||||
*
|
|
||||||
* @deprecated 4.0 Use the "RestfulService.default_curl_options" config setting instead
|
|
||||||
*/
|
|
||||||
public static function set_default_curl_options($optionArray) {
|
|
||||||
Deprecation::notice('4.0', 'Use the "RestfulService.default_curl_options" config setting instead');
|
|
||||||
Config::inst()->update('RestfulService', 'default_curl_options', $optionArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets default proxy settings for outbound RestfulService connections
|
|
||||||
*
|
|
||||||
* @param string $proxy The URL of the proxy to use.
|
|
||||||
* @param int $port Proxy port
|
|
||||||
* @param string $user The proxy auth user name
|
|
||||||
* @param string $password The proxy auth password
|
|
||||||
* @param boolean $socks Set true to use socks5 proxy instead of http
|
|
||||||
* @deprecated 4.0 Use the "RestfulService.default_curl_options" config setting instead,
|
|
||||||
* with direct reference to the CURL_* options
|
|
||||||
*/
|
|
||||||
public static function set_default_proxy($proxy, $port = 80, $user = "", $password = "", $socks = false) {
|
|
||||||
Deprecation::notice(
|
|
||||||
'4.0',
|
|
||||||
'Use the "RestfulService.default_curl_options" config setting instead, '
|
|
||||||
. 'with direct reference to the CURL_* options'
|
|
||||||
);
|
|
||||||
config::inst()->update('RestfulService', 'default_proxy', array(
|
|
||||||
CURLOPT_PROXY => $proxy,
|
|
||||||
CURLOPT_PROXYUSERPWD => "{$user}:{$password}",
|
|
||||||
CURLOPT_PROXYPORT => $port,
|
|
||||||
CURLOPT_PROXYTYPE => ($socks ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new restful service.
|
|
||||||
* @param string $base Base URL of the web service eg: api.example.com
|
|
||||||
* @param int $expiry Set the cache expiry interva. Defaults to 1 hour (3600 seconds)
|
|
||||||
*/
|
|
||||||
public function __construct($base, $expiry=3600){
|
|
||||||
$this->baseURL = $base;
|
|
||||||
$this->cache_expire = $expiry;
|
|
||||||
parent::__construct();
|
|
||||||
$this->proxy = $this->config()->default_proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Query string parameters to send a request.
|
|
||||||
* @param array $params An array passed with necessary parameters.
|
|
||||||
*/
|
|
||||||
public function setQueryString($params=NULL){
|
|
||||||
$this->queryString = http_build_query($params,'','&');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set proxy settings for this RestfulService instance
|
|
||||||
*
|
|
||||||
* @param string $proxy The URL of the proxy to use.
|
|
||||||
* @param int $port Proxy port
|
|
||||||
* @param string $user The proxy auth user name
|
|
||||||
* @param string $password The proxy auth password
|
|
||||||
* @param boolean $socks Set true to use socks5 proxy instead of http
|
|
||||||
*/
|
|
||||||
public function setProxy($proxy, $port = 80, $user = "", $password = "", $socks = false) {
|
|
||||||
$this->proxy = array(
|
|
||||||
CURLOPT_PROXY => $proxy,
|
|
||||||
CURLOPT_PROXYUSERPWD => "{$user}:{$password}",
|
|
||||||
CURLOPT_PROXYPORT => $port,
|
|
||||||
CURLOPT_PROXYTYPE => ($socks ? CURLPROXY_SOCKS5 : CURLPROXY_HTTP)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set basic authentication
|
|
||||||
*/
|
|
||||||
public function basicAuth($username, $password) {
|
|
||||||
$this->authUsername = $username;
|
|
||||||
$this->authPassword = $password;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a custom HTTP header
|
|
||||||
*/
|
|
||||||
public function httpHeader($header) {
|
|
||||||
$this->customHeaders[] = $header;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated since version 4.0
|
|
||||||
*/
|
|
||||||
protected function constructURL(){
|
|
||||||
Deprecation::notice('4.0', 'constructURL is deprecated, please use `getAbsoluteRequestURL` instead');
|
|
||||||
return Controller::join_links($this->baseURL, '?' . $this->queryString);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes a request to the RESTful server, and return a {@link RestfulService_Response} object for parsing of the
|
|
||||||
* result.
|
|
||||||
*
|
|
||||||
* @todo Better POST, PUT, DELETE, and HEAD support
|
|
||||||
* @todo Caching of requests - probably only GET and HEAD requestst
|
|
||||||
* @todo JSON support in RestfulService_Response
|
|
||||||
* @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->getAbsoluteRequestURL($subURL);
|
|
||||||
$method = strtoupper($method);
|
|
||||||
|
|
||||||
assert(in_array($method, array('GET','POST','PUT','DELETE','HEAD','OPTIONS','PATCH')));
|
|
||||||
|
|
||||||
$cache_path = $this->getCachePath(array(
|
|
||||||
$url,
|
|
||||||
$method,
|
|
||||||
$data,
|
|
||||||
array_merge((array)$this->customHeaders, (array)$headers),
|
|
||||||
$curlOptions + (array)$this->config()->default_curl_options,
|
|
||||||
$this->getBasicAuthString()
|
|
||||||
));
|
|
||||||
|
|
||||||
// Check for unexpired cached feed (unless flush is set)
|
|
||||||
//assume any cache_expire that is 0 or less means that we dont want to
|
|
||||||
// cache
|
|
||||||
if($this->cache_expire > 0 && !self::$flush
|
|
||||||
&& @file_exists($cache_path)
|
|
||||||
&& @filemtime($cache_path) + $this->cache_expire > time()) {
|
|
||||||
|
|
||||||
$store = file_get_contents($cache_path);
|
|
||||||
$response = unserialize($store);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$response = $this->curlRequest($url, $method, $data, $headers, $curlOptions);
|
|
||||||
|
|
||||||
if(!$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->setCachedResponse($cachedResponse);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$response->setCachedResponse(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actually performs a remote service request using curl. This is used by
|
|
||||||
* {@link RestfulService::request()}.
|
|
||||||
*
|
|
||||||
* @param string $url
|
|
||||||
* @param string $method
|
|
||||||
* @param array $data
|
|
||||||
* @param array $headers
|
|
||||||
* @param array $curlOptions
|
|
||||||
* @return RestfulService_Response
|
|
||||||
*/
|
|
||||||
public function curlRequest($url, $method, $data = null, $headers = null, $curlOptions = array()) {
|
|
||||||
$ch = curl_init();
|
|
||||||
$timeout = 5;
|
|
||||||
$sapphireInfo = new SapphireInfo();
|
|
||||||
$useragent = 'SilverStripe/' . $sapphireInfo->Version();
|
|
||||||
$curlOptions = $curlOptions + (array)$this->config()->default_curl_options;
|
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
||||||
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
|
|
||||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
|
|
||||||
if(!ini_get('open_basedir')) curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
|
|
||||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
|
||||||
|
|
||||||
|
|
||||||
// Write headers to a temporary file
|
|
||||||
$headerfd = tmpfile();
|
|
||||||
curl_setopt($ch, CURLOPT_WRITEHEADER, $headerfd);
|
|
||||||
|
|
||||||
// Add headers
|
|
||||||
if($this->customHeaders) {
|
|
||||||
$headers = array_merge((array)$this->customHeaders, (array)$headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
if($headers) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
|
||||||
|
|
||||||
// Add authentication
|
|
||||||
if($this->authUsername) curl_setopt($ch, CURLOPT_USERPWD, $this->getBasicAuthString());
|
|
||||||
|
|
||||||
// Add fields to POST and PUT requests
|
|
||||||
if($method == 'POST' || $method == 'PATCH') {
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
|
||||||
} elseif($method == 'PUT') {
|
|
||||||
$put = fopen("php://temp", 'r+');
|
|
||||||
fwrite($put, $data);
|
|
||||||
fseek($put, 0);
|
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_PUT, 1);
|
|
||||||
curl_setopt($ch, CURLOPT_INFILE, $put);
|
|
||||||
curl_setopt($ch, CURLOPT_INFILESIZE, strlen($data));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply proxy settings
|
|
||||||
if(is_array($this->proxy)) {
|
|
||||||
curl_setopt_array($ch, $this->proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set any custom options passed to the request() function
|
|
||||||
curl_setopt_array($ch, $curlOptions);
|
|
||||||
|
|
||||||
// Run request
|
|
||||||
$body = curl_exec($ch);
|
|
||||||
|
|
||||||
rewind($headerfd);
|
|
||||||
$headers = stream_get_contents($headerfd);
|
|
||||||
fclose($headerfd);
|
|
||||||
|
|
||||||
$response = $this->extractResponse($ch, $headers, $body);
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function to return the auth string. This helps consistency through the
|
|
||||||
* class but also allows tests to pull it out when generating the expected
|
|
||||||
* cache keys
|
|
||||||
*
|
|
||||||
* @see {self::getCachePath()}
|
|
||||||
* @see {RestfulServiceTest::createFakeCachedResponse()}
|
|
||||||
*
|
|
||||||
* @return string The auth string to be base64 encoded
|
|
||||||
*/
|
|
||||||
protected function getBasicAuthString() {
|
|
||||||
return $this->authUsername . ':' . $this->authPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a cache key based on any cache data sent. The cache data can be
|
|
||||||
* any type
|
|
||||||
*
|
|
||||||
* @param mixed $cacheData The cache seed for generating the key
|
|
||||||
* @param string the md5 encoded cache seed.
|
|
||||||
*/
|
|
||||||
protected function generateCacheKey($cacheData) {
|
|
||||||
return md5(var_export($cacheData, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the cache path
|
|
||||||
*
|
|
||||||
* This is mainly so that the cache path can be generated in a consistent
|
|
||||||
* way in tests without having to hard code the cachekey generate function
|
|
||||||
* in tests
|
|
||||||
*
|
|
||||||
* @param mixed $cacheData The cache seed {@see self::generateCacheKey}
|
|
||||||
*
|
|
||||||
* @return string The path to the cache file
|
|
||||||
*/
|
|
||||||
protected function getCachePath($cacheData) {
|
|
||||||
return TEMP_FOLDER . "/xmlresponse_" . $this->generateCacheKey($cacheData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the response body and headers from a full curl response
|
|
||||||
*
|
|
||||||
* @param curl_handle $ch The curl handle for the request
|
|
||||||
* @param string $rawResponse The raw response text
|
|
||||||
*
|
|
||||||
* @return RestfulService_Response The response object
|
|
||||||
*/
|
|
||||||
protected function extractResponse($ch, $rawHeaders, $rawBody) {
|
|
||||||
//get the status code
|
|
||||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
//get a curl error if there is one
|
|
||||||
$curlError = curl_error($ch);
|
|
||||||
//normalise the status code
|
|
||||||
if(curl_error($ch) !== '' || $statusCode == 0) $statusCode = 500;
|
|
||||||
//parse the headers
|
|
||||||
$parts = array_filter(explode("\r\n\r\n", $rawHeaders));
|
|
||||||
$lastHeaders = array_pop($parts);
|
|
||||||
$headers = $this->parseRawHeaders($lastHeaders);
|
|
||||||
//return the response object
|
|
||||||
return new RestfulService_Response($rawBody, $statusCode, $headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes raw headers and parses them to turn them to an associative array
|
|
||||||
*
|
|
||||||
* Any header that we see more than once is turned into an array.
|
|
||||||
*
|
|
||||||
* This is meant to mimic http_parse_headers {@link http://php.net/manual/en/function.http-parse-headers.php}
|
|
||||||
* thanks to comment #77241 on that page for foundation of this
|
|
||||||
*
|
|
||||||
* @param string $rawHeaders The raw header string
|
|
||||||
* @return array The assosiative array of headers
|
|
||||||
*/
|
|
||||||
protected function parseRawHeaders($rawHeaders) {
|
|
||||||
$headers = array();
|
|
||||||
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $rawHeaders));
|
|
||||||
foreach( $fields as $field ) {
|
|
||||||
if( preg_match('/([^:]+): (.+)/m', $field, $match) ) {
|
|
||||||
$match[1] = preg_replace_callback(
|
|
||||||
'/(?<=^|[\x09\x20\x2D])./',
|
|
||||||
create_function('$matches', 'return strtoupper($matches[0]);'),
|
|
||||||
trim($match[1])
|
|
||||||
);
|
|
||||||
if( isset($headers[$match[1]]) ) {
|
|
||||||
if (!is_array($headers[$match[1]])) {
|
|
||||||
$headers[$match[1]] = array($headers[$match[1]]);
|
|
||||||
}
|
|
||||||
$headers[$match[1]][] = $match[2];
|
|
||||||
} else {
|
|
||||||
$headers[$match[1]] = trim($match[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a full request url
|
|
||||||
* @param string
|
|
||||||
*/
|
|
||||||
public function getAbsoluteRequestURL($subURL = '') {
|
|
||||||
$url = Controller::join_links($this->baseURL, $subURL, '?' . $this->queryString);
|
|
||||||
|
|
||||||
return str_replace(' ', '%20', $url); // Encode spaces
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets attributes as an array, of a particular type of element.
|
|
||||||
* Example : <photo id="2636" owner="123" secret="ab128" server="2">
|
|
||||||
* returns id, owner,secret and sever attribute values of all such photo elements.
|
|
||||||
* @param string $xml The source xml to parse, this could be the original response received.
|
|
||||||
* @param string $collection The name of parent node which wraps the elements, if available
|
|
||||||
* @param string $element The element we need to extract the attributes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public function getAttributes($xml, $collection=NULL, $element=NULL){
|
|
||||||
$xml = new SimpleXMLElement($xml);
|
|
||||||
$output = new ArrayList();
|
|
||||||
|
|
||||||
if($collection)
|
|
||||||
$childElements = $xml->{$collection};
|
|
||||||
if($element)
|
|
||||||
$childElements = $xml->{$collection}->{$element};
|
|
||||||
|
|
||||||
if($childElements){
|
|
||||||
foreach($childElements as $child){
|
|
||||||
$data = array();
|
|
||||||
foreach($child->attributes() as $key => $value){
|
|
||||||
$data["$key"] = Convert::raw2xml($value);
|
|
||||||
}
|
|
||||||
$output->push(new ArrayData($data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $output;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an attribute of a particular element.
|
|
||||||
* @param string $xml The source xml to parse, this could be the original response received.
|
|
||||||
* @param string $collection The name of the parent node which wraps the element, if available
|
|
||||||
* @param string $element The element we need to extract the attribute
|
|
||||||
* @param string $attr The name of the attribute
|
|
||||||
*/
|
|
||||||
|
|
||||||
public function getAttribute($xml, $collection=NULL, $element=NULL, $attr){
|
|
||||||
$xml = new SimpleXMLElement($xml);
|
|
||||||
$attr_value = "";
|
|
||||||
|
|
||||||
if($collection)
|
|
||||||
$childElements = $xml->{$collection};
|
|
||||||
if($element)
|
|
||||||
$childElements = $xml->{$collection}->{$element};
|
|
||||||
|
|
||||||
if($childElements)
|
|
||||||
$attr_value = (string) $childElements[$attr];
|
|
||||||
|
|
||||||
return Convert::raw2xml($attr_value);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets set of node values as an array.
|
|
||||||
* When you get to the depth in the hierarchy use node_child_subchild syntax to get the value.
|
|
||||||
* @param string $xml The the source xml to parse, this could be the original response received.
|
|
||||||
* @param string $collection The name of parent node which wraps the elements, if available
|
|
||||||
* @param string $element The element we need to extract the node values.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public function getValues($xml, $collection=NULL, $element=NULL){
|
|
||||||
$xml = new SimpleXMLElement($xml);
|
|
||||||
$output = new ArrayList();
|
|
||||||
|
|
||||||
$childElements = $xml;
|
|
||||||
if($collection)
|
|
||||||
$childElements = $xml->{$collection};
|
|
||||||
if($element)
|
|
||||||
$childElements = $xml->{$collection}->{$element};
|
|
||||||
|
|
||||||
if($childElements){
|
|
||||||
foreach($childElements as $child){
|
|
||||||
$data = array();
|
|
||||||
$this->getRecurseValues($child,$data);
|
|
||||||
$output->push(new ArrayData($data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getRecurseValues($xml,&$data,$parent=""){
|
|
||||||
$conv_value = "";
|
|
||||||
$child_count = 0;
|
|
||||||
foreach($xml as $key=>$value)
|
|
||||||
{
|
|
||||||
$child_count++;
|
|
||||||
$k = ($parent == "") ? (string)$key : $parent . "_" . (string)$key;
|
|
||||||
if($this->getRecurseValues($value,$data,$k) == 0){ // no childern, aka "leaf node"
|
|
||||||
$conv_value = Convert::raw2xml($value);
|
|
||||||
}
|
|
||||||
//Review the fix for similar node names overriding it's predecessor
|
|
||||||
if(array_key_exists($k, $data) == true) {
|
|
||||||
$data[$k] = $data[$k] . ",". $conv_value;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$data[$k] = $conv_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
return $child_count;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a single node value.
|
|
||||||
* @param string $xml The source xml to parse, this could be the original response received.
|
|
||||||
* @param string $collection The name of parent node which wraps the elements, if available
|
|
||||||
* @param string $element The element we need to extract the node value.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public function getValue($xml, $collection=NULL, $element=NULL){
|
|
||||||
$xml = new SimpleXMLElement($xml);
|
|
||||||
|
|
||||||
if($collection)
|
|
||||||
$childElements = $xml->{$collection};
|
|
||||||
if($element)
|
|
||||||
$childElements = $xml->{$collection}->{$element};
|
|
||||||
|
|
||||||
if($childElements)
|
|
||||||
return Convert::raw2xml($childElements);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for a node in document tree and returns it value.
|
|
||||||
* @param string $xml source xml to parse, this could be the original response received.
|
|
||||||
* @param string $node Node to search for
|
|
||||||
*/
|
|
||||||
public function searchValue($xml, $node=NULL){
|
|
||||||
$xml = new SimpleXMLElement($xml);
|
|
||||||
$childElements = $xml->xpath($node);
|
|
||||||
|
|
||||||
if($childElements)
|
|
||||||
return Convert::raw2xml($childElements[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for a node in document tree and returns its attributes.
|
|
||||||
* @param string $xml the source xml to parse, this could be the original response received.
|
|
||||||
* @param string $node Node to search for
|
|
||||||
*/
|
|
||||||
public function searchAttributes($xml, $node=NULL){
|
|
||||||
$xml = new SimpleXMLElement($xml);
|
|
||||||
$output = new ArrayList();
|
|
||||||
|
|
||||||
$childElements = $xml->xpath($node);
|
|
||||||
|
|
||||||
if($childElements)
|
|
||||||
foreach($childElements as $child){
|
|
||||||
$data = array();
|
|
||||||
foreach($child->attributes() as $key => $value){
|
|
||||||
$data["$key"] = Convert::raw2xml($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$output->push(new ArrayData($data));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package framework
|
|
||||||
* @subpackage integration
|
|
||||||
*/
|
|
||||||
class RestfulService_Response extends SS_HTTPResponse {
|
|
||||||
protected $simpleXML;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var boolean It should be populated with cached request
|
|
||||||
* when a request referring to this response was unsuccessful
|
|
||||||
*/
|
|
||||||
protected $cachedResponse = false;
|
|
||||||
|
|
||||||
public function __construct($body, $statusCode = 200, $headers = null) {
|
|
||||||
$this->setbody($body);
|
|
||||||
$this->setStatusCode($statusCode);
|
|
||||||
$this->headers = $headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function simpleXML() {
|
|
||||||
if(!$this->simpleXML) {
|
|
||||||
try {
|
|
||||||
$this->simpleXML = new SimpleXMLElement($this->body);
|
|
||||||
}
|
|
||||||
catch(Exception $e) {
|
|
||||||
user_error("String could not be parsed as XML. " . $e, E_USER_WARNING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->simpleXML;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the cached response object. This allows you to access the cached
|
|
||||||
* eaders, not just the cached body.
|
|
||||||
*
|
|
||||||
* @return RestfulSerivice_Response The cached response object
|
|
||||||
*/
|
|
||||||
public function getCachedResponse() {
|
|
||||||
return $this->cachedResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getCachedBody() {
|
|
||||||
if ($this->cachedResponse) {
|
|
||||||
return $this->cachedResponse->getBody();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string
|
|
||||||
* @deprecated since version 4.0
|
|
||||||
*/
|
|
||||||
public function setCachedBody($content) {
|
|
||||||
Deprecation::notice('4.0', 'Setting the response body is now deprecated, set the cached request instead');
|
|
||||||
if (!$this->cachedResponse) {
|
|
||||||
$this->cachedResponse = new RestfulService_Response($content);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$this->cachedResponse->setBody($content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string
|
|
||||||
*/
|
|
||||||
public function setCachedResponse($response) {
|
|
||||||
$this->cachedResponse = $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an array of xpath matches
|
|
||||||
*/
|
|
||||||
public function xpath($xpath) {
|
|
||||||
return $this->simpleXML()->xpath($xpath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the first xpath match
|
|
||||||
*/
|
|
||||||
public function xpath_one($xpath) {
|
|
||||||
$items = $this->xpath($xpath);
|
|
||||||
if (isset($items[0])) {
|
|
||||||
return $items[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -103,13 +103,28 @@ class Oembed implements ShortcodeHandler {
|
|||||||
*/
|
*/
|
||||||
protected static function autodiscover_from_url($url)
|
protected static function autodiscover_from_url($url)
|
||||||
{
|
{
|
||||||
// Fetch the URL (cache for a week by default)
|
$timeout = 5;
|
||||||
$service = new RestfulService($url, 60 * 60 * 24 * 7);
|
$sapphireInfo = new SapphireInfo();
|
||||||
$body = $service->request();
|
$useragent = 'SilverStripe/' . $sapphireInfo->Version();
|
||||||
if (!$body || $body->isError()) {
|
$curlRequest = curl_init();
|
||||||
|
curl_setopt_array(
|
||||||
|
$curlRequest,
|
||||||
|
array(
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => 1,
|
||||||
|
CURLOPT_USERAGENT => $useragent,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => $timeout,
|
||||||
|
CURLOPT_FOLLOWLOCATION => 1,
|
||||||
|
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = curl_exec($curlRequest);
|
||||||
|
$headers = curl_getinfo($curlRequest);
|
||||||
|
if(!$response || $headers['http_code'] !== 200) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$body = $body->getBody();
|
$body = $response;
|
||||||
return static::autodiscover_from_body($body);
|
return static::autodiscover_from_body($body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,15 +298,29 @@ class Oembed_Result extends ViewableData {
|
|||||||
if($this->data !== false) {
|
if($this->data !== false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$timeout = 5;
|
||||||
|
$sapphireInfo = new SapphireInfo();
|
||||||
|
$useragent = 'SilverStripe/' . $sapphireInfo->Version();
|
||||||
|
$curlRequest = curl_init();
|
||||||
|
curl_setopt_array(
|
||||||
|
$curlRequest,
|
||||||
|
array(
|
||||||
|
CURLOPT_URL => $this->url,
|
||||||
|
CURLOPT_RETURNTRANSFER => 1,
|
||||||
|
CURLOPT_USERAGENT => $useragent,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => $timeout,
|
||||||
|
CURLOPT_FOLLOWLOCATION => 1,
|
||||||
|
|
||||||
// Fetch from Oembed URL (cache for a week by default)
|
)
|
||||||
$service = new RestfulService($this->url, 60*60*24*7);
|
);
|
||||||
$body = $service->request();
|
|
||||||
if(!$body || $body->isError()) {
|
$response = curl_exec($curlRequest);
|
||||||
|
$headers = curl_getinfo($curlRequest);
|
||||||
|
if(!$response || $headers['http_code'] !== 200) {
|
||||||
$this->data = array();
|
$this->data = array();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$body = $body->getBody();
|
$body = $response;
|
||||||
$data = json_decode($body, true);
|
$data = json_decode($body, true);
|
||||||
if(!$data) {
|
if(!$data) {
|
||||||
// if the response is no valid JSON we might have received a binary stream to an image
|
// if the response is no valid JSON we might have received a binary stream to an image
|
||||||
|
@ -1,487 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package framework
|
|
||||||
* @subpackage tests
|
|
||||||
*/
|
|
||||||
class RestfulServiceTest extends SapphireTest {
|
|
||||||
|
|
||||||
protected $member_unique_identifier_field = '';
|
|
||||||
|
|
||||||
public function setUp() {
|
|
||||||
// backup the project unique identifier field
|
|
||||||
$this->member_unique_identifier_field = Member::config()->unique_identifier_field;
|
|
||||||
|
|
||||||
Member::config()->unique_identifier_field = 'Email';
|
|
||||||
|
|
||||||
parent::setUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function tearDown() {
|
|
||||||
parent::tearDown();
|
|
||||||
|
|
||||||
// set old Member::config()->unique_identifier_field value
|
|
||||||
if ($this->member_unique_identifier_field) {
|
|
||||||
Member::config()->unique_identifier_field = $this->member_unique_identifier_field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check we can put slashes anywhere and it works
|
|
||||||
*/
|
|
||||||
public function testGetAbsoluteURLSlashes() {
|
|
||||||
$urls = array(
|
|
||||||
'/url/',
|
|
||||||
'url',
|
|
||||||
'/url',
|
|
||||||
'url/',
|
|
||||||
);
|
|
||||||
$restWithoutSlash = new RestfulService('http://example.com');
|
|
||||||
$restWithSlash = new RestfulService('http://example.com/');
|
|
||||||
foreach ($urls as $url) {
|
|
||||||
$url = ltrim($url, '/');
|
|
||||||
$this->assertEquals("http://example.com/$url", $restWithoutSlash->getAbsoluteRequestURL($url));
|
|
||||||
$this->assertEquals("http://example.com/$url", $restWithSlash->getAbsoluteRequestURL($url));
|
|
||||||
$this->assertEquals($restWithoutSlash->getAbsoluteRequestURL($url), $restWithSlash->getAbsoluteRequestURL($url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check we can add query strings all over the shop and it's ok
|
|
||||||
*/
|
|
||||||
public function testGetAbsoluteURLQueries() {
|
|
||||||
$restWithoutSlash = new RestfulService('http://example.com?b=query2');
|
|
||||||
$restWithSlash = new RestfulService('http://example.com/?b=query2');
|
|
||||||
$restWithQuery = new RestfulService('http://example.com/?b=query2');
|
|
||||||
$restWithQuery->setQueryString(array(
|
|
||||||
'c' => 'query3',
|
|
||||||
));
|
|
||||||
$this->assertEquals('http://example.com/url?b=query2&a=query1', $restWithoutSlash->getAbsoluteRequestURL('url?a=query1'));
|
|
||||||
$this->assertEquals('http://example.com/url?b=query2&a=query1', $restWithSlash->getAbsoluteRequestURL('url?a=query1'));
|
|
||||||
$this->assertEquals('http://example.com/url?b=query2&a=query1&c=query3', $restWithQuery->getAbsoluteRequestURL('url?a=query1'));
|
|
||||||
|
|
||||||
$this->assertEquals('http://example.com/url?b=query2', $restWithoutSlash->getAbsoluteRequestURL('url'));
|
|
||||||
$this->assertEquals('http://example.com/url?b=query2', $restWithSlash->getAbsoluteRequestURL('url'));
|
|
||||||
$this->assertEquals('http://example.com/url?b=query2&c=query3', $restWithQuery->getAbsoluteRequestURL('url'));
|
|
||||||
|
|
||||||
$restWithoutSlash = new RestfulService('http://example.com');
|
|
||||||
$restWithSlash = new RestfulService('http://example.com/');
|
|
||||||
$restWithQuery = new RestfulService('http://example.com/');
|
|
||||||
$restWithQuery->setQueryString(array(
|
|
||||||
'c' => 'query3',
|
|
||||||
));
|
|
||||||
$this->assertEquals('http://example.com/url?a=query1', $restWithoutSlash->getAbsoluteRequestURL('url?a=query1'));
|
|
||||||
$this->assertEquals('http://example.com/url?a=query1', $restWithSlash->getAbsoluteRequestURL('url?a=query1'));
|
|
||||||
$this->assertEquals('http://example.com/url?a=query1&c=query3', $restWithQuery->getAbsoluteRequestURL('url?a=query1'));
|
|
||||||
|
|
||||||
$this->assertEquals('http://example.com/url', $restWithoutSlash->getAbsoluteRequestURL('url'));
|
|
||||||
$this->assertEquals('http://example.com/url', $restWithSlash->getAbsoluteRequestURL('url'));
|
|
||||||
$this->assertEquals('http://example.com/url?c=query3', $restWithQuery->getAbsoluteRequestURL('url'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check spaces are encoded
|
|
||||||
*/
|
|
||||||
public function testGetAbsoluteURLWithSpaces() {
|
|
||||||
$rest = new RestfulService('http://example.com');
|
|
||||||
$this->assertEquals('http://example.com/query%20with%20spaces', $rest->getAbsoluteRequestURL('query with spaces'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSpecialCharacters() {
|
|
||||||
$service = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL());
|
|
||||||
$url = 'RestfulServiceTest_Controller/';
|
|
||||||
$params = array(
|
|
||||||
'test1a' => 4352655636.76543, // number test
|
|
||||||
'test1b' => '$&+,/:;=?@#%', // special char test. These should all get encoded
|
|
||||||
'test1c' => 'And now for a string test' // string test
|
|
||||||
);
|
|
||||||
$service->setQueryString($params);
|
|
||||||
$responseBody = $service->request($url)->getBody();
|
|
||||||
foreach ($params as $key => $value) {
|
|
||||||
$this->assertContains("<request_item name=\"$key\">$value</request_item>", $responseBody);
|
|
||||||
$this->assertContains("<get_item name=\"$key\">$value</get_item>", $responseBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetDataWithSetQueryString() {
|
|
||||||
$service = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL());
|
|
||||||
$url = 'RestfulServiceTest_Controller/';
|
|
||||||
$params = array(
|
|
||||||
'test1a' => 'val1a',
|
|
||||||
'test1b' => 'val1b'
|
|
||||||
);
|
|
||||||
$service->setQueryString($params);
|
|
||||||
$responseBody = $service->request($url)->getBody();
|
|
||||||
foreach ($params as $key => $value) {
|
|
||||||
$this->assertContains("<request_item name=\"$key\">$value</request_item>", $responseBody);
|
|
||||||
$this->assertContains("<get_item name=\"$key\">$value</get_item>", $responseBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetDataWithUrlParameters() {
|
|
||||||
$service = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL());
|
|
||||||
$url = 'RestfulServiceTest_Controller/';
|
|
||||||
$params = array(
|
|
||||||
'test1a' => 'val1a',
|
|
||||||
'test1b' => 'val1b'
|
|
||||||
);
|
|
||||||
$url .= '?' . http_build_query($params);
|
|
||||||
$responseBody = $service->request($url)->getBody();
|
|
||||||
foreach ($params as $key => $value) {
|
|
||||||
$this->assertContains("<request_item name=\"$key\">$value</request_item>", $responseBody);
|
|
||||||
$this->assertContains("<get_item name=\"$key\">$value</get_item>", $responseBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testPostData() {
|
|
||||||
$service = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL(), 0);
|
|
||||||
$params = array(
|
|
||||||
'test1a' => 'val1a',
|
|
||||||
'test1b' => 'val1b'
|
|
||||||
);
|
|
||||||
$responseBody = $service->request('RestfulServiceTest_Controller/', 'POST', $params)->getBody();
|
|
||||||
foreach ($params as $key => $value) {
|
|
||||||
$this->assertContains("<request_item name=\"$key\">$value</request_item>", $responseBody);
|
|
||||||
$this->assertContains("<post_item name=\"$key\">$value</post_item>", $responseBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testPutData() {
|
|
||||||
$service = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL(), 0);
|
|
||||||
$data = 'testPutData';
|
|
||||||
$responseBody = $service->request('RestfulServiceTest_Controller/', 'PUT', $data)->getBody();
|
|
||||||
$this->assertContains("<body>$data</body>", $responseBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testConnectionDoesntCacheWithDifferentUrl() {
|
|
||||||
$service = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL());
|
|
||||||
$url = 'RestfulServiceTest_Controller/';
|
|
||||||
|
|
||||||
// First run
|
|
||||||
$params = array(
|
|
||||||
'test1a' => 'first run',
|
|
||||||
);
|
|
||||||
$service->setQueryString($params);
|
|
||||||
$responseBody = $service->request($url)->getBody();
|
|
||||||
$this->assertContains("<request_item name=\"test1a\">first run</request_item>", $responseBody);
|
|
||||||
|
|
||||||
// Second run
|
|
||||||
$params = array(
|
|
||||||
'test1a' => 'second run',
|
|
||||||
);
|
|
||||||
$service->setQueryString($params);
|
|
||||||
$responseBody = $service->request($url)->getBody();
|
|
||||||
$this->assertContains("<request_item name=\"test1a\">second run</request_item>", $responseBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException PHPUnit_Framework_Error
|
|
||||||
*/
|
|
||||||
public function testIncorrectData() {
|
|
||||||
$connection = new RestfulService(Director::absoluteBaseURL(), 0);
|
|
||||||
$test1 = $connection->request('RestfulServiceTest_Controller/invalid');
|
|
||||||
$test1->xpath("\\fail");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHttpErrorWithoutCache() {
|
|
||||||
$connection = new RestfulServiceTest_MockRestfulService(Director::absoluteBaseURL(), 0);
|
|
||||||
$response = $connection->request('RestfulServiceTest_Controller/httpErrorWithoutCache');
|
|
||||||
|
|
||||||
$this->assertEquals(400, $response->getStatusCode());
|
|
||||||
$this->assertFalse($response->getCachedBody());
|
|
||||||
$this->assertContains("<error>HTTP Error</error>", $response->getBody());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHttpErrorWithCache() {
|
|
||||||
$subUrl = 'RestfulServiceTest_Controller/httpErrorWithCache';
|
|
||||||
$connection = new RestfulServiceTest_MockErrorService(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
|
|
||||||
*
|
|
||||||
* @todo Generate the cachepath without hardcoding the cache data
|
|
||||||
*/
|
|
||||||
private function createFakeCachedResponse($connection, $subUrl) {
|
|
||||||
$fullUrl = $connection->getAbsoluteRequestURL($subUrl);
|
|
||||||
//these are the defaul values that one would expect in the
|
|
||||||
$basicAuthStringMethod = new ReflectionMethod('RestfulServiceTest_MockErrorService', 'getBasicAuthString');
|
|
||||||
$basicAuthStringMethod->setAccessible(true);
|
|
||||||
$cachePathMethod = new ReflectionMethod('RestfulServiceTest_MockErrorService', 'getCachePath');
|
|
||||||
$cachePathMethod->setAccessible(true);
|
|
||||||
$cache_path = $cachePathMethod->invokeArgs($connection, array(array(
|
|
||||||
$fullUrl,
|
|
||||||
'GET',
|
|
||||||
null,
|
|
||||||
array(),
|
|
||||||
array(),
|
|
||||||
$basicAuthStringMethod->invoke($connection)
|
|
||||||
)));
|
|
||||||
|
|
||||||
$cacheResponse = new RestfulService_Response("Cache response body");
|
|
||||||
$store = serialize($cacheResponse);
|
|
||||||
file_put_contents($cache_path, $store);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHttpHeaderParseing() {
|
|
||||||
$headers = "content-type: text/html; charset=UTF-8\r\n".
|
|
||||||
"Server: Funky/1.0\r\n".
|
|
||||||
"X-BB-ExampleMANycaPS: test\r\n".
|
|
||||||
"Set-Cookie: foo=bar\r\n".
|
|
||||||
"Set-Cookie: baz=quux\r\n".
|
|
||||||
"Set-Cookie: bar=foo\r\n";
|
|
||||||
$expected = array(
|
|
||||||
'Content-Type' => 'text/html; charset=UTF-8',
|
|
||||||
'Server' => 'Funky/1.0',
|
|
||||||
'X-BB-ExampleMANycaPS' => 'test',
|
|
||||||
'Set-Cookie' => array(
|
|
||||||
'foo=bar',
|
|
||||||
'baz=quux',
|
|
||||||
'bar=foo'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$headerFunction = new ReflectionMethod('RestfulService', 'parseRawHeaders');
|
|
||||||
$headerFunction->setAccessible(true);
|
|
||||||
$this->assertEquals(
|
|
||||||
$expected,
|
|
||||||
$headerFunction->invoke(
|
|
||||||
new RestfulService(Director::absoluteBaseURL(),0), $headers
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExtractResponseRedirectionAndProxy() {
|
|
||||||
// This is an example of real raw response for a request via a proxy that gets redirected.
|
|
||||||
$rawHeaders =
|
|
||||||
"HTTP/1.0 200 Connection established\r\n" .
|
|
||||||
"\r\n" .
|
|
||||||
"HTTP/1.1 301 Moved Permanently\r\n" .
|
|
||||||
"Server: nginx\r\n" .
|
|
||||||
"Date: Fri, 20 Sep 2013 01:53:07 GMT\r\n" .
|
|
||||||
"Content-Type: text/html\r\n" .
|
|
||||||
"Content-Length: 178\r\n" .
|
|
||||||
"Connection: keep-alive\r\n" .
|
|
||||||
"Location: https://www.foobar.org.nz/\r\n" .
|
|
||||||
"\r\n" .
|
|
||||||
"HTTP/1.0 200 Connection established\r\n" .
|
|
||||||
"\r\n" .
|
|
||||||
"HTTP/1.1 200 OK\r\n" .
|
|
||||||
"Server: nginx\r\n" .
|
|
||||||
"Date: Fri, 20 Sep 2013 01:53:08 GMT\r\n" .
|
|
||||||
"Content-Type: text/html; charset=utf-8\r\n" .
|
|
||||||
"Transfer-Encoding: chunked\r\n" .
|
|
||||||
"Connection: keep-alive\r\n" .
|
|
||||||
"X-Frame-Options: SAMEORIGIN\r\n" .
|
|
||||||
"Cache-Control: no-cache, max-age=0, must-revalidate, no-transform\r\n" .
|
|
||||||
"Vary: Accept-Encoding\r\n" .
|
|
||||||
"\r\n"
|
|
||||||
;
|
|
||||||
|
|
||||||
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
|
|
||||||
$headerFunction->setAccessible(true);
|
|
||||||
|
|
||||||
$ch = curl_init();
|
|
||||||
$response = $headerFunction->invoke(
|
|
||||||
new RestfulService(Director::absoluteBaseURL(),0),
|
|
||||||
$ch,
|
|
||||||
$rawHeaders,
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
|
||||||
$response->getHeaders(),
|
|
||||||
array(
|
|
||||||
'Server' => "nginx",
|
|
||||||
'Date' => "Fri, 20 Sep 2013 01:53:08 GMT",
|
|
||||||
'Content-Type' => "text/html; charset=utf-8",
|
|
||||||
'Transfer-Encoding' => "chunked",
|
|
||||||
'Connection' => "keep-alive",
|
|
||||||
'X-Frame-Options' => "SAMEORIGIN",
|
|
||||||
'Cache-Control' => "no-cache, max-age=0, must-revalidate, no-transform",
|
|
||||||
'Vary' => "Accept-Encoding"
|
|
||||||
),
|
|
||||||
'Only last header is extracted and parsed.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testExtractResponseNoHead() {
|
|
||||||
$headerFunction = new ReflectionMethod('RestfulService', 'extractResponse');
|
|
||||||
$headerFunction->setAccessible(true);
|
|
||||||
|
|
||||||
$ch = curl_init();
|
|
||||||
$response = $headerFunction->invoke(
|
|
||||||
new RestfulService(Director::absoluteBaseURL(),0),
|
|
||||||
$ch,
|
|
||||||
'',
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals($response->getHeaders(), array(), 'Headers are correctly extracted.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RestfulServiceTest_Controller extends Controller implements TestOnly {
|
|
||||||
|
|
||||||
private static $allowed_actions = array(
|
|
||||||
'index',
|
|
||||||
'httpErrorWithoutCache',
|
|
||||||
'httpErrorWithCache'
|
|
||||||
);
|
|
||||||
|
|
||||||
public function init() {
|
|
||||||
$this->basicAuthEnabled = false;
|
|
||||||
|
|
||||||
parent::init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function index() {
|
|
||||||
$request = '';
|
|
||||||
foreach ($this->getRequest()->requestVars() as $key=>$value) {
|
|
||||||
$request .= "\t\t<request_item name=\"$key\">$value</request_item>\n";
|
|
||||||
}
|
|
||||||
$get = '';
|
|
||||||
foreach ($this->getRequest()->getVars() as $key => $value) {
|
|
||||||
$get .= "\t\t<get_item name=\"$key\">$value</get_item>\n";
|
|
||||||
}
|
|
||||||
$post = '';
|
|
||||||
foreach ($this->getRequest()->postVars() as $key => $value) {
|
|
||||||
$post .= "\t\t<post_item name=\"$key\">$value</post_item>\n";
|
|
||||||
}
|
|
||||||
$body = $this->getRequest()->getBody();
|
|
||||||
|
|
||||||
$out = <<<XML
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<test>
|
|
||||||
<request>$request</request>
|
|
||||||
<get>$get</get>
|
|
||||||
<post>$post</post>
|
|
||||||
<body>$body</body>
|
|
||||||
</test>
|
|
||||||
XML;
|
|
||||||
$response = $this->getResponse();
|
|
||||||
$response->setBody($out);
|
|
||||||
$response->addHeader('Content-type', 'text/xml');
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invalid() {
|
|
||||||
$out = <<<XML
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<test>
|
|
||||||
<fail><invalid>
|
|
||||||
</test>
|
|
||||||
XML;
|
|
||||||
header('Content-type: text/xml');
|
|
||||||
echo $out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function httpErrorWithoutCache() {
|
|
||||||
$out = <<<XML
|
|
||||||
<?xml version="1.0"?>
|
|
||||||
<test>
|
|
||||||
<error>HTTP Error</error>
|
|
||||||
</test>
|
|
||||||
XML;
|
|
||||||
|
|
||||||
$this->getResponse()->setBody($out);
|
|
||||||
$this->getResponse()->setStatusCode(400);
|
|
||||||
$this->getResponse()->addHeader('Content-type', 'text/xml');
|
|
||||||
|
|
||||||
return $this->getResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mock implementation of {@link RestfulService}, which uses {@link Director::test()}
|
|
||||||
* instead of direct curl system calls.
|
|
||||||
*
|
|
||||||
* @todo Less overloading of request()
|
|
||||||
* @todo Currently only works with relative (internal) URLs
|
|
||||||
*
|
|
||||||
* @package framework
|
|
||||||
* @subpackage tests
|
|
||||||
*/
|
|
||||||
class RestfulServiceTest_MockRestfulService extends RestfulService {
|
|
||||||
|
|
||||||
public $session = null;
|
|
||||||
|
|
||||||
public function request($subURL = '', $method = "GET", $data = null, $headers = null, $curlOptions = array()) {
|
|
||||||
|
|
||||||
if(!$this->session) {
|
|
||||||
$this->session = Injector::inst()->create('Session', 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
|
|
||||||
|
|
||||||
// Custom for mock implementation: Director::test() doesn't cope with absolute URLs
|
|
||||||
$url = Director::makeRelative($url);
|
|
||||||
|
|
||||||
$method = strtoupper($method);
|
|
||||||
|
|
||||||
assert(in_array($method, array('GET','POST','PUT','DELETE','HEAD','OPTIONS')));
|
|
||||||
|
|
||||||
// Add headers
|
|
||||||
if($this->customHeaders) {
|
|
||||||
$headers = array_merge((array)$this->customHeaders, (array)$headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add authentication
|
|
||||||
if($this->authUsername) {
|
|
||||||
$headers[] = "Authorization: Basic " . base64_encode(
|
|
||||||
$this->authUsername.':'.$this->authPassword
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom for mock implementation: Use Director::test()
|
|
||||||
$body = null;
|
|
||||||
$postVars = null;
|
|
||||||
|
|
||||||
if($method!='POST') $body = $data;
|
|
||||||
else $postVars = $data;
|
|
||||||
|
|
||||||
$responseFromDirector = Director::test($url, $postVars, $this->session, $method, $body, $headers);
|
|
||||||
|
|
||||||
$response = new RestfulService_Response(
|
|
||||||
$responseFromDirector->getBody(),
|
|
||||||
$responseFromDirector->getStatusCode()
|
|
||||||
);
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mock service that returns a 400 error for requests.
|
|
||||||
*/
|
|
||||||
class RestfulServiceTest_MockErrorService extends RestfulService {
|
|
||||||
|
|
||||||
public function curlRequest($url, $method, $data = null, $headers = null, $curlOptions = array()) {
|
|
||||||
return new RestfulService_Response('<error>HTTP Error</error>', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user