Wednesday, April 07, 2010

HTTP verbs in PHP

While PHP is capable of performing HTTP requests towards external servers with any method, either via the HTTP extension or by opening streams directly, the support of the various GET, POST, PUT and other verbs on the receiving side of HTTP requests is a bit more complicated.

Background
The HTTP 1.0 specification (RFC 1945) officially defined only the GET, HEAD and POST methods, leaving open the possibility of adding extension methods:
Method        = "GET"                    ; Section 8.1
              | "HEAD"                   ; Section 8.2
              | "POST"                   ; Section 8.3
              | extension-method
The specification of HTTP 1.1 (as its last 1999 incarnation RFC 2616) defines explicitly other methods:
Method         = "OPTIONS"                ; Section 9.2
               | "GET"                    ; Section 9.3
               | "HEAD"                   ; Section 9.4
               | "POST"                   ; Section 9.5
               | "PUT"                    ; Section 9.6
               | "DELETE"                 ; Section 9.7
               | "TRACE"                  ; Section 9.8
               | "CONNECT"                ; Section 9.9
               | extension-method
Of these methods, the more interesting ones due to their usage in RESTful applications are GET, POST, PUT and DELETE:
  • GET is a safe, idempotent method and it is used to retrieve a resource.
  • POST is considered a catch-all method nowadays, but its intent is defining a subordinate resource to the current one. For instance, posting to a blog resource may create a new post.
  • PUT is the analogue of GET used to send a resource to the HTTP server.
  • DELETE is the analogue of GET used to, of course, delete a particular resource.
Client support
GET and POST are the bread and butter of requests sent towards PHP applications. They are commonly generated directly by the browser. A GET request is generated by a link or a form with the specified method attribute set as get. A POST request instead is usually obtainable in native HTML 4 only with a form, which may contain also an enctype attribute to set the Content-Type header of the request (usually employed for the upload of files via POST method.)
Browsers often limit their support for HTTP request to these two, as the HTML specification does not define a standard mean to generate other type of requests on the client-side. We have a programmatical way of making asynchronous HTTP requests, Javascript, but it does not help either as it's limited by the implementor's capabilities.
Javascript libraries do not force particular restrictions of the allowed methods: we may send GET, POST, PUT or DELETE requests to an endpoint on the server, but via an XMLHttpRequest object (which is their standard back-end) the unsupported methods will be emulated via overloaded POST. This means a POST request will be produced with an additional parameter (which may be named _method or _requestType, depending on the particular library) that describes the actual method used in the client. You get to maintain the semantic of the request in the client code, though, and maybe in the future native support for PUT and DELETE request will be available.
There are ways to make a real PUT or DELETE request, and they usually require more complex infrastructure on the client, like Java applets or non-standard Javascript (which is not supported in the majority of browsers), or even not using a browser as the client, for instance using a web service acting as a client of another one.

Server support
But how can we detect the HTTP request method in a PHP script? For the common methods, such as GET and POST, the superglobal arrays $_GET and $_POST are always available and contain the request parameters. You may want to wrap them with an object-oriented interface, and note that in the case of files upload via POST you should also look at the $_FILES superglobal array.
For the other methods, the first thing to do is setting up your webserver with a directive that routes all the PUT requests to a single, dynamic entry point. In the case of Apache, this is described in the PHP manual as:
Script PUT /put.php
The pointed script simply has to read from the standard input the PUT request:
$putdata = fopen("php://input", "r");
file_get_contents() won't work here; welcome back to the world of CGI! :)
The manual and the user comments are also a valuable resource for common pitfalls in implementing this type of endpoints.
With regard to the DELETE requests, the same configuration is valid to route the requests to a single entry point. Then, it's a matter of identifying the right environment variables, which may vary depending on your HTTP server. Fortunately, PHP frameworks do most of the work, and you are free to programmatically implement different behaviors depending on the type of the request.

3 comments:

  1. I'm not a fan of multiple php files as entry points for a web application. So I'd recommend checking $_SERVER['REQUEST_METHOD'] early on, and dispatching based on its value. Also, I'd have a look at stream_get_contents() as it might work where file_get_contents() does not.

    ReplyDelete
  2. Yes, the directives can point to your index.php, it was only an example. The important thing is to rewrite the request someway so that PUT /resource/name won't look for a resource folder.

    ReplyDelete
  3. parse_str(file_get_contents('php://input'), $data); //works perfectly fine for put and delete request methods.
    after the above line of code, your $data variable will have nicely parsed request data.

    ReplyDelete