13 Jan 2010, 6:21 p.m.

HTTP in PHP, Part 1: The Request

When I wrote about HTTP for php|architect magazine, one of the topics that I ended up having to skim over for wordcount reasons was that of how to work with HTTP from within PHP.

So here's a quick overview of how to make and manipulate HTTP requests using PHP. I'll hopefully follow this up in due course with a counterpart post dealing with HTTP responses.

file_get_contents()

It's always best to keep things simple wherever possible, and the very simplest method of making an HTTP request in PHP is through use of the file_get_contents() function. When this function is passed a URL as a parameter, rather than a path to a local file, it will retrieve it using the appropriate protocol - in our case HTTP. Here's an example of that in action:


<?php

$url  = 'http://example.org/';
$page = file_get_contents($url);

That's really all there is to it [1], and $page now contains the body of the HTTP response, for example the HTML source of a web page, or the binary data of an image file.

The HTTP request headers sent by file_get_contents() in this particular example are very basic. Let's have a look at them:

GET / HTTP/1.1
Host: example.org

That's the bare minimum required for an HTTP 1.1 request, and should serve you well in many cases, but what if we need more control over the HTTP request and the headers that are sent? Stream contexts to the rescue!

Stream Contexts

Behind the scenes, file_get_contents() is one of numerous PHP functions which perform input/output using what's known as a stream. Streams were introduced in PHP 4.3.0, and you can think of a stream as an abstract "thing" that lets you read from and write to it. The actual IO is performed by a concrete stream wrapper - so if you pass file_get_contents() an FTP URL, it'll transparently select the FTP stream wrapper, whereas in our case, it'll use the HTTP wrapper.

The reason this is relevant is because you can pass configuration options to a stream wrapper using what's known as a "stream context". This is what we'll do now: let's say we want to send a User-Agent header, and a Referer, and in addition we want the request to be made using the POST method:


<?php

$url = 'http://example.org/';

$params = array(
        'http' => array(
            'method' => 'POST',
            'header' => "User-Agent: Nokia N95\\r\\n" .
                        "Referer: http://pointbeing.net/\\r\\n"
        )
);

$context = stream_context_create($params);
$page    = file_get_contents($url, false, $context);

The amended HTTP request is as follows:

POST / HTTP/1.1
Host: example.org
User-Agent: Nokia N95
Referer: http://pointbeing.net/

You can also use the stream context to send cookies, or to specify other options, such as how many redirects the client should follow before giving up, or how long it should wait for a request to complete before timing out.

So it turns out that file_get_contents() is far more powerful than we might have thought. There is a lot more that can be done with it than simply grabbing remote files over HTTP. However, sometimes we need yet more power! We may also want an object-oriented API to work with, and in order to achieve this, we'll need to step beyond core PHP functionality and call upon some libraries.

Zend_Http_Client

While there exist several HTTP client libraries for PHP, a particularly nice one ships with the Zend Framework, and is called Zend_Http_Client.

Zend_Http_Client provides an object-oriented wrapper for HTTP requests and almost anything you might wish to do with them. It's a breeze to use, even if your application is not Zend Framework based.

Making a POST request with Referer and User-Agent headers - much like the previous example - can be done like so:


<?php

$url = 'http://example.org/';

$client = new Zend_Http_Client();
$client->setUri($url);
$client->setMethod(Zend_Http_Client::POST);
$client->setConfig(
            array(
                'useragent' => 'Nokia N95',
                'referer'   => 'http://pointbeing.net/',
                ));

// perform the actual request
$response = $client->request();

Once the request has been performed, request() returns a Zend_Http_Response object (we'll look at HTTP responses in much more detail in part two of this mini-series). The response can alternatively be retrieved later using the client object's getLastResponse() method.

Zend_Http_Client can perform HTTP authentication in a very simple fashion:



$client->setAuth('myusername',
                 'mypassword',
                 Zend_Http_Client::AUTH_BASIC);

Extra GET or POST parameters can be added using setParameterGet(), or in our case setParameterPost():


<?php

$client->setParameterPost('myname', 'Simon');
$client->setParameterPost('myemailaddress', 'foo@example.com');

So far, the HTTP headers which will be sent by Zend_Http_Client are as follows:

POST / HTTP/1.1
Host: example.org
Connection: close
Accept-encoding: gzip, deflate
User-agent: Nokia N95
Authorization: Basic bXl1c2VybmFtZTpteXBhc3N3b3Jk
Content-type: application/x-www-form-urlencoded
Content-length: 45

That's a little more complicated than the headers sent by file_get_contents(), but should be quite self-explanatory. We can see the authentication details being passed on to the server, and it's also interesting to note that Zend_Http_Client claims support for HTTP messages compressed using both Gzip and Deflate.

The Content-type and Content-length headers are present because the POST request now includes an entity body, comprising the two parameters we attached using setParameterPost(). The entity body is 45 characters long, as specified in the headers, and looks like so:

myname=Simon&myemailaddress=foo%40example.com

It's probably also worth mentioning that it's also quite possible to specify arbitrary headers using the setHeaders() method, or perform file uploads using setFileUpload(). Cookies can be sent along with the request if they are specified via the setCookie() or setCookieJar() methods.

One real benefit of wrapping the HTTP service in an object in this way, is that this object can subsequently be injected into any code which is dependent on it, meaning it can easily be mocked for the purposes of unit testing. This would be a very similar principle to the one I wrote about a little while ago in Unit Testing Code which Consumes SOAP Services.

Conclusions

Of course, there are many more ways to do this kind of thing than could possibly fit into one post. Other popular methods include PEAR's HTTP_Client, and also the procedural cURL libraries. Still, hopefully this has given you a flavour of how simple and powerful it can be to make and manipulate HTTP requests in PHP.

Footnotes

[1] Do note however that in order for this to work, the allow_url_fopen php.ini setting must be set to On. The security implications of this are beyond the scope of this post.

Posted by Simon in PHP and Zend Framework
1 Oct 2010, 6:42 p.m.

BisonTech

Not work the referer:
$client->setConfig(
array(
'useragent' => 'Nokia N95',
'referer' => 'http://pointbeing.net/',
));

for change the Referer you must use setHeader method, like this:

$client->setHeaders('Referer', 'http://www.pointbeing.net/');

bye :D