diff --git a/README.md b/README.md index 0dbe5731..b1fc40ab 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ require 'autoload.php'; Initialization --------------- -After including the required files from the SDK, you need to initalize the ParseClient using your Parse API keys: +After including the required files from the SDK, you need to initialize the ParseClient using your Parse API keys: ```php ParseClient::initialize( $app_id, $rest_key, $master_key ); @@ -63,6 +63,43 @@ For example if your parse server's url is `http://example.com:1337/parse` then y `ParseClient::setServerURL('https://example.com:1337','parse');` + +Http Clients +------------ + +This SDK has the ability to change the underlying http client at your convenience. +The default is to use the curl http client if none is set, there is also a stream http client that can be used as well. + +Setting the http client can be done as follows: +```php +// set curl http client (default if none set) +ParseClient::setHttpClient(new ParseCurlHttpClient()); + +// set stream http client +// ** requires 'allow_url_fopen' to be enabled in php.ini ** +ParseClient::setHttpClient(new ParseStreamHttpClient()); +``` + +If you have a need for an additional http client you can request one by opening an issue or by submitting a PR. + +If you wish to build one yourself make sure your http client implements ```ParseHttpable``` for it be compatible with the SDK. Once you have a working http client that enhances the SDK feel free to submit it in a PR so we can look into adding it in. + + +Alternate Certificate Authority File +------------------------------------ + +It is possible that your local setup may not be able to verify with peers over SSL/TLS. This may especially be the case if you do not have control over your local installation, such as for shared hosting. + +If this is the case you may need to specify a Certificate Authority bundle. You can download such a bundle from (http://curl.haxx.se/ca/cacert.pem)[http://curl.haxx.se/ca/cacert.pem] to use for this purpose. This one happens to be a Mozilla CA certificate store, you don't necessarily have to use this one but it's recommended. + +Once you have your bundle you can set it as follows: +```php +// ** Use an Absolute path for your file! ** +// holds one or more certificates to verify the peer with +ParseClient::setCAFile(__DIR__ . '/certs/cacert.pem'); +``` + + Usage ----- diff --git a/src/Parse/HttpClients/ParseCurl.php b/src/Parse/HttpClients/ParseCurl.php new file mode 100644 index 00000000..4c62cd7e --- /dev/null +++ b/src/Parse/HttpClients/ParseCurl.php @@ -0,0 +1,158 @@ + + */ + +namespace Parse\HttpClients; + + +use Parse\ParseException; + +/** + * Class ParseCurl + * @package Parse\HttpClients + */ +class ParseCurl +{ + /** + * Curl handle + * @var resource + */ + private $curl; + + /** + * Sets up a new curl instance internally if needed + */ + public function init() + { + if($this->curl === null) { + $this->curl = curl_init(); + + } + + } + + /** + * Executes this curl request + * + * @return mixed + * @throws ParseException + */ + public function exec() + { + if(!isset($this->curl)) { + throw new ParseException('You must call ParseCurl::init first'); + + } + + return curl_exec($this->curl); + } + + /** + * Sets a curl option + * + * @param int $option Option to set + * @param mixed $value Value to set for this option + * @throws ParseException + */ + public function setOption($option, $value) + { + if(!isset($this->curl)) { + throw new ParseException('You must call ParseCurl::init first'); + + } + + curl_setopt($this->curl, $option, $value); + + } + + /** + * Sets multiple curl options + * + * @param array $options Array of options to set + * @throws ParseException + */ + public function setOptionsArray($options) + { + if(!isset($this->curl)) { + throw new ParseException('You must call ParseCurl::init first'); + + } + + curl_setopt_array($this->curl, $options); + + } + + /** + * Gets info for this curl handle + * + * @param int $info Constatnt for info to get + * @return mixed + * @throws ParseException + */ + public function getInfo($info) + { + if(!isset($this->curl)) { + throw new ParseException('You must call ParseCurl::init first'); + + } + + return curl_getinfo($this->curl, $info); + + } + + /** + * Gets the curl error message + * + * @return string + * @throws ParseException + */ + public function getError() + { + if(!isset($this->curl)) { + throw new ParseException('You must call ParseCurl::init first'); + + } + + return curl_error($this->curl); + + } + + /** + * Gets the curl error code + * + * @return int + * @throws ParseException + */ + public function getErrorCode() + { + if(!isset($this->curl)) { + throw new ParseException('You must call ParseCurl::init first'); + + } + + return curl_errno($this->curl); + + } + + /** + * Closed our curl handle and disposes of it + */ + public function close() + { + if(!isset($this->curl)) { + throw new ParseException('You must call ParseCurl::init first'); + + } + + // close our handle + curl_close($this->curl); + + // unset our curl handle + $this->curl = null; + + } + +} \ No newline at end of file diff --git a/src/Parse/HttpClients/ParseCurlHttpClient.php b/src/Parse/HttpClients/ParseCurlHttpClient.php new file mode 100644 index 00000000..4d8eff8a --- /dev/null +++ b/src/Parse/HttpClients/ParseCurlHttpClient.php @@ -0,0 +1,384 @@ + + */ + +namespace Parse\HttpClients; + + +use Parse\ParseException; + +/** + * Class ParseCurlHttpClient + * @package Parse\HttpClients + */ +class ParseCurlHttpClient implements ParseHttpable +{ + /** + * Curl handle + * @var ParseCurl + */ + private $parseCurl; + + /** + * Request Headers + * @var array + */ + private $headers = array(); + + /** + * Response headers + * @var array + */ + private $responseHeaders = array(); + + /** + * Response code + * @var int + */ + private $responseCode = 0; + + /** + * Content type of our response + * @var string|null + */ + private $responseContentType; + + /** + * cURL error code + * @var int + */ + private $curlErrorCode; + + /** + * cURL error message + * @var string + */ + private $curlErrorMessage; + + /** + * @const Curl Version which is unaffected by the proxy header length error. + */ + const CURL_PROXY_QUIRK_VER = 0x071E00; + + /** + * @const "Connection Established" header text + */ + const CONNECTION_ESTABLISHED = "HTTP/1.0 200 Connection established\r\n\r\n"; + + /** + * Response from our request + * @var string + */ + private $response; + + + public function __construct() + { + if(!isset($this->parseCurl)) { + $this->parseCurl = new ParseCurl(); + + } + } + + /** + * Adds a header to this request + * + * @param string $key Header name + * @param string $value Header value + */ + public function addRequestHeader($key, $value) + { + $this->headers[$key] = $value; + + } + + /** + * Builds and returns the coalesced request headers + * + * @return array + */ + private function buildRequestHeaders() + { + // coalesce our header key/value pairs + $headers = []; + foreach($this->headers as $key => $value) { + $headers[] = $key.': '.$value; + + } + + return $headers; + + } + + /** + * Gets headers in the response + * + * @return array + */ + public function getResponseHeaders() + { + return $this->responseHeaders; + + } + + /** + * Returns the status code of the response + * + * @return int + */ + public function getResponseStatusCode() + { + return $this->responseCode; + + } + + /** + * Returns the content type of the response + * + * @return null|string + */ + public function getResponseContentType() + { + return $this->responseContentType; + + } + + /** + * Sets up our cURL request in advance + */ + public function setup() + { + // init parse curl + $this->parseCurl->init(); + + $this->parseCurl->setOptionsArray(array( + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_HEADER => 1, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_SSL_VERIFYHOST => 2, + )); + + } + + /** + * Sends an HTTP request + * + * @param string $url Url to send this request to + * @param string $method Method to send this request via + * @param array $data Data to send in this request + * @return string + * @throws ParseException + */ + public function send($url, $method = 'GET', $data = array()) + { + + if($method == "GET" && !empty($data)) { + // handle get + $url .= '?'.http_build_query($data); + + } else if($method == "POST") { + // handle post + $this->parseCurl->setOptionsArray(array( + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => $data + )); + + } else if($method == "PUT") { + // handle put + $this->parseCurl->setOptionsArray(array( + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_POSTFIELDS => $data + )); + + } else if($method == "DELETE") { + // handle delete + $this->parseCurl->setOption(CURLOPT_CUSTOMREQUEST, $method); + + } + + if(count($this->headers) > 0) { + // set our custom request headers + $this->parseCurl->setOption(CURLOPT_HTTPHEADER, $this->buildRequestHeaders()); + + } + + // set url + $this->parseCurl->setOption(CURLOPT_URL, $url); + + // perform our request and get our response + $this->response = $this->parseCurl->exec(); + + // get our response code + $this->responseCode = $this->parseCurl->getInfo(CURLINFO_HTTP_CODE); + + // get our content type + $this->responseContentType = $this->parseCurl->getInfo(CURLINFO_CONTENT_TYPE); + + // get any error code and message + $this->curlErrorMessage = $this->parseCurl->getError(); + $this->curlErrorCode = $this->parseCurl->getErrorCode(); + + // calculate size of our headers + $headerSize = $this->getHeaderSize(); + + // get and set response headers + $headerContent = trim(substr($this->response, 0, $headerSize)); + $this->responseHeaders = $this->getHeadersArray($headerContent); + + // get our final response + $response = trim(substr($this->response, $headerSize)); + + // close our handle + $this->parseCurl->close(); + + // flush our existing headers + $this->headers = array(); + + return $response; + + } + + /** + * Convert and return response headers as an array + * @param string $headerContent Raw headers to parse + * + * @return array + */ + private function getHeadersArray($headerContent) + { + $headers = []; + + // normalize our line breaks + $headerContent = str_replace("\r\n", "\n", $headerContent); + + // Separate our header sets, particularly if we followed a 301 redirect + $headersSet = explode("\n\n", $headerContent); + + // Get the last set of headers, ignoring all others + $rawHeaders = array_pop($headersSet); + + // sepearate our header components + $headerComponents = explode("\n", $rawHeaders); + + foreach($headerComponents as $component) { + if (strpos($component, ': ') === false) { + // set our http_code + $headers['http_code'] = $component; + + + } else { + // set this header key/value pair + list($key, $value) = explode(': ', $component); + $headers[$key] = $value; + + } + } + + // return our completed headers + return $headers; + + } + + + /** + * Sets the connection timeout + * + * @param int $timeout Timeout to set + */ + public function setConnectionTimeout($timeout) + { + $this->parseCurl->setOption(CURLOPT_CONNECTTIMEOUT, $timeout); + + } + + /** + * Sets the request timeout + * + * @param int $timeout Sets the timeout for the request + */ + public function setTimeout($timeout) + { + $this->parseCurl->setOption(CURLOPT_TIMEOUT, $timeout); + + } + + /** + * Sets the CA file to validate requests with + * + * @param string $caFile CA file to set + */ + public function setCAFile($caFile) + { + // name of a file holding one or more certificates to verify the peer with + $this->parseCurl->setOption(CURLOPT_CAINFO, $caFile); + + } + + /** + * Gets the error code + * + * @return int + */ + public function getErrorCode() + { + return $this->curlErrorCode; + + } + + /** + * Gets the error message + * + * @return string + */ + public function getErrorMessage() + { + return $this->curlErrorMessage; + + } + + /** + * Return proper header size + * + * @return integer + */ + private function getHeaderSize() + { + $headerSize = $this->parseCurl->getInfo(CURLINFO_HEADER_SIZE); + + // This corrects a Curl bug where header size does not account + // for additional Proxy headers. + if ( $this->needsCurlProxyFix() ) { + + // Additional way to calculate the request body size. + if (preg_match('/Content-Length: (\d+)/', $this->response, $match)) { + $headerSize = mb_strlen($this->response) - $match[1]; + + } elseif (stripos($this->response, self::CONNECTION_ESTABLISHED) !== false) { + $headerSize += mb_strlen(self::CONNECTION_ESTABLISHED); + + } + } + return $headerSize; + + } + + /** + * Detect versions of Curl which report incorrect header lengths when + * using Proxies. + * + * @return boolean + */ + private function needsCurlProxyFix() + { + $versionDat = curl_version(); + $version = $versionDat['version_number']; + + return $version < self::CURL_PROXY_QUIRK_VER; + + } + +} \ No newline at end of file diff --git a/src/Parse/HttpClients/ParseHttpable.php b/src/Parse/HttpClients/ParseHttpable.php new file mode 100644 index 00000000..760369df --- /dev/null +++ b/src/Parse/HttpClients/ParseHttpable.php @@ -0,0 +1,92 @@ + + */ + +namespace Parse\HttpClients; + + +interface ParseHttpable +{ + /** + * Adds a header to this request + * + * @param string $key Header name + * @param string $value Header value + */ + public function addRequestHeader($key, $value); + + /** + * Gets headers in the response + * + * @return array + */ + public function getResponseHeaders(); + + /** + * Returns the status code of the response + * + * @return int + */ + public function getResponseStatusCode(); + + /** + * Returns the content type of the response + * + * @return null|string + */ + public function getResponseContentType(); + + /** + * Sets the connection timeout + * + * @param int $timeout Timeout to set + */ + public function setConnectionTimeout($timeout); + + /** + * Sets the request timeout + * + * @param int $timeout Sets the timeout for the request + */ + public function setTimeout($timeout); + + /** + * Sets the CA file to validate requests with + * + * @param string $caFile CA file to set + */ + public function setCAFile($caFile); + + /** + * Gets the error code + * + * @return int + */ + public function getErrorCode(); + + /** + * Gets the error message + * + * @return string + */ + public function getErrorMessage(); + + /** + * Sets up our client before we make a request + */ + public function setup(); + + /** + * Sends an HTTP request + * + * @param string $url Url to send this request to + * @param string $method Method to send this request via + * @param array $data Data to send in this request + * @return string + */ + public function send($url, $method = 'GET', $data = array()); + +} \ No newline at end of file diff --git a/src/Parse/HttpClients/ParseStream.php b/src/Parse/HttpClients/ParseStream.php new file mode 100644 index 00000000..86d0ad28 --- /dev/null +++ b/src/Parse/HttpClients/ParseStream.php @@ -0,0 +1,111 @@ + + */ + +namespace Parse\HttpClients; + +/** + * Class ParseStream + * @package Parse\HttpClients + */ +class ParseStream +{ + /** + * + * @var resource + */ + private $stream; + + /** + * Response headers + * @var array|null + */ + private $responseHeaders; + + /** + * Error message + * @var string + */ + private $errorMessage; + + /** + * Error code + * @var int + */ + private $errorCode; + + /** + * Create a stream context + * + * @param array $options Options to pass to our context + */ + public function createContext($options) + { + $this->stream = stream_context_create($options); + + } + + /** + * Gets the contents from the given url + * + * @param string $url Url to get contents of + * @return string + */ + public function get($url) + { + try { + // get our response + $response = file_get_contents($url, false, $this->stream); + + } catch(\Exception $e) { + // set our error message/code and return false + $this->errorMessage = $e->getMessage(); + $this->errorCode = $e->getCode(); + return false; + + } + + // set response headers + $this->responseHeaders = $http_response_header; + + return $response; + + } + + /** + * Returns the response headers for the last request + * + * @return array + */ + public function getResponseHeaders() + { + return $this->responseHeaders; + + } + + /** + * Gets the current error message + * + * @return string + */ + public function getErrorMessage() + { + return $this->errorMessage; + + } + + /** + * Gest the current error code + * + * @return int + */ + public function getErrorCode() + { + return $this->errorCode; + + } + +} \ No newline at end of file diff --git a/src/Parse/HttpClients/ParseStreamHttpClient.php b/src/Parse/HttpClients/ParseStreamHttpClient.php new file mode 100644 index 00000000..60d61617 --- /dev/null +++ b/src/Parse/HttpClients/ParseStreamHttpClient.php @@ -0,0 +1,346 @@ + + */ + +namespace Parse\HttpClients; + + +use Parse\ParseException; + +class ParseStreamHttpClient implements ParseHttpable +{ + /** + * Stream handle + * @var ParseStream + */ + private $parseStream; + + /** + * Request Headers + * @var array + */ + private $headers = array(); + + /** + * Response headers + * @var array + */ + private $responseHeaders = array(); + + /** + * Response code + * @var int + */ + private $responseCode = 0; + + /** + * Content type of our response + * @var string|null + */ + private $responseContentType; + + /** + * Stream error code + * @var int + */ + private $streamErrorCode; + + /** + * Stream error message + * @var string + */ + private $streamErrorMessage; + + /** + * Options to pass to our stream + * @var array + */ + private $options = array(); + + /** + * Optional CA file to verify our peers with + * @var string + */ + private $caFile; + + /** + * Response from our request + * @var string + */ + private $response; + + public function __construct() + { + if(!isset($this->parseStream)) { + $this->parseStream = new ParseStream(); + + } + } + + + + /** + * Adds a header to this request + * + * @param string $key Header name + * @param string $value Header value + */ + public function addRequestHeader($key, $value) + { + $this->headers[$key] = $value; + + } + + /** + * Gets headers in the response + * + * @return array + */ + public function getResponseHeaders() + { + return $this->responseHeaders; + + } + + /** + * Returns the status code of the response + * + * @return int + */ + public function getResponseStatusCode() + { + return $this->responseCode; + + } + + /** + * Returns the content type of the response + * + * @return null|string + */ + public function getResponseContentType() + { + return $this->responseContentType; + + } + + /** + * Builds and returns the coalesced request headers + * + * @return array + */ + private function buildRequestHeaders() + { + // coalesce our header key/value pairs + $headers = []; + foreach($this->headers as $key => $value) { + if($key == 'Expect' && $value == '') { + // drop this pair + continue; + + } + + // add this header key/value pair + $headers[] = $key.': '.$value; + + } + + return implode("\r\n", $headers); + + } + + public function setup() + { + // setup ssl options + $this->options['ssl'] = array( + 'verify_peer' => true, + 'verify_peer_name' => true, + 'allow_self_signed' => true, // All root certificates are self-signed + 'follow_location' => 1 + ); + + } + + public function send($url, $method = 'GET', $data = array()) + { + + // verify this url + if(preg_match('/\s/', trim($url))) { + throw new ParseException('Url may not contain spaces for stream client: '.$url); + + } + + if(isset($this->caFile)) { + // set CA file as well + $this->options['ssl']['cafile'] = $this->caFile; + + } + + // add additional options for this request + $this->options['http'] = array( + 'method' => $method, + 'ignore_errors' => true + ); + + if(isset($this->timeout)) { + $this->options['http']['timeout'] = $this->timeout; + + } + + if(isset($data) && $data != "{}") { + if ($method == "GET") { + // handle GET + $query = http_build_query($data, null, '&'); + $this->options['http']['content'] = $query; + $this->addRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + + } else if ($method == "POST") { + // handle POST + $this->options['http']['content'] = $data; + + } else if ($method == "PUT") { + // handle PUT + $this->options['http']['content'] = $data; + + } + + } + + // set headers + $this->options['http']['header'] = $this->buildRequestHeaders(); + + // create a stream context + $this->parseStream->createContext($this->options); + + // send our request + $response = $this->parseStream->get($url); + + // get our response headers + $rawHeaders = $this->parseStream->getResponseHeaders(); + + if ($response === false || !$rawHeaders) { + // set an error and code + $this->streamErrorMessage = $this->parseStream->getErrorMessage(); + $this->streamErrorCode = $this->parseStream->getErrorCode(); + + } else { + + // set our response headers + $this->responseHeaders = self::formatHeaders($rawHeaders); + + // get and set content type, if present + if(isset($this->responseHeaders['Content-Type'])) { + $this->responseContentType = $this->responseHeaders['Content-Type']; + + } + + // set our http status code + $this->responseCode = self::getStatusCodeFromHeader($this->responseHeaders['http_code']); + + } + + // clear options + $this->options = array(); + + // flush our existing headers + $this->headers = array(); + + return $response; + + } + + /** + * Converts unformatted headers to an array of headers + * + * @param array $rawHeaders + * + * @return array + */ + public static function formatHeaders(array $rawHeaders) + { + $headers = array(); + + foreach ($rawHeaders as $line) { + if (strpos($line, ':') === false) { + // set our http status code + $headers['http_code'] = $line; + + } else { + // set this header entry + list ($key, $value) = explode(': ', $line); + $headers[$key] = $value; + + } + } + + return $headers; + + } + /** + * Extracts the Http status code from the given header + * + * @param string $header + * + * @return int + */ + public static function getStatusCodeFromHeader($header) + { + preg_match('{HTTP/\d\.\d\s+(\d+)\s+.*}', $header, $match); + return (int) $match[1]; + + } + + /** + * Gets the error code + * + * @return int + */ + public function getErrorCode() + { + return $this->streamErrorCode; + + } + + /** + * Gets the error message + * + * @return string + */ + public function getErrorMessage() + { + return $this->streamErrorMessage; + + } + + public function setConnectionTimeout($timeout) + { + // do nothing + } + + /** + * Sets the CA file to validate requests with + * + * @param string $caFile CA file to set + */ + public function setCAFile($caFile) + { + $this->caFile = $caFile; + + } + + /** + * Sets the request timeout + * + * @param int $timeout Sets the timeout for the request + */ + public function setTimeout($timeout) + { + $this->timeout = $timeout; + + } + + +} \ No newline at end of file diff --git a/src/Parse/ParseClient.php b/src/Parse/ParseClient.php index 7da3a8c1..821d2f36 100755 --- a/src/Parse/ParseClient.php +++ b/src/Parse/ParseClient.php @@ -3,7 +3,9 @@ namespace Parse; use Exception; -use InvalidArgumentException; +use Parse\HttpClients\ParseCurlHttpClient; +use Parse\HttpClients\ParseHttpable; +use Parse\HttpClients\ParseStreamHttpClient; use Parse\Internal\Encodable; /** @@ -90,12 +92,26 @@ final class ParseClient */ private static $timeout; + /** + * Http client for requests + * + * @var ParseHttpable + */ + private static $httpClient; + + /** + * CA file holding one or more certificates to verify a peer + * + * @var string + */ + private static $caFile; + /** * Constant for version string to include with requests. * * @var string */ - const VERSION_STRING = 'php1.2.1'; + const VERSION_STRING = 'php1.2.2'; /** * Parse\Client::initialize, must be called before using Parse features. @@ -143,7 +159,7 @@ public static function initialize($app_id, $rest_key, $master_key, $enableCurlEx /** * ParseClient::setServerURL, to change the Parse Server address & mount path for this app - * @param string $serverUrl The remote server url + * @param string $serverURL The remote server url * @param string $mountPath The mount path for this server * * @throws \Exception @@ -167,6 +183,56 @@ public static function setServerURL($serverURL, $mountPath) { } } + /** + * Sets the Http client to use for requests + * + * @param ParseHttpable $httpClient Http client to use + */ + public static function setHttpClient(ParseHttpable $httpClient) + { + self::$httpClient = $httpClient; + + } + + /** + * Gets the current Http client, or creates one to suite the need + * + * @return ParseHttpable + */ + public static function getHttpClient() + { + if (static::$httpClient) { + // use existing client + return static::$httpClient; + + } else { + // default to cURL/stream + return function_exists('curl_init') ? new ParseCurlHttpClient() : new ParseStreamHttpClient(); + + } + + } + + /** + * Clears the currently set http client + */ + public static function clearHttpClient() + { + self::$httpClient = null; + + } + + /** + * Sets a CA file to validate peers of our requests with + * + * @param string $caFile CA file to set + */ + public static function setCAFile($caFile) + { + self::$caFile = $caFile; + + } + /** * ParseClient::_encode, internal method for encoding object values. * @@ -326,80 +392,104 @@ public static function _request( if ($data === '[]') { $data = '{}'; } + + // get our http client + $httpClient = self::getHttpClient(); + + // setup + $httpClient->setup(); + + if(isset(self::$caFile)) { + // set CA file + $httpClient->setCAFile(self::$caFile); + + } + if ($appRequest) { - // 'app' requests are not available in open source parse-server + // ** 'app' requests are not available in open source parse-server self::assertAppInitialized(); - $headers = self::_getAppRequestHeaders(); + + $httpClient->addRequestHeader('X-Parse-Account-Key', self::$accountKey); } else { self::assertParseInitialized(); - $headers = self::_getRequestHeaders($sessionToken, $useMasterKey); - } - $url = self::$serverURL.'/'.self::$mountPath.ltrim($relativeUrl, '/'); + // add appId & client version + $httpClient->addRequestHeader('X-Parse-Application-Id', self::$applicationId); + $httpClient->addRequestHeader('X-Parse-Client-Version', self::VERSION_STRING); - if ($method === 'GET' && !empty($data)) { - $url .= '?'.http_build_query($data); - } - $rest = curl_init(); - curl_setopt($rest, CURLOPT_URL, $url); - curl_setopt($rest, CURLOPT_RETURNTRANSFER, 1); + if ($sessionToken) { + // add our current session token + $httpClient->addRequestHeader('X-Parse-Session-Token', $sessionToken); - if ($method === 'POST') { - $headers[] = 'Content-Type: '.$contentType; - curl_setopt($rest, CURLOPT_POST, 1); - curl_setopt($rest, CURLOPT_POSTFIELDS, $data); - } + } - if ($method === 'PUT') { - $headers[] = 'Content-Type: '.$contentType; - curl_setopt($rest, CURLOPT_CUSTOMREQUEST, $method); - curl_setopt($rest, CURLOPT_POSTFIELDS, $data); - } + if ($useMasterKey) { + // pass master key + $httpClient->addRequestHeader('X-Parse-Master-Key', self::$masterKey); - if ($method === 'DELETE') { - curl_setopt($rest, CURLOPT_CUSTOMREQUEST, $method); - } + } else if(isset(self::$restKey)) { + // pass REST key + $httpClient->addRequestHeader('X-Parse-REST-API-Key', self::$restKey); - curl_setopt($rest, CURLOPT_HTTPHEADER, $headers); + } - if (!is_null(self::$connectionTimeout)) { - curl_setopt($rest, CURLOPT_CONNECTTIMEOUT, self::$connectionTimeout); - } + if (self::$forceRevocableSession) { + // indicate we are using revocable sessions + $httpClient->addRequestHeader('X-Parse-Revocable-Session', '1'); + + } - if (!is_null(self::$timeout)) { - curl_setopt($rest, CURLOPT_TIMEOUT, self::$timeout); } - if ($returnHeaders) { - curl_setopt($rest, CURLOPT_HEADER, 1); - curl_setopt($rest, CURLOPT_FOLLOWLOCATION, true); + /* + * Set an empty Expect header to stop the 100-continue behavior for post + * data greater than 1024 bytes. + * http://pilif.github.io/2007/02/the-return-of-except-100-continue/ + */ + $httpClient->addRequestHeader('Expect', ''); + + // create request url + $url = self::$serverURL . '/' . self::$mountPath.ltrim($relativeUrl, '/'); + + if($method === 'POST' || $method === 'PUT') { + // add content type to the request + $httpClient->addRequestHeader('Content-type', $contentType); + } - $response = curl_exec($rest); - $status = curl_getinfo($rest, CURLINFO_HTTP_CODE); - $contentType = curl_getinfo($rest, CURLINFO_CONTENT_TYPE); - if (curl_errno($rest)) { - if (self::$enableCurlExceptions) { - throw new ParseException(curl_error($rest), curl_errno($rest)); - } else { - return false; - } + if (!is_null(self::$connectionTimeout)) { + // set connection timeout + $httpClient->setConnectionTimeout(self::$connectionTimeout); + } - $headerData = []; + if (!is_null(self::$timeout)) { + // set request/response timeout + $httpClient->setTimeout(self::$timeout); - if ($returnHeaders) { - $headerSize = curl_getinfo($rest, CURLINFO_HEADER_SIZE); - $headerContent = substr($response, 0, $headerSize); - $headerData = self::parseCurlHeaders($headerContent); - $response = substr($response, $headerSize); } - curl_close($rest); + // send our request + $response = $httpClient->send($url, $method, $data); + + // check content type of our response + $contentType = $httpClient->getResponseContentType(); + if (strpos($contentType, 'text/html') !== false) { throw new ParseException('Bad Request', -1); + + } + + if($httpClient->getErrorCode()) { + if(self::$enableCurlExceptions) { + throw new ParseException($httpClient->getErrorMessage(), $httpClient->getErrorCode()); + + } else { + return false; + + } } $decoded = json_decode($response, true); @@ -414,54 +504,12 @@ public static function _request( } if ($returnHeaders) { - $decoded['_headers'] = $headerData; - } - - return $decoded; - } - - /** - * ParseClient::parseCurlHeaders, will parse headers data and returns it as array. - * @param $headerContent - * - * @return array - */ - private static function parseCurlHeaders($headerContent) - { - $headers = []; - $headersContentSet = explode("\r\n\r\n", $headerContent); - $withRedirect = count($headersContentSet) > 2; + $decoded['_headers'] = $httpClient->getResponseHeaders(); - if ($withRedirect) { - $headers['_previous'] = []; } - foreach ($headersContentSet as $headerIndex => $headersData) { - if (empty($headersData)) { - continue; - } - - if ($withRedirect && $headerIndex === 0) { - $storage = &$headers['_previous']; - } else { - $storage = &$headers; - } - - $exploded = explode("\r\n", $headersData); - - foreach ($exploded as $i => $line) { - if (empty($line)) { - continue; - } elseif ($i === 0) { - $storage['http_status'] = $line; - } else { - list ($headerName, $headerValue) = explode(': ', $line); - $storage[$headerName] = $headerValue; - } - } - } + return $decoded; - return $headers; } /** @@ -511,65 +559,14 @@ private static function assertParseInitialized() */ private static function assertAppInitialized() { - if (self::$accountKey === null) { + if (self::$accountKey === null || empty(self::$accountKey)) { throw new Exception( - 'You must call Parse::initialize(..., $accountKey) before making any requests.' + 'You must call Parse::initialize(..., $accountKey) before making any app requests. '. + 'Your account key must not be null or empty.' ); } } - /** - * @param $sessionToken - * @param $useMasterKey - * - * @return array - */ - public static function _getRequestHeaders($sessionToken, $useMasterKey) - { - $headers = ['X-Parse-Application-Id: '.self::$applicationId, - 'X-Parse-Client-Version: '.self::VERSION_STRING, ]; - if ($sessionToken) { - $headers[] = 'X-Parse-Session-Token: '.$sessionToken; - } - if ($useMasterKey) { - $headers[] = 'X-Parse-Master-Key: '.self::$masterKey; - } else if(isset(self::$restKey)) { - $headers[] = 'X-Parse-REST-API-Key: '.self::$restKey; - } - if (self::$forceRevocableSession) { - $headers[] = 'X-Parse-Revocable-Session: 1'; - } - /* - * Set an empty Expect header to stop the 100-continue behavior for post - * data greater than 1024 bytes. - * http://pilif.github.io/2007/02/the-return-of-except-100-continue/ - */ - $headers[] = 'Expect: '; - - return $headers; - } - - /** - * @return array - */ - public static function _getAppRequestHeaders() - { - if (is_null(self::$accountKey) || empty(self::$accountKey)) { - throw new InvalidArgumentException('A account key is required and can not be null or empty'); - } else { - $headers[] = 'X-Parse-Account-Key: '.self::$accountKey; - } - - /* - * Set an empty Expect header to stop the 100-continue behavior for post - * data greater than 1024 bytes. - * http://pilif.github.io/2007/02/the-return-of-except-100-continue/ - */ - $headers[] = 'Expect: '; - - return $headers; - } - /** * Get remote Parse API url. * diff --git a/tests/Parse/Helper.php b/tests/Parse/Helper.php index 6194838d..a71f0a78 100644 --- a/tests/Parse/Helper.php +++ b/tests/Parse/Helper.php @@ -2,7 +2,10 @@ namespace Parse\Test; +use Parse\HttpClients\ParseCurlHttpClient; +use Parse\HttpClients\ParseStreamHttpClient; use Parse\ParseClient; +use Parse\ParseException; use Parse\ParseObject; use Parse\ParseQuery; @@ -47,6 +50,29 @@ public static function setUp() self::$accountKey ); self::setServerURL(); + self::setHttpClient(); + + } + + public static function setHttpClient() + { + // + // Set a curl http client to run primary tests under + // may be: + // + // ParseCurlHttpClient + // ParseStreamHttpClient + // + + if(function_exists('curl_init')) { + // cURL client + ParseClient::setHttpClient(new ParseCurlHttpClient()); + + } else { + // stream client + ParseClient::setHttpClient(new ParseStreamHttpClient()); + } + } public static function setServerURL() diff --git a/tests/Parse/IncrementTest.php b/tests/Parse/IncrementTest.php index de30a7d9..3b2d368a 100644 --- a/tests/Parse/IncrementTest.php +++ b/tests/Parse/IncrementTest.php @@ -18,6 +18,9 @@ public function tearDown() Helper::tearDown(); } + /** + * @group fresh-increment + */ public function testIncrementOnFreshObject() { $obj = ParseObject::create('TestObject'); @@ -184,6 +187,9 @@ public function testIncrementEmptyFieldOnFreshObject() ); } + /** + * @group increment-empty + */ public function testIncrementEmptyField() { $obj = ParseObject::create('TestObject'); @@ -205,6 +211,9 @@ public function testIncrementEmptyField() ); } + /** + * @group empty-field-type-conflict + */ public function testIncrementEmptyFieldAndTypeConflict() { $obj = ParseObject::create('TestObject'); diff --git a/tests/Parse/ParseACLTest.php b/tests/Parse/ParseACLTest.php index 9c5c13f8..71208eff 100644 --- a/tests/Parse/ParseACLTest.php +++ b/tests/Parse/ParseACLTest.php @@ -34,6 +34,9 @@ public function testIsSharedDefault() { } + /** + * @group acl-one-user + */ public function testACLAnObjectOwnedByOneUser() { $user = new ParseUser(); diff --git a/tests/Parse/ParseClientTest.php b/tests/Parse/ParseClientTest.php index bbcfe116..7151f2e9 100644 --- a/tests/Parse/ParseClientTest.php +++ b/tests/Parse/ParseClientTest.php @@ -9,6 +9,8 @@ namespace Parse\Test; +use Parse\HttpClients\ParseCurlHttpClient; +use Parse\HttpClients\ParseStreamHttpClient; use Parse\ParseClient; use Parse\ParseException; use Parse\ParseInstallation; @@ -28,11 +30,18 @@ public static function setUpBeforeClass() public function setUp() { Helper::setServerURL(); + Helper::setHttpClient(); + + } public function tearDown() { Helper::tearDown(); + + // unset CA file + ParseClient::setCAFile(null); + } /** @@ -62,9 +71,9 @@ public function testParseNotInitialized() { * @group client-not-initialized */ public function testAppNotNotInitialized() { - $this->setExpectedException( - '\Exception', - 'You must call Parse::initialize(..., $accountKey) before making any requests.' + $this->setExpectedException(\Exception::class, + 'You must call Parse::initialize(..., $accountKey) before making any app requests. '. + 'Your account key must not be null or empty.' ); ParseClient::initialize( @@ -84,48 +93,6 @@ public function testAppNotNotInitialized() { } - /** - * @group client-app-request - */ - public function testAppRequestHeaders() { - - // call init - ParseClient::initialize( - Helper::$appId, - Helper::$restKey, - Helper::$masterKey, - true, - Helper::$accountKey - ); - - $headers = ParseClient::_getAppRequestHeaders(); - - $this->assertEquals([ - 'X-Parse-Account-Key: '.Helper::$accountKey, - 'Expect: ' - ], $headers); - - } - - /** - * @group client-app-request - */ - public function testAppRequestHeadersMissingAccountKey() { - $this->setExpectedException( - '\InvalidArgumentException', - 'A account key is required and can not be null or empty' - ); - - ParseClient::initialize( - null, - null, - null - ); - - ParseClient::_getAppRequestHeaders(); - - } - /** * @group client-init */ @@ -291,7 +258,7 @@ public function testDecodingStdClass() { /** * @group timeouts */ - public function testTimeout() { + public function testCurlTimeout() { ParseClient::setTimeout(3000); @@ -312,7 +279,7 @@ public function testTimeout() { /** * @group timeouts */ - public function testConnectionTimeout() { + public function testCurlConnectionTimeout() { ParseClient::setConnectionTimeout(3000); // perform a standard save @@ -330,7 +297,53 @@ public function testConnectionTimeout() { } /** - * @group curl-exceptions + * @group timeouts + */ + public function testStreamTimeout() { + + ParseClient::setHttpClient(new ParseStreamHttpClient()); + + ParseClient::setTimeout(3000); + + // perform a standard save + $obj = new ParseObject('TestingClass'); + $obj->set('key', 'value'); + $obj->save(true); + + $this->assertNotNull($obj->getObjectId()); + + $obj->destroy(); + + // clear timeout + ParseClient::setTimeout(null); + + } + + /** + * @group timeouts + */ + public function testStreamConnectionTimeout() { + + ParseClient::setHttpClient(new ParseStreamHttpClient()); + + ParseClient::setConnectionTimeout(3000); + + // perform a standard save + $obj = new ParseObject('TestingClass'); + $obj->set('key', 'value'); + $obj->save(); + + $this->assertNotNull($obj->getObjectId()); + + $obj->destroy(); + + // clear timeout + ParseClient::setConnectionTimeout(null); + + } + + /** + * @group no-curl-exceptions */ public function testNoCurlExceptions() { Helper::setUpWithoutCURLExceptions(); @@ -351,7 +364,10 @@ public function testNoCurlExceptions() { /** * @group curl-exceptions */ - public function testCurlExceptions() { + public function testCurlException() { + + ParseClient::setHttpClient(new ParseCurlHttpClient()); + $this->setExpectedException('\Parse\ParseException', '', 6); ParseClient::setServerURL('http://404.example.com', 'parse'); @@ -362,12 +378,56 @@ public function testCurlExceptions() { } + /** + * @group stream-exceptions + */ + public function testStreamException() { + + ParseClient::setHttpClient(new ParseStreamHttpClient()); + + $this->setExpectedException('\Parse\ParseException', '', 2); + + ParseClient::setServerURL('http://404.example.com', 'parse'); + ParseClient::_request( + 'GET', + 'not-a-real-endpoint-to-reach', + null); + + } + + /** + * @group stream-bad-request + * + * **NOTE** + * file_get_contents may SOMETIMES not return a full set of headers. + * This causes this case to fail frequently while not being a serious error. + * If you are running test cases and are having problems with this, + * run it a few more times and you should be OK + */ + public function testBadStreamRequest() + { + $this->setExpectedException('\Parse\ParseException', + "Bad Request"); + + ParseClient::setHttpClient(new ParseStreamHttpClient()); + + ParseClient::setServerURL('http://example.com', '/'); + ParseClient::_request( + 'GET', + '', + null); + } + /** * @group client-bad-request */ - public function testBadRequest() { + public function testCurlBadRequest() + { $this->setExpectedException('\Parse\ParseException', "Bad Request"); + + ParseClient::setHttpClient(new ParseCurlHttpClient()); + ParseClient::setServerURL('http://example.com', '/'); ParseClient::_request( 'GET', @@ -375,4 +435,72 @@ public function testBadRequest() { null); } + + /** + * @group default-http-client + */ + public function testGetDefaultHttpClient() + { + // clear existing client + ParseClient::clearHttpClient(); + + // get default client + $default = ParseClient::getHttpClient(); + + if(function_exists('curl_init')) { + // should be a curl client + $this->assertTrue($default instanceof ParseCurlHttpClient); + + } else { + // should be a stream client + $this->assertTrue($default instanceof ParseStreamHttpClient); + + } + + } + + /** + * @group ca-file + */ + public function testCurlCAFile() + { + // set a curl client + ParseClient::setHttpClient(new ParseCurlHttpClient()); + + // not a real ca file, just testing setting + ParseClient::setCAFile("not-real-ca-file"); + + $this->setExpectedException('\Parse\ParseException', + "Bad Request"); + + ParseClient::setServerURL('http://example.com', '/'); + ParseClient::_request( + 'GET', + '', + null); + + } + + /** + * @group ca-file + */ + public function testStreamCAFile() + { + // set a stream client + ParseClient::setHttpClient(new ParseStreamHttpClient()); + + // not a real ca file, just testing setting + ParseClient::setCAFile("not-real-ca-file"); + + $this->setExpectedException('\Parse\ParseException', + "Bad Request"); + + ParseClient::setServerURL('http://example.com', '/'); + ParseClient::_request( + 'GET', + '', + null); + + } + } \ No newline at end of file diff --git a/tests/Parse/ParseCurlHttpClientTest.php b/tests/Parse/ParseCurlHttpClientTest.php new file mode 100644 index 00000000..cb163b27 --- /dev/null +++ b/tests/Parse/ParseCurlHttpClientTest.php @@ -0,0 +1,25 @@ +setup(); + $client->send("http://example.com"); + + $this->assertEquals(200, $client->getResponseStatusCode()); + + } +} \ No newline at end of file diff --git a/tests/Parse/ParseCurlTest.php b/tests/Parse/ParseCurlTest.php new file mode 100644 index 00000000..45b2e7bf --- /dev/null +++ b/tests/Parse/ParseCurlTest.php @@ -0,0 +1,86 @@ +setExpectedException(ParseException::class, + 'You must call ParseCurl::init first'); + + $parseCurl = new ParseCurl(); + $parseCurl->exec(); + + } + + public function testBadSetOption() + { + $this->setExpectedException(ParseException::class, + 'You must call ParseCurl::init first'); + + $parseCurl = new ParseCurl(); + $parseCurl->setOption(1, 1); + + } + + public function testBadSetOptionsArray() + { + $this->setExpectedException(ParseException::class, + 'You must call ParseCurl::init first'); + + $parseCurl = new ParseCurl(); + $parseCurl->setOptionsArray([]); + + } + + public function testBadGetInfo() + { + $this->setExpectedException(ParseException::class, + 'You must call ParseCurl::init first'); + + $parseCurl = new ParseCurl(); + $parseCurl->getInfo(1); + + } + + public function testBadGetError() + { + $this->setExpectedException(ParseException::class, + 'You must call ParseCurl::init first'); + + $parseCurl = new ParseCurl(); + $parseCurl->getError(); + + } + + public function testBadErrorCode() + { + $this->setExpectedException(ParseException::class, + 'You must call ParseCurl::init first'); + + $parseCurl = new ParseCurl(); + $parseCurl->getErrorCode(); + + } + + public function testBadClose() + { + $this->setExpectedException(ParseException::class, + 'You must call ParseCurl::init first'); + + $parseCurl = new ParseCurl(); + $parseCurl->close(); + + } +} \ No newline at end of file diff --git a/tests/Parse/ParseFileTest.php b/tests/Parse/ParseFileTest.php index 1a9dfae8..81e0c8f9 100644 --- a/tests/Parse/ParseFileTest.php +++ b/tests/Parse/ParseFileTest.php @@ -40,6 +40,9 @@ public function testParseFileFactories() ); } + /** + * @group file-upload-test + */ public function testParseFileUpload() { $file = ParseFile::createFromData('Fosco', 'test.txt'); diff --git a/tests/Parse/ParseObjectTest.php b/tests/Parse/ParseObjectTest.php index 2752e2b7..fbe5ad68 100644 --- a/tests/Parse/ParseObjectTest.php +++ b/tests/Parse/ParseObjectTest.php @@ -2,8 +2,11 @@ namespace Parse\Test; +use Parse\HttpClients\ParseCurlHttpClient; +use Parse\HttpClients\ParseStreamHttpClient; use Parse\Internal\SetOperation; use Parse\ParseACL; +use Parse\ParseClient; use Parse\ParseInstallation; use Parse\ParseObject; use Parse\ParsePushStatus; @@ -19,6 +22,11 @@ public static function setUpBeforeClass() Helper::setUp(); } + public function setUp() + { + Helper::setHttpClient(); + } + public function tearDown() { Helper::tearDown(); @@ -89,8 +97,10 @@ public function testFetch() $this->assertEquals('test', $t2->get('test'), 'Fetch failed.'); } - public function testDelete() + public function testDeleteStream() { + ParseClient::setHttpClient(new ParseStreamHttpClient()); + $obj = ParseObject::create('TestObject'); $obj->set('foo', 'bar'); $obj->save(); @@ -98,6 +108,21 @@ public function testDelete() $query = new ParseQuery('TestObject'); $this->setExpectedException('Parse\ParseException', 'Object not found'); $out = $query->get($obj->getObjectId()); + + } + + public function testDeleteCurl() + { + ParseClient::setHttpClient(new ParseCurlHttpClient()); + + $obj = ParseObject::create('TestObject'); + $obj->set('foo', 'bar'); + $obj->save(); + $obj->destroy(); + $query = new ParseQuery('TestObject'); + $this->setExpectedException('Parse\ParseException', 'Object not found'); + $out = $query->get($obj->getObjectId()); + } public function testFind() @@ -895,8 +920,10 @@ public function testObjectIsDirtyWithChildren() $this->assertFalse($childSimultaneous->isDirty()); } - public function testSaveAll() + public function testSaveAllStream() { + ParseClient::setHttpClient(new ParseStreamHttpClient()); + Helper::clearClass('TestObject'); $objs = []; for ($i = 1; $i <= 90; $i++) { @@ -908,8 +935,30 @@ public function testSaveAll() $query = new ParseQuery('TestObject'); $result = $query->find(); $this->assertEquals(90, count($result)); + } + public function testSaveAllCurl() + { + ParseClient::setHttpClient(new ParseCurlHttpClient()); + + Helper::clearClass('TestObject'); + $objs = []; + for ($i = 1; $i <= 90; $i++) { + $obj = ParseObject::create('TestObject'); + $obj->set('test', 'test'); + $objs[] = $obj; + } + ParseObject::saveAll($objs); + $query = new ParseQuery('TestObject'); + $result = $query->find(); + $this->assertEquals(90, count($result)); + + } + + /** + * @group test-empty-objects-arrays + */ public function testEmptyObjectsAndArrays() { $obj = ParseObject::create('TestObject'); diff --git a/tests/Parse/ParseRoleTest.php b/tests/Parse/ParseRoleTest.php index 00bdf396..e2b82ff2 100644 --- a/tests/Parse/ParseRoleTest.php +++ b/tests/Parse/ParseRoleTest.php @@ -97,6 +97,9 @@ public function testRoleNameUnique() } + /** + * @group explicit-role-acl + */ public function testExplicitRoleACL() { $eden = $this->createEden(); diff --git a/tests/Parse/ParseSchemaTest.php b/tests/Parse/ParseSchemaTest.php index 4b224246..75c90bf6 100644 --- a/tests/Parse/ParseSchemaTest.php +++ b/tests/Parse/ParseSchemaTest.php @@ -10,6 +10,9 @@ */ namespace Parse\Test; +use Parse\HttpClients\ParseCurlHttpClient; +use Parse\HttpClients\ParseStreamHttpClient; +use Parse\ParseClient; use Parse\ParseException; use Parse\ParseSchema; use Parse\ParseUser; @@ -35,6 +38,7 @@ public function setUp() { self::$schema = new ParseSchema('SchemaTest'); Helper::clearClass('_User'); + Helper::setHttpClient(); } @@ -133,8 +137,10 @@ public function testAllSchemaWithUserLoggedIn() $schema_2->delete(); } - public function testUpdateSchema() + public function testUpdateSchemaStream() { + ParseClient::setHttpClient(new ParseStreamHttpClient()); + // create $schema = self::$schema; $schema->addString('name'); @@ -153,6 +159,32 @@ public function testUpdateSchema() } $this->assertNotNull($result['fields']['quantity']); $this->assertNotNull($result['fields']['status']); + + } + + public function testUpdateSchemaCurl() + { + ParseClient::setHttpClient(new ParseCurlHttpClient()); + + // create + $schema = self::$schema; + $schema->addString('name'); + $schema->save(); + // update + $schema->deleteField('name'); + $schema->addNumber('quantity'); + $schema->addField('status', 'Boolean'); + $schema->update(); + // get + $getSchema = new ParseSchema('SchemaTest'); + $result = $getSchema->get(); + + if (isset($result['fields']['name'])) { + $this->fail('Field not deleted in update action'); + } + $this->assertNotNull($result['fields']['quantity']); + $this->assertNotNull($result['fields']['status']); + } public function testUpdateWrongFieldType() @@ -302,8 +334,7 @@ public function testInvalidTypeException() */ public function testBadSchemaGet() { - $this->setExpectedException(ParseException::class, - 'Empty reply from server'); + $this->setExpectedException(ParseException::class); $user = new ParseUser(); $user->setUsername('schema-user'); @@ -320,8 +351,7 @@ public function testBadSchemaGet() */ public function testBadSchemaSave() { - $this->setExpectedException(ParseException::class, - 'Empty reply from server'); + $this->setExpectedException(ParseException::class); $user = new ParseUser(); $user->setUsername('schema-user'); @@ -338,8 +368,7 @@ public function testBadSchemaSave() */ public function testBadSchemaUpdate() { - $this->setExpectedException(ParseException::class, - 'Empty reply from server'); + $this->setExpectedException(ParseException::class); $user = new ParseUser(); $user->setUsername('schema-user'); @@ -356,8 +385,7 @@ public function testBadSchemaUpdate() */ public function testBadSchemaDelete() { - $this->setExpectedException(ParseException::class, - 'Empty reply from server'); + $this->setExpectedException(ParseException::class); $user = new ParseUser(); $user->setUsername('schema-user'); diff --git a/tests/Parse/ParseStreamHttpClientTest.php b/tests/Parse/ParseStreamHttpClientTest.php new file mode 100644 index 00000000..e98a9952 --- /dev/null +++ b/tests/Parse/ParseStreamHttpClientTest.php @@ -0,0 +1,44 @@ +send('http://example.com'); + + // get response code + $this->assertEquals(200, $client->getResponseStatusCode()); + + // get response headers + $headers = $client->getResponseHeaders(); + + $this->assertEquals('HTTP/1.0 200 OK', $headers['http_code']); + + } + + public function testInvalidUrl() + { + $url = 'http://example.com/lots of spaces here'; + + $this->setExpectedException(ParseException::class, + 'Url may not contain spaces for stream client: ' + .$url); + + $client = new ParseStreamHttpClient(); + $client->send($url); + } +} \ No newline at end of file