HTTP Verbs - PHP Web Services (2013)

PHP Web Services (2013)

Chapter 2. HTTP Verbs

HTTP verbs such as GET and POST let us send our intention along with the URL so we can instruct the server what to do with it. Web requests are more than just a series of addresses, and verbs contribute to the rich fabric of the journey.

I mentioned GET and POST because it’s very likely you’re already familiar with those. There are many verbs that can be used with HTTP—in fact, we can even invent our own—but we’ll get to that later in the chapter (see Using Other HTTP Verbs). First, let’s revisit GET and POST in some detail, looking at when to use each one and what the differences are between them.

Making GET Requests

URLs used with GET can be bookmarked, they can be called as many times as needed, and the request should not affect change to the data it accesses. A great example of using a GET request when filling in a web form is when using a search form, which should always use GET. Searches can be repeated safely, and the URLs can be shared.

Consider the simple web form in Figure 2-1, which allows users to state which category of results they’d like and how many results to show. The code for displaying the form and the (placeholder) search results on the page could be something like this:

<?php

if(empty($_GET)) {

?>

<form name="search" method="get">

Category:

<select name="category">

<option value="entertainment">Entertainment</option>

<option value="sport">Sport</option>

<option value="technology">Technology</option>

</select> <br />

Rows per page: <select name="rows">

<option value="10">10</option>

<option value="20">20</option>

<option value="50">50</option>

</select> <br />

<input type="submit" value="Search" />

</form>

<?php

} else {

echo "Wonderfully filtered search results";

}

An example search form

Figure 2-1. An example search form

You can see that PHP simply checks if it has been given some search criteria (or indeed any data in the $_GET superglobal) and if not, it displays the empty form. If there was data, then it would process it (although probably in a more interesting way than this trivial example does). The data gets submitted on the URL when the form is filled in (GET requests typically have no body data), resulting in a URL like this:

http://localhost/book/get-form-page.php?category=technology&rows=20

The previous example showed how PHP responds to a GET request, but how does it make one? Well, as discussed in Chapter 1, there are many ways to approach this. For a very quick solution, and a useful approach to use when working with GET requests in particular, use PHP’s stream handling to create the complete request to send:

<?php

$url = 'http://localhost/book/get-form-page.php';

$data = array("category" => "technology", "rows" => 20);

$get_addr = $url . '?' . http_build_query($data);

$page = file_get_contents($get_addr);

echo $page;

In a real system, it is prudent to be cautious of the data coming in from external APIs; it is best to filter the contents of $page before outputting it or using it anywhere else. As an alternative to using PHP’s stream features, you could use whatever functionality your existing frameworks or libraries offer, or make use of the Curl extension that is built in to PHP. Using Curl, our code would instead look like this:

<?php

$url = 'http://localhost/book/get-form-page.php';

$data = array("category" => "technology", "rows" => 20);

$get_addr = $url . '?' . http_build_query($data);

$ch = curl_init($get_addr);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$page = curl_exec($ch);

echo $page;

Either of these approaches work well when you want to fetch data into your PHP script from an external API or page. The examples here show web pages, but they apply when working with HTML, XML, JSON, or anything else.

Making POST Requests

In contrast to GET requests, a POST request is one that does cause change on the server that handles the request. These requests shouldn’t be repeated or bookmarked, which is why your browser warns you when it is resubmitting data. Let’s use a POST form when the request changes data on the server side. Figure 2-2, for example, involves updating a bit of user profile information.

Simple form that updates data, sending content via a POST request

Figure 2-2. Simple form that updates data, sending content via a POST request

When a form is submitted via GET, we can see the variables being sent on the URL. With POST, however, the data goes into the body of the request, and the Content-Type header denotes what kind of data can be found in the body. When we fill in the form in Figure 2-2, the request looks like this:

POST /book/post-form-page.php HTTP/1.1

Host: localhost

Content-Length: 48

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip,deflate,sdch

Accept-Language: en-GB,en-US;q=0.8,en;q=0.6

Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

email=lorna%40example.com&display_name=LornaJane

In this example, you can see the data in the body, with the Content-Type and Content-Length headers set appropriately so a client could decode the response (more about content negotiation in Chapter 3). PHP knows how to handle form data, so it can parse this out and place the fields into $_POST, so it will be ready for use in the script. Here is the code behind this page, showing the form without any incoming data; if data existed, it would be displayed:

<?php

if(empty($_POST)) {

?>

<form name="user" method="post">

Email:

<input type="text" length="60" name="email" /><br />

Display name:

<input type="text" length="60" name="display_name" /><br />

<input type="submit" value="Go" />

</form>

<?php

} else {

echo "New user email: " . filter_input(INPUT_POST,

"email", FILTER_VALIDATE_EMAIL);

}

It is very common to build PHP forms and parse data in this way, but when handling HTTP requests, it is also important to consider how the requests can be made and responded to. This is not dissimilar to the way that GET requests are made. For example, to POST data to this form using streams (as in Making GET Requests), the same basic approach can be used, but some context should be added to the stream, so it will know which methods, headers, and verbs to use:

<?php

$url = 'http://localhost/book/post-form-page.php';

$data = array("email" => "lorna@example.com", "display_name" => "LornaJane");

$options = array(

"http" => array(

"method" => "POST",

"header" => "Content-Type: application/x-www-form-urlencoded",

"content" => http_build_query($data)

)

);

$page = file_get_contents($url, false, stream_context_create($options));

echo $page;

When POST data is sent to the page created, the data sent appears in the output rather than in the form, so it shows “New user email: lorna@example.com.” This code looks very similar to the previous streams example, but this example uses stream_context_create() to add some additional information to the stream.

You can see that we added the body content as a simple string, formatted it as a URL using http_build_query(), and indicated which content type the body is. This means that other data formats can very easily be sent by formatting the strings correctly and setting the headers.

Here is an example that does the exact same thing, but uses the pecl_http extension:

<?php

$url = 'http://localhost/book/post-form-page.php';

$data = array("email" => "lorna@example.com", "display_name" => "LornaJane");

$request = new HttpRequest($url, HTTP_METH_POST);

$request->setPostFields($data);

$request->send();

$page = $request->getResponseBody();

echo $page;

Because this is a POST request, PHP assumes that a form is being posted; but different Content-Type headers can be set if appropriate, and another format of string data can be sent. This approach is illustrated in many different ways as this book progresses. When working with non-standard verbs (as seen in the next section) or with data that isn’t from a form post, it isn’t possible to access the data by grabbing it from $_POST. Instead, PHP’s own stream of raw body data can be accessed at php://input.

Using Other HTTP Verbs

There are many specifications relating to HTTP, as well as protocols based upon it, and between them they define a wide selection of verbs that can be used with HTTP. Even better, there is always room to invent new HTTP verbs; so long as your client and server both know how to handle a new verb, it is valid to use it. However, be aware that not all elements of network infrastructure between these two points will necessarily know how to handle every verb. Some pieces of network infrastructure do not support PATCH, for example, or the verbs used by the WebDAV protocol. When working with APIs, particularly RESTful ones, it is normal to make use of two additional verbs: PUT and DELETE. REST is covered in detail in Chapter 8, but for now it is useful to examine some examples of how to use these less common verbs in applications.

The simplest of these two is DELETE, because it doesn’t have any body data associated with it. It is possible to see what kind of request was made to a PHP script acting as a server by inspecting the $_SERVER["REQUEST_METHOD"] value, which indicates which verb was used in the request.

To make the request from PHP, it is necessary to set the verb and then make the request as normal. Here’s an example using the Curl extension:

<?php

$url = 'http://localhost/book/example-delete.php';

$ch = curl_init($url);

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");

curl_exec($ch);

This example simply issues a request to the $url shown using a DELETE verb.

Using PUT is slightly more involved because, like POST, it can be accompanied by data and the data can be in a variety of formats. In Making POST Requests, I mentioned that for incoming form data, PHP reads form-encoded values for POST and creates a $_POST array for us. There is no equivalent to $_PUT superglobal, but we can still make use of the php://input stream to inspect the body data of the request to which the script is sending a response at that time.

When using PHP to respond to PUT requests, the code runs along the lines of this example:

<?php

if($_SERVER['REQUEST_METHOD'] == "PUT") {

$data = array();

$incoming = file_get_contents("php://input");

parse_str($incoming, $data);

echo "New user email: " . filter_var($data["email"], FILTER_VALIDATE_EMAIL);

} else {

echo "um?";

}

This example inspects the $_SERVER superglobal to see which verb was used, and then responds accordingly. The data coming into this example is form style, meaning it uses file_get_contents() to grab all the body data, then parse_str() to decode it.

WARNING

Be careful with parse_str()—if the second argument is omitted, the variables will be extracted as local variables, rather than contained in an array.

In order to use PHP to make a request that the previous script can handle, it is necessary to create the contents of the body of the request and specify that it is a PUT request. Below is an example built using the pecl_http extension:

<?php

$url = 'http://localhost/book/put-form-page.php';

$data = array("email" => "lorna@example.com", "display_name" => "LornaJane");

$request = new HttpRequest($url, HTTP_METH_PUT);

$request->setHeaders(array(

"Content-Type" => "application/x-www-form-urlencoded"));

$request->setPutData(http_build_query($data));

$request->send();

$page = $request->getResponseBody();

echo $page;

The PUT verb is specified in this example, and the correct header for the form-encoded data is set. We dictate the data to PUT (manually building the form elements into a string) and then send the request. We will discuss more about other data formats in Chapter 5 and Chapter 6, which cover JSON and XML specifically, but the basic principles of preparing the data and setting the Content-Type header accordingly still stand.

Armed with this knowledge of how to handle GET, POST, DELETE, and PUT verbs, we are able to work with many different kinds of API acting as both a client and as a server. When using other verbs, either those that already exist as part of the HTTP spec or those that are custom to your application, you can use the approaches described here for PUT and DELETE.