From 059e7e078856a6c07b6e8b55cae8e66512e60a31 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Thu, 2 Nov 2017 18:47:02 -0700 Subject: [PATCH 1/4] Adds Server Health Check --- README.md | 40 ++++++++++++++ src/Parse/ParseClient.php | 97 +++++++++++++++++++++++++++++---- tests/Parse/ParseClientTest.php | 34 ++++++++++++ 3 files changed, 160 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index aa120964..2aa2d068 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,45 @@ 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'" +} +``` + ### 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..d8114f2b 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..61fda8e9 100644 --- a/tests/Parse/ParseClientTest.php +++ b/tests/Parse/ParseClientTest.php @@ -584,4 +584,38 @@ 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'); + } + + /** + * @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); + + echo json_encode($health, JSON_PRETTY_PRINT); + + ParseClient::setServerURL('http://___uh___oh___.com', 'parse'); + $health = ParseClient::getServerHealth(); + $this->assertTrue(isset($health['error'])); + $this->assertTrue(isset($health['error_message'])); + + echo json_encode($health, JSON_PRETTY_PRINT); + } } From 17b8dbcb05b573b842485396813935df8c31b0b2 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Thu, 2 Nov 2017 19:06:49 -0700 Subject: [PATCH 2/4] lint --- src/Parse/ParseClient.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Parse/ParseClient.php b/src/Parse/ParseClient.php index d8114f2b..bdabef9c 100755 --- a/src/Parse/ParseClient.php +++ b/src/Parse/ParseClient.php @@ -239,7 +239,7 @@ public static function getServerHealth() $errorCode = $httpClient->getErrorCode(); - if($errorCode) { + if ($errorCode) { return [ 'status' => $httpClient->getResponseStatusCode(), 'error' => $errorCode, @@ -254,11 +254,11 @@ public static function getServerHealth() // attempt to decode this response $decoded = json_decode($response, true); - if(isset($decoded)) { + if (isset($decoded)) { // add decoded response $status['response'] = $decoded; } else { - if($response === 'OK') { + if ($response === 'OK') { // implied status: ok! $status['response'] = [ 'status' => 'ok' From 6832fbb6871e09dd083ff158e9f987fee8c15bd2 Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Thu, 2 Nov 2017 21:52:28 -0700 Subject: [PATCH 3/4] Updated coverage, removed echos --- tests/Parse/ParseClientTest.php | 77 +++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/tests/Parse/ParseClientTest.php b/tests/Parse/ParseClientTest.php index 61fda8e9..741eb724 100644 --- a/tests/Parse/ParseClientTest.php +++ b/tests/Parse/ParseClientTest.php @@ -597,6 +597,79 @@ public function testCheckServer() $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 */ @@ -609,13 +682,9 @@ public function testCheckBadServer() $this->assertFalse(isset($health['error_message'])); $this->assertEquals($health['status'], 404); - echo json_encode($health, JSON_PRETTY_PRINT); - ParseClient::setServerURL('http://___uh___oh___.com', 'parse'); $health = ParseClient::getServerHealth(); $this->assertTrue(isset($health['error'])); $this->assertTrue(isset($health['error_message'])); - - echo json_encode($health, JSON_PRETTY_PRINT); } } From 5dae4980d7a0ee71e609eb155715526d8b71b95b Mon Sep 17 00:00:00 2001 From: Benjamin Friedman Date: Fri, 3 Nov 2017 18:02:27 -0700 Subject: [PATCH 4/4] Clarification that the reported error & error_message can change --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2aa2d068..f71b30e7 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ A couple examples of bad health responses could include an incorrect mount path, "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