diff --git a/README.md b/README.md index aa120964..f71b30e7 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ from your PHP app or script. Designed to work with the self-hosted Parse Server - [Setup](#setup) - [Initializing](#initializing) - [Server URL](#server-url) + - [Server Health Check](#server-health-check) - [Http Clients](#http-clients) - [Alternate CA files](#alternate-ca-file) - [Getting Started](#getting-started) @@ -112,6 +113,46 @@ For example if your parse server's url is `http://example.com:1337/parse` then y ParseClient::setServerURL('https://example.com:1337','parse'); ``` +### Server Health Check + +To verify that the server url and mount path you've provided are correct you can run a health check on your server. +```php +$health = ParseClient::getServerHealth(); +if($health['status'] === 200) { + // everything looks good! +} +``` +If you wanted to analyze it further the health response may look something like this. +```json +{ + "status" : 200, + "response" : { + "status" : "ok" + } +} +``` +The 'status' being the http response code, and the 'response' containing what the server replies with. +Any additional details in the reply can be found under 'response', and you can use them to check and determine the availability of parse-server before you make requests. + +Note that it is _not_ guaranteed that 'response' will be a parsable json array. If the response cannot be decoded it will be returned as a string instead. + +A couple examples of bad health responses could include an incorrect mount path, port or domain. +```json +// ParseClient::setServerURL('http://localhost:1337', 'not-good'); +{ + "status": 404, + "response": "...Cannot GET \/not-good\/health..." +} + +// ParseClient::setServerURL('http://__uh__oh__.com', 'parse'); +{ + "status": 0, + "error": 6, + "error_message": "Couldn't resolve host '__uh__oh__.com'" +} +``` +Keep in mind `error` & `error_message` may change depending on whether you are using the **curl** (may change across versions of curl) or **stream** client. + ### Http Clients This SDK has the ability to change the underlying http client at your convenience. diff --git a/src/Parse/ParseClient.php b/src/Parse/ParseClient.php index 8bd7cb9e..bdabef9c 100755 --- a/src/Parse/ParseClient.php +++ b/src/Parse/ParseClient.php @@ -221,6 +221,57 @@ public static function setHttpClient(ParseHttpable $httpClient) self::$httpClient = $httpClient; } + /** + * Returns an array of information regarding the current server's health + * + * @return array + */ + public static function getServerHealth() + { + self::assertServerInitialized(); + + // get our prepared http client + $httpClient = self::getPreparedHttpClient(); + + // try to get a response from the server + $url = self::createRequestUrl('health'); + $response = $httpClient->send($url); + + $errorCode = $httpClient->getErrorCode(); + + if ($errorCode) { + return [ + 'status' => $httpClient->getResponseStatusCode(), + 'error' => $errorCode, + 'error_message' => $httpClient->getErrorMessage() + ]; + } + + $status = [ + 'status' => $httpClient->getResponseStatusCode(), + ]; + + // attempt to decode this response + $decoded = json_decode($response, true); + + if (isset($decoded)) { + // add decoded response + $status['response'] = $decoded; + } else { + if ($response === 'OK') { + // implied status: ok! + $status['response'] = [ + 'status' => 'ok' + ]; + } else { + // add plain response + $status['response'] = $response; + } + } + + return $status; + } + /** * Gets the current Http client, or creates one to suite the need * @@ -389,6 +440,38 @@ public static function _encodeArray($value, $allowParseObjects) return $output; } + /** + * Returns an httpClient prepared for use + * + * @return ParseHttpable + */ + private static function getPreparedHttpClient() + { + // get our http client + $httpClient = self::getHttpClient(); + + // setup + $httpClient->setup(); + + if (isset(self::$caFile)) { + // set CA file + $httpClient->setCAFile(self::$caFile); + } + + return $httpClient; + } + + /** + * Creates an absolute request url from a relative one + * + * @param string $relativeUrl Relative url to create full request url from + * @return string + */ + private static function createRequestUrl($relativeUrl) + { + return self::$serverURL . '/' . self::$mountPath.ltrim($relativeUrl, '/'); + } + /** * Parse\Client::_request, internal method for communicating with Parse. * @@ -419,16 +502,8 @@ public static function _request( $data = '{}'; } - // get our http client - $httpClient = self::getHttpClient(); - - // setup - $httpClient->setup(); - - if (isset(self::$caFile)) { - // set CA file - $httpClient->setCAFile(self::$caFile); - } + // get our prepared http client + $httpClient = self::getPreparedHttpClient(); // verify the server url and mount path have been set self::assertServerInitialized(); @@ -473,7 +548,7 @@ public static function _request( $httpClient->addRequestHeader('Expect', ''); // create request url - $url = self::$serverURL . '/' . self::$mountPath.ltrim($relativeUrl, '/'); + $url = self::createRequestUrl($relativeUrl); if ($method === 'POST' || $method === 'PUT') { // add content type to the request diff --git a/tests/Parse/ParseClientTest.php b/tests/Parse/ParseClientTest.php index f4e38419..741eb724 100644 --- a/tests/Parse/ParseClientTest.php +++ b/tests/Parse/ParseClientTest.php @@ -584,4 +584,107 @@ public function testBadApiResponse() $obj = new ParseObject('TestingClass'); $obj->save(); } + + /** + * @group check-server + */ + public function testCheckServer() + { + $health = ParseClient::getServerHealth(); + + $this->assertNotNull($health); + $this->assertEquals($health['status'], 200); + $this->assertEquals($health['response']['status'], 'ok'); + } + + /** + * Structured response present in modified/later versions of parse-server + * + * @group check-server + */ + public function testStructuredHealthResponse() + { + $httpClient = ParseClient::getHttpClient(); + + // create a mock of the current http client + $stubClient = $this->getMockBuilder(get_class($httpClient)) + ->getMock(); + + // stub the response type to return + // something we will try to work with + $stubClient + ->method('getResponseContentType') + ->willReturn('application/octet-stream'); + + $stubClient + ->method('getResponseStatusCode') + ->willReturn(200); + + $stubClient + ->method('send') + ->willReturn('{"status":"ok"}'); + + // replace the client with our stub + ParseClient::setHttpClient($stubClient); + + $health = ParseClient::getServerHealth(); + + $this->assertNotNull($health); + $this->assertEquals($health['status'], 200); + $this->assertEquals($health['response']['status'], 'ok'); + } + + /** + * Plain response present in earlier versions of parse-server (from 2.2.25 on) + * @group check-server + */ + public function testPlainHealthResponse() + { + $httpClient = ParseClient::getHttpClient(); + + // create a mock of the current http client + $stubClient = $this->getMockBuilder(get_class($httpClient)) + ->getMock(); + + // stub the response type to return + // something we will try to work with + $stubClient + ->method('getResponseContentType') + ->willReturn('text/plain'); + + $stubClient + ->method('getResponseStatusCode') + ->willReturn(200); + + $stubClient + ->method('send') + ->willReturn('OK'); + + // replace the client with our stub + ParseClient::setHttpClient($stubClient); + + $health = ParseClient::getServerHealth(); + + $this->assertNotNull($health); + $this->assertEquals($health['status'], 200); + $this->assertEquals($health['response']['status'], 'ok'); + } + + /** + * @group check-server + */ + public function testCheckBadServer() + { + ParseClient::setServerURL('http://localhost:1337', 'not-a-real-endpoint'); + $health = ParseClient::getServerHealth(); + $this->assertNotNull($health); + $this->assertFalse(isset($health['error'])); + $this->assertFalse(isset($health['error_message'])); + $this->assertEquals($health['status'], 404); + + ParseClient::setServerURL('http://___uh___oh___.com', 'parse'); + $health = ParseClient::getServerHealth(); + $this->assertTrue(isset($health['error'])); + $this->assertTrue(isset($health['error_message'])); + } }