How to: PECL HTTP request exception and error handling

In a previous post, we created a simple PHP page that told us if http://www.example.com is up or down by using the PECL HTTP extension to make an HTTP request to the site and look for the string “example” in the response. Here is the code for our test page, httptest.php:

<?php

$http_req = new HttpRequest("http://www.example.com");
$http_req->setOptions(array(timeout=>10,useragent=>"MyScript"));
$http_req->send();

if (stripos($http_req->getResponseBody(), "example") === false){
    echo "The page is down!";
} else {
    echo "The page is up!";
}

?>

The problem with this code is that there is no error handling. Below are a few examples of what can go wrong and the resulting errors:

DNS Lookup Failure

If the DNS lookup fails the page will return the following error:

Fatal error: Uncaught exception 'HttpInvalidParamException' with message 'Empty or too short HTTP message: ''' in /var/www/httptest.php:12 inner exception 'HttpRequestException' with message 'Couldn’t resolve host name; Couldn’t resolve host 'www.somewebsitethatdoesnotexist.com'
(http://www.somewebsitethatdoesnotexist.com/)' in /var/www/httptest.php:4 Stack trace: #0 /var/www/httptest.php(12): HttpRequest->send() #1 {main} thrown in /var/www/httptest.php on line 12

Since www.example.com is a valid DNS name I used “www.somewebsitethatdoesnotexist.com” instead to demonstrate what happens with an invalid name or failed DNS lookup. Note the “inner exception” that says “Couldn’t resolve host name”. More on “inner exceptions” in a bit. This is not very pretty for a diagnostic page.

Connection Failure

In this example I again used “www.somewebsitethatdoesnotexist.com” but I added the following entry to the /etc/hosts file on the server:

10.10.10.10 www.somewebsitethatdoesnotexist.com

Now the DNS entry will resolve using the /etc/hosts file but this is not a valid IP for any machine on my neetwork so I see this error:

Fatal error: Uncaught exception ‘HttpInvalidParamException’ with message ‘Empty or too short HTTP message: ''' in /var/www/httptest.php:12 inner exception ‘HttpRequestException’ with message ‘Timeout was reached; connect() timed out! (http://www.somewebsitethatdoesnotexist.com/)’ in /var/www/httptest.php:4 Stack trace: #0 /var/www/httptest.php(12): HttpRequest->send() #1 {main} thrown in /var/www/httptest.php on line 12

Again we have a inner exception buried in all of that telling me that the connection time out.

404 Error

In this example I put in “http://localhost/notarealpage.php” for the URL. This will connect to the local Apache server but that page doesn’t exist so the server will return a 404 file not found error. The server responded but since we are not checking the response code from the server our code just tells us the page is down and that is true but it would be useful to know that it is because the page is missing!

The page is down!

If the server responds OK we will get a 200 status code. We should handle any other response appropriately.

Handle the exceptions

The first thing we can do is put a try catch block around our code and try catching the HttpException as shown in example section of the documentation for the HttpRequest::send method:

<?php

$http_req = new HttpRequest("http://www.example.com");
$http_req->setOptions(array(timeout=>10,useragent=>"MyScript"));

try {
    $http_req->send();
} catch (HttpException $ex) {
    echo $ex->getMessage();
}

if (stripos($http_req->getResponseBody(), "example") === false){
    echo "The page is down!";
} else {
    echo "The page is up!";
}

?>

If there is a time out or connection failure the HttpException is caught and we see this:

Empty or too short HTTP message: ''The page is down!

Hmm… that is not very informative and the same error is displayed for both a name lookup failure and a connection timeout. We can also try changing:

echo $ex->getMessage();
to
echo $ex;

Now we get this:

exception 'HttpInvalidParamException' with message 'Empty or too short HTTP message: ''' in /var/www/httptest.php:16 inner exception 'HttpRequestException' with message 'Couldn’t resolve host name; Couldn’t resolve host 'www.ssomewebsitethatdoesnotexist.com'
(http://www.ssomewebsitethatdoesnotexist.com/)' in /var/www/httptest.php:5 Stack trace: #0 /var/www/httptest.php(16): HttpRequest->send() #1 {main}The page is down!

That is painfully ugly but at least get the “Couldn’t resolve host name” message in there so we know what went wrong. Still, we can do better.

In addition to putting a try-catch around the send() method you probably should surround all of your HttpRequest code with a try-catch that eventually catches “Exception” to be safe.

The undocumented inner exception

The HttpException object, which is not really documented all that well as of this writing, has something completely undocumented called an inner exception. The inner exception is a more detailed exception that is nested inside the HttpException object. We can check if an inner exception is set and if so, display that instead:

<?php

$http_req = new HttpRequest("http://www.example.com");
$http_req->setOptions(array(timeout=>10,useragent=>"MyScript"));

try {
    $http_req->send();
} catch (HttpException $ex) {
    if (isset($ex->innerException)){
        echo $ex->innerException->getMessage();
        exit;
    } else {
        echo $ex;
        exit;
    }
}

if (stripos($http_req->getResponseBody(), "example") === false){
    echo "The page is down!";
} else {
    echo "The page is up!";
}

?>

Now we get just the part we are interested in:

Couldn’t resolve host name; Couldn’t resolve host 'www.ssomewebsitethatdoesnotexist.com'
(http://www.ssomewebsitethatdoesnotexist.com/)

If the lookup is OK but we get a connection timeout we now see this:

Timeout was reached; connect() timed out! (http://www.somewebsitethatdoesnotexist.com/)

If no inner exception is detected the HttpException is echoed.

Check status codes

Sometimes the server may be responding but we do not get a 200 status. This could because of a redirect, security error, missing page, or a 500 server error. The HttpRequest object provides a getResponseCode() method so we can check what the response was and handle it appropriately. If redirects are followed the last received response is used. For this example we will simply echo out some of the common status codes if we don’t get a 200:

<?php

$http_req = new HttpRequest("http://www.example.com/blah");
$http_req->setOptions(array(timeout=>10,useragent=>"MyScript"));

try {
    $http_req->send();
} catch (HttpException $ex) {
    if (isset($ex->innerException)){
        echo $ex->innerException->getMessage();
        exit;
    } else {
        echo $ex;
        exit;
    }
}

$response_code = $http_req->getResponseCode();

switch ($response_code){
    case 200:
    break;
    case 301:
    echo "301 Moved Permanently";
    exit;
    case 401:
    echo "401 Unauthorized";
    exit;
    case 404:
    echo "404 File Not Found";
    exit;
    case 500:
    echo "500 Internal Server Error";
    exit;
    default:
    echo "Did not receive a 200 response code from the server";
    exit;
}

if (stripos($http_req->getResponseBody(), "example") === false){
    echo "The page is down!";
} else {
    echo "The page is up!";
}

?>

This handles a few of the more common response/status codes. To test the code we can put in a valid URL but a bogus page (I.e. http://www.example.com/blah) If everything works right we now see the following response from our diagnostic page:

404 File Not Found

Final Notes

Our little diagnostic page can now handle most of the errors it will likely encounter when it attempts to test our example site, example.com. If we wanted to take this a bit further we could add a database back-end that maintains a list of multiple sites we would like to test. To take things a step further we could execute this PHP script from the command line via a cron job that runs every 5 minutes. We could then have the script send an e-mail when a site was down with the problem indicated in the message. If we wanted to maintain some up-times stats would could log the outages to a database and generate uptime/SLA reports daily, weekly, yearly, etc.

In reality, I would just use something like IPSentry or Nagios to conserve effort for future generations but it was nice to think about. 😉 Happy coding!

6 Comments

Add Yours →

I’ve been getting too many exceptions using HttpRequest::send() of this type:
HttpInvalidParamException with message “Empty or too short HTTP message: ”” and inner exception HttpRequestException with message “a timeout was reached; connect() timed out!”

The frequency at which this occurred was suspicious so I wrapped the send() in a loop of max 3 attempts and now the code always works even though the 1st attempt sometimes fails.

I’ve had this same problem on different servers with different OS’ and different code so there’s obviously something broken in the PECL http code.

I decided to replace all the PECL Http code and this ridiculous workaround with CURL code and I haven’t had a problem since.

PECL HTTP library seems to use the „pipelining“ feature of cURL, which somehows caches the open handles for next HTTP requests.

I minimized the amount of produced error just by calling

http_persistent_handles_clean()

before each HttpRequest. This solution has minimized the amount of errors to minimum.

exception ‘HttpInvalidParamException’ with message ‘Empty or too short HTTP message: ”’ in /home/tsjeyasarathi/Desktop/MAC_123456789efd/myownssl.php:0
i m using ssl connection to server frrm linux machine frm comment line ..

when executing my php code

try {
http_persistent_handles_clean();
$request = new HttpRequest(‘https://115.115.65.2:7788/index.php/download_manager/requestserver/index/17’);
$data=$request->seturl($path);
$ssl_options = array(‘verifypeer’ => FALSE,
‘verifyhost’ => 1,

‘cert’ => ‘/home/tsjeyasarathi/Desktop/MAC_123456789efd/cluster/keys/devicecert.pem’,
‘certtype’ => ‘PEM’,
‘key’ =>’/home/tsjeyasarathi/Desktop/MAC_123456789efd/cluster/keys/devicekey.pem’,
‘keytype’ => ‘PEM’,
‘cainfo’ => ‘/home/tsjeyasarathi/Desktop/MAC_123456789efd/cluster/keys/cacert.pem’,

‘version’ => 3,
‘certpasswd’ => ‘0000123456789efd’
);
$request->setSslOptions($ssl_options);
var_dump($request->getSslOptions());
//

$data=$request->seturl($path);
$request->send();
if ($request->getResponseCode()) {
echo “hai”;
echo”value $data”;
file_put_contents(‘local.rss’, $r->getResponseBody());
}
}
catch (HttpException $ex) {
echo $ex;
}

got the following..

inner exception ‘HttpRequestException’ with message ‘SSL connect error; error reading X.509 key or certificate file (https://115.115.65.2:7788/index.php/download_manager/requestserver/index/17)’ in /home/tsjeyasarathi/Desktop/MAC_123456789efd/myownssl.php:34
Stack trace:
#0 /home/tsjeyasarathi/Desktop/MAC_123456789efd/myownssl.php(0): HttpRequest->send()
#1 {main}tsjeyasarathi@tsjeyasarathi-VirtualBox:~/Desktop/MAC_123456789efd$ php myownssl.php

please help

Leave a Reply