Skip to content

Commit 9324d19

Browse files
authored
Adds Server Health Check (#366)
* Adds Server Health Check * lint * Updated coverage, removed echos * Clarification that the reported error & error_message can change
1 parent 18ec4d0 commit 9324d19

File tree

3 files changed

+230
-11
lines changed

3 files changed

+230
-11
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ from your PHP app or script. Designed to work with the self-hosted Parse Server
1414
- [Setup](#setup)
1515
- [Initializing](#initializing)
1616
- [Server URL](#server-url)
17+
- [Server Health Check](#server-health-check)
1718
- [Http Clients](#http-clients)
1819
- [Alternate CA files](#alternate-ca-file)
1920
- [Getting Started](#getting-started)
@@ -114,6 +115,46 @@ For example if your parse server's url is `http://example.com:1337/parse` then y
114115
ParseClient::setServerURL('https://example.com:1337','parse');
115116
```
116117

118+
### Server Health Check
119+
120+
To verify that the server url and mount path you've provided are correct you can run a health check on your server.
121+
```php
122+
$health = ParseClient::getServerHealth();
123+
if($health['status'] === 200) {
124+
// everything looks good!
125+
}
126+
```
127+
If you wanted to analyze it further the health response may look something like this.
128+
```json
129+
{
130+
"status" : 200,
131+
"response" : {
132+
"status" : "ok"
133+
}
134+
}
135+
```
136+
The 'status' being the http response code, and the 'response' containing what the server replies with.
137+
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.
138+
139+
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.
140+
141+
A couple examples of bad health responses could include an incorrect mount path, port or domain.
142+
```json
143+
// ParseClient::setServerURL('http://localhost:1337', 'not-good');
144+
{
145+
"status": 404,
146+
"response": "<!DOCTYPE html>...Cannot GET \/not-good\/health..."
147+
}
148+
149+
// ParseClient::setServerURL('http://__uh__oh__.com', 'parse');
150+
{
151+
"status": 0,
152+
"error": 6,
153+
"error_message": "Couldn't resolve host '__uh__oh__.com'"
154+
}
155+
```
156+
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.
157+
117158
### Http Clients
118159

119160
This SDK has the ability to change the underlying http client at your convenience.

src/Parse/ParseClient.php

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,57 @@ public static function setHttpClient(ParseHttpable $httpClient)
221221
self::$httpClient = $httpClient;
222222
}
223223

224+
/**
225+
* Returns an array of information regarding the current server's health
226+
*
227+
* @return array
228+
*/
229+
public static function getServerHealth()
230+
{
231+
self::assertServerInitialized();
232+
233+
// get our prepared http client
234+
$httpClient = self::getPreparedHttpClient();
235+
236+
// try to get a response from the server
237+
$url = self::createRequestUrl('health');
238+
$response = $httpClient->send($url);
239+
240+
$errorCode = $httpClient->getErrorCode();
241+
242+
if ($errorCode) {
243+
return [
244+
'status' => $httpClient->getResponseStatusCode(),
245+
'error' => $errorCode,
246+
'error_message' => $httpClient->getErrorMessage()
247+
];
248+
}
249+
250+
$status = [
251+
'status' => $httpClient->getResponseStatusCode(),
252+
];
253+
254+
// attempt to decode this response
255+
$decoded = json_decode($response, true);
256+
257+
if (isset($decoded)) {
258+
// add decoded response
259+
$status['response'] = $decoded;
260+
} else {
261+
if ($response === 'OK') {
262+
// implied status: ok!
263+
$status['response'] = [
264+
'status' => 'ok'
265+
];
266+
} else {
267+
// add plain response
268+
$status['response'] = $response;
269+
}
270+
}
271+
272+
return $status;
273+
}
274+
224275
/**
225276
* Gets the current Http client, or creates one to suite the need
226277
*
@@ -389,6 +440,38 @@ public static function _encodeArray($value, $allowParseObjects)
389440
return $output;
390441
}
391442

443+
/**
444+
* Returns an httpClient prepared for use
445+
*
446+
* @return ParseHttpable
447+
*/
448+
private static function getPreparedHttpClient()
449+
{
450+
// get our http client
451+
$httpClient = self::getHttpClient();
452+
453+
// setup
454+
$httpClient->setup();
455+
456+
if (isset(self::$caFile)) {
457+
// set CA file
458+
$httpClient->setCAFile(self::$caFile);
459+
}
460+
461+
return $httpClient;
462+
}
463+
464+
/**
465+
* Creates an absolute request url from a relative one
466+
*
467+
* @param string $relativeUrl Relative url to create full request url from
468+
* @return string
469+
*/
470+
private static function createRequestUrl($relativeUrl)
471+
{
472+
return self::$serverURL . '/' . self::$mountPath.ltrim($relativeUrl, '/');
473+
}
474+
392475
/**
393476
* Parse\Client::_request, internal method for communicating with Parse.
394477
*
@@ -419,16 +502,8 @@ public static function _request(
419502
$data = '{}';
420503
}
421504

422-
// get our http client
423-
$httpClient = self::getHttpClient();
424-
425-
// setup
426-
$httpClient->setup();
427-
428-
if (isset(self::$caFile)) {
429-
// set CA file
430-
$httpClient->setCAFile(self::$caFile);
431-
}
505+
// get our prepared http client
506+
$httpClient = self::getPreparedHttpClient();
432507

433508
// verify the server url and mount path have been set
434509
self::assertServerInitialized();
@@ -473,7 +548,7 @@ public static function _request(
473548
$httpClient->addRequestHeader('Expect', '');
474549

475550
// create request url
476-
$url = self::$serverURL . '/' . self::$mountPath.ltrim($relativeUrl, '/');
551+
$url = self::createRequestUrl($relativeUrl);
477552

478553
if ($method === 'POST' || $method === 'PUT') {
479554
// add content type to the request

tests/Parse/ParseClientTest.php

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,4 +584,107 @@ public function testBadApiResponse()
584584
$obj = new ParseObject('TestingClass');
585585
$obj->save();
586586
}
587+
588+
/**
589+
* @group check-server
590+
*/
591+
public function testCheckServer()
592+
{
593+
$health = ParseClient::getServerHealth();
594+
595+
$this->assertNotNull($health);
596+
$this->assertEquals($health['status'], 200);
597+
$this->assertEquals($health['response']['status'], 'ok');
598+
}
599+
600+
/**
601+
* Structured response present in modified/later versions of parse-server
602+
*
603+
* @group check-server
604+
*/
605+
public function testStructuredHealthResponse()
606+
{
607+
$httpClient = ParseClient::getHttpClient();
608+
609+
// create a mock of the current http client
610+
$stubClient = $this->getMockBuilder(get_class($httpClient))
611+
->getMock();
612+
613+
// stub the response type to return
614+
// something we will try to work with
615+
$stubClient
616+
->method('getResponseContentType')
617+
->willReturn('application/octet-stream');
618+
619+
$stubClient
620+
->method('getResponseStatusCode')
621+
->willReturn(200);
622+
623+
$stubClient
624+
->method('send')
625+
->willReturn('{"status":"ok"}');
626+
627+
// replace the client with our stub
628+
ParseClient::setHttpClient($stubClient);
629+
630+
$health = ParseClient::getServerHealth();
631+
632+
$this->assertNotNull($health);
633+
$this->assertEquals($health['status'], 200);
634+
$this->assertEquals($health['response']['status'], 'ok');
635+
}
636+
637+
/**
638+
* Plain response present in earlier versions of parse-server (from 2.2.25 on)
639+
* @group check-server
640+
*/
641+
public function testPlainHealthResponse()
642+
{
643+
$httpClient = ParseClient::getHttpClient();
644+
645+
// create a mock of the current http client
646+
$stubClient = $this->getMockBuilder(get_class($httpClient))
647+
->getMock();
648+
649+
// stub the response type to return
650+
// something we will try to work with
651+
$stubClient
652+
->method('getResponseContentType')
653+
->willReturn('text/plain');
654+
655+
$stubClient
656+
->method('getResponseStatusCode')
657+
->willReturn(200);
658+
659+
$stubClient
660+
->method('send')
661+
->willReturn('OK');
662+
663+
// replace the client with our stub
664+
ParseClient::setHttpClient($stubClient);
665+
666+
$health = ParseClient::getServerHealth();
667+
668+
$this->assertNotNull($health);
669+
$this->assertEquals($health['status'], 200);
670+
$this->assertEquals($health['response']['status'], 'ok');
671+
}
672+
673+
/**
674+
* @group check-server
675+
*/
676+
public function testCheckBadServer()
677+
{
678+
ParseClient::setServerURL('http://localhost:1337', 'not-a-real-endpoint');
679+
$health = ParseClient::getServerHealth();
680+
$this->assertNotNull($health);
681+
$this->assertFalse(isset($health['error']));
682+
$this->assertFalse(isset($health['error_message']));
683+
$this->assertEquals($health['status'], 404);
684+
685+
ParseClient::setServerURL('http://___uh___oh___.com', 'parse');
686+
$health = ParseClient::getServerHealth();
687+
$this->assertTrue(isset($health['error']));
688+
$this->assertTrue(isset($health['error_message']));
689+
}
587690
}

0 commit comments

Comments
 (0)