PHP Consuming RESTful APIs Debugging the Raw HTTP Exchange - Supercoders | Web Development and Design | Tutorial for Java, PHP, HTML, Javascript PHP Consuming RESTful APIs Debugging the Raw HTTP Exchange - Supercoders | Web Development and Design | Tutorial for Java, PHP, HTML, Javascript

Breaking

Post Top Ad

Post Top Ad

Thursday, June 20, 2019

PHP Consuming RESTful APIs Debugging the Raw HTTP Exchange

PHP Consuming RESTful APIs


Debugging the Raw HTTP Exchange


Problem

You want to analyze the HTTP request a browser makes to your server and the corresponding HTTP response. For example, your server doesn’t supply the expected response to a particular request so you want to see exactly what the components of the request are.

Solution

For simple requests, connect to the web server with Telnet and type in the request headers. A sample exchange looks like:

       POST /submit.php HTTP/1.1
       User-Agent: PEAR HTTP_Request2 class ( http://pear.php.net/ )
       Content-Type: application/x-www-form-urlencoded
       Connection: close
       Host: www.example.com
       Content-Length: 12

       monkey=uncle

Discussion

When you type in request headers, the web server doesn’t know that it’s just you typing and not a web browser submitting a request. However, some web servers have timeouts on how long they’ll wait for a request, so it can be useful to pretype the request and then just paste it into Telnet. The first line of the request contains the request method (POST), a space and the path of the file you want (/submit.php), and then a space and the protocol you’re using (HTTP/1.1). A subsequent line, the Host header, tells the server which virtual host to use if many are sharing the same IP address. A blank line tells the server that the request is over; it then spits back its response: first headers, then a blank line, and then the body of the response. The Netcat program is also useful for this sort of task.

Pasting text into Telnet can get tedious, and it’s even harder to make requests with the POST method that way. If you make a request with HTTP_Request2, you can retrieve the response headers and the response body with the getResponseHeader() and getResponseBody() methods, as shown:

       require 'HTTP/Request2.php';
       $r = new HTTP_Request2('http://www.example.com/submit.php');
       $r = new HTTP_Request2('http://localhost/submit.php');
       $r->setMethod(HTTP_Request2::METHOD_POST)
              ->addPostParameter('monkey', 'uncle');
       $response = $r->send();

       $response_headers = $response->getHeader();
       $response_body = $response->getBody();

To retrieve a specific response header, pass the header name to getResponseHeader(). The header name must be all lowercase. Without an argument, getResponseHeader() returns an array containing all the response headers. HTTP_Request2 saves the outgoing request. Access it by calling the getLastEvent() method, as shown:

       require 'HTTP/Request2.php';

       $r = new HTTP_Request2('http://www.example.com/submit.php');
       $r = new HTTP_Request2('http://localhost/submit.php');
       $r->setMethod(HTTP_Request2::METHOD_POST)
              ->addPostParameter('monkey', 'uncle');
       $response = $r->send();

       print_r($r->getLastEvent());

That request is something like:
  
       POST /submit.php HTTP/1.1
       User-Agent: PEAR HTTP_Request2 class ( http://pear.php.net/ )
       Content-Type: application/x-www-form-urlencoded
       Connection: close
       Host: www.example.com
       Content-Length: 12

       monkey=uncle

Accessing response headers with the http stream is possible, but you have to use a function such as fopen() that gives you a stream resource. One piece of the metadata you get when passing that stream resource to stream_get_meta_data() after the request has been made is the set of response headers. This example demonstrates how to access response headers with a stream resource:

       $url = 'http://www.example.com/submit.php';
       $stream = fopen($url, 'r');
       $metadata = stream_get_meta_data($stream);
       // The headers are stored in the 'wrapper_data'
       foreach ($metadata['wrapper_data'] as $header) {
              print $header . "\n";
       }
       // The body can be retrieved with
       // stream_get_contents()
       $response_body = stream_get_contents($stream);

stream_get_meta_data() returns an array of information about the stream. The wrapper_data element of that array contains wrapper-specific data. For the http wrapper, that means the response headers, one per subarray element. It prints something like:

       HTTP/1.1 200 OK
       Date: Sun, 07 May 2014 18:24:37 GMT
       Server: Apache/2.2.2 (Unix)
       Last-Modified: Sun, 07 May 2014 01:58:12 GMT
       ETag: "1348011-7-16167502"
       Accept-Ranges: bytes
       Content-Length: 7
       Connection: close
       Content-Type: text/plain

The fopen() function accepts an optional stream context. Pass it as the fourth argument to fopen() if you want to use one. (The second argument is the mode and the third argument is the optional flag indicating whether to use include_path in looking for a file.)

With cURL, include response headers in the output from curl_exec() by setting the CURLOPT_HEADER option, as shown:

       $c = curl_init('http://www.example.com/submit.php');
       curl_setopt($c, CURLOPT_HEADER, true);
       curl_setopt($c, CURLOPT_POST, true);
       curl_setopt($c, CURLOPT_POSTFIELDS, 'monkey=uncle&rhino=aunt');
       curl_setopt($c, CURLOPT_RETURNTRANSFER, true);

       $response_headers_and_page = curl_exec($c);
       curl_close($c);

To write the response headers directly to a file, open a filehandle with fopen() and set CURLOPT_WRITEHEADER to that filehandle, as shown:

       $fh = fopen('/tmp/curl-response-headers.txt','w') or die($php_errormsg);
       $c = curl_init('http://www.example.com/submit.php');
       curl_setopt($c, CURLOPT_POST, true);
       curl_setopt($c, CURLOPT_POSTFIELDS, 'monkey=uncle&rhino=aunt');
       curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
       curl_setopt($c, CURLOPT_WRITEHEADER, $fh);
       $page = curl_exec($c);
       curl_close($c);
       fclose($fh) or die($php_errormsg);

cURL’s CURLOPT_VERBOSE option causes curl_exec() and curl_close() to print out debugging information to standard error, including the contents of the request, as shown:

       $c = curl_init('http://www.example.com/submit.php');
       curl_setopt($c, CURLOPT_VERBOSE, true);
       curl_setopt($c, CURLOPT_POST, true);
       curl_setopt($c, CURLOPT_POSTFIELDS, 'monkey=uncle&rhino=aunt');
       curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
       $page = curl_exec($c);
       curl_close($c);

It prints something like:

       * Connected to www.example.com (10.1.1.1)
       > POST /submit.php HTTP/1.1
       Host: www.example.com
       Pragma: no-cache
       Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
       Content-Length: 23
       Content-Type: application/x-www-form-urlencoded

       monkey=uncle&rhino=aunt* Connection #0 left intact
       * Closing connection #0

Because cURL prints the debugging information to standard error and not standard output, it can’t be captured with output buffering. You can, however, open a filehandle for writing and set CURLOUT_STDERR to that filehandle to divert the debugging information to a file:

       $fh = fopen('/tmp/curl.out','w') or die($php_errormsg);
       $c = curl_init('http://www.example.com/submit.php');
       curl_setopt($c, CURLOPT_VERBOSE, true);
       curl_setopt($c, CURLOPT_POST, true);
       curl_setopt($c, CURLOPT_POSTFIELDS, 'monkey=uncle&rhino=aunt');
       curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
       curl_setopt($c, CURLOPT_STDERR, $fh);
       $page = curl_exec($c);
       curl_close($c);
       fclose($fh) or die($php_errormsg);

Another way to access response headers with cURL is to write a header function. This is similar to a cURL write function except it is called to handle response headers instead of the response body. This example defines a HeaderSaver class whose header()method can be used as a header function to accumulate response headers:

       class HeaderSaver {
              public $headers = array();
              public $code = null;

              public function header($curl, $data){
                     if (is_null($this->code) &&
                           preg_match('@^HTTP/\d\.\d (\d+) @',$data,$matches)) {
                           $this->code = $matches[1];
                     } else {
                           // Remove the trailing newline
                           $trimmed = rtrim($data);
                           if (strlen($trimmed)) {
                                 // If this line begins with a space or tab, it's a
                                 // continuation of the previous header
                                 if (($trimmed[0] == ' ') || ($trimmed[0] == "\t")) {
                                      // Collapse the leading whitespace into one space
                                      $trimmed = preg_replace('@^[ \t]+@',' ', $trimmed);
                                      $this->headers[count($this->headers)-1] .= $trimmed;
                                 }
                                 // Otherwise, it's a new header
                                 else {
                                      $this->headers[] = $trimmed;
                                 }
                           }
                     }
                     return strlen($data);
              }
       }

       $h = new HeaderSaver();
       $c = curl_init('http://www.example.com/plankton.php');
       // Register the header function
       curl_setopt($c, CURLOPT_HEADERFUNCTION, array($h,'header'));
       curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
       $page = curl_exec($c);
       // Now $h is populated with data
       print 'The response code was: ' . $h->code . "\n";
       print "The response headers were: \n";
       foreach ($h->headers as $header) {
              print " $header\n";
       }

The HTTP 1.1 standard specifies that headers can span multiple lines by putting at least one space or tab character at the beginning of the additional lines of the header. The header arrays returned by stream_get_meta_data() and HTTP_Request2::getResponseHeader() do not properly handle multiline headers, though. The additional lines in a header are treated as separate headers. This code, however, correctly combines the additional lines in multiline headers.



No comments:

Post a Comment

Post Top Ad