Recipe 15.1. Serving a REST Method


15.1.1. Problem

You want to expose a server via REST. This allows people to make HTTP requests and receive XML in response.

15.1.2. Solution

The most basic REST server is a page that accepts query arguments and returns XML:

<?php // data $music_database = <<<_MUSIC_ <?xml version="1.0" encoding="utf-8" ?> <music>     <album >         <name>Revolver</name>         <artist>The Beatles</artist>     </album>     <!-- 941 more albums here -->     <album >         <name>Miles And Coltrane</name>         <artist>Miles Davis</artist>         <artist>John Coltrane</artist>     </album> </music> _MUSIC_; // load data $s = simplexml_load_string($music_database); // query data $artist = addslashes($_GET['artist']); $query = "/music/album[artist = '$artist']"; $albums = $s->xpath($query); // display query results as XML print "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; print "<music>\n\t"; foreach ($albums as $a) {     print $a->asXML(); } print "\n</music>"; ?>

When this page is stored at http://api.example.org/music, an HTTP GET request to http://api.example.org/music?artist=The+Beatles returns:

<?xml version="1.0" encoding="utf-8" ?> <music>     <album >         <name>Revolver</name>         <artist>The Beatles</artist>     </album> </music>

15.1.3. Discussion

At its most basic level, serving a REST request is no different than processing an HTML form. The key difference is that you're replying with XML instead of HTML.

Input parameters come in as query parameters, so PHP parses them into $_GET. You then process the values in $_GET to determine the correct query for your data, which you use to retrieve the proper records to return.

For instance, Example 15-1 uses code that queries an XML document using XPath for all the albums put out by the artist passed in via the artist get variable.

Implementing a REST query server

<?php // data $music_database = <<<_MUSIC_ <?xml version="1.0" encoding="utf-8" ?> <music>     <album >         <name>Revolver</name>         <artist>The Beatles</artist>     </album>     <!-- 941 more albums here -->     <album >         <name>Miles And Coltrane</name>         <artist>Miles Davis</artist>         <artist>John Coltrane</artist>     </album> </music> _MUSIC_; // load data $s = simplexml_load_string($music_database); // query data $artist = addslashes($_GET['artist']); $query = "/music/album[artist = '$artist']"; $albums = $s->xpath($query); // display query results as XML print "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; print "<music>\n\t"; foreach ($albums as $a) {     print $a->asXML(); } print "\n</music>"; ?>

For simplicity, Example 15-1 uses XML as the data source and XPath as the query language. This eliminates the need to convert the results to XML. It's likely that you will query a database using SQL. That's okay! For the purposes of REST, the particular backend system is irrelevant.

The important part is outputting your results as XML. In this case, since the data started as XML, you can wrap it inside a root element and echo it without any conversion:

<?php // display query results as XML print "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; print "<music>\n\t"; foreach ($albums as $a) {     print $a->asXML(); } print "\n</music>"; ?>

This gives you:

<?xml version="1.0" encoding="utf-8" ?> <music>     <album >         <name>Revolver</name>         <artist>The Beatles</artist>     </album> </music>

Now your work is done and it's up to the REST client to process the XML you returned, using the XML-processing tool of its choice. In PHP 5, this is frequently SimpleXML.

It's useful to publish a data schema for your REST responses. This lets people know what to expect from your replies and lets them validate the data to ensure its properly formatted. XML Schema and RelaxNG are two good choices for your schema.

REST isn't restricted to read-only operations, such as search. REST supports reading and writing data, including adding, updating, and deleting records.

There are two popular ways to expose this complete set of features:

  1. Accepting an additional parameter on the query string.

  2. Using HTTP verbs, such as post and put.

Both options are relatively straightforward to implement. This first is marginally easier, on both you and REST clients, but limits the size of the data you can accept and has potentially negative side effects.

When you use get for everything, it's very easy for people to construct requests because they can use just standard URLs with a query string. This is a familiar operation and people can even test their code by replicating their requests through the location bars on their web browsers.

However, many web servers place a limit on the size of the URLs they can process. People often need to pass large amounts of data when they add a new record. There's no such limitation on the size of post data. Therefore, get is not a good choice for adding or updating records.

Additionally, according to the HTTP specification, get requests are not supposed to alter backend data. You should design your site so that when a person makes two identical get requests, she gets two identical replies.

When you allow people to add, update, or delete records via get, you're violating this principle of HTTP. While this is normally not a problem, it can bite you when you're not looking. For instance, automated scripts, such as the Google spider, try to index your pages. If you expose destructive operations as URLs in the href attribute inside of HTML anchor tags, the spider may follow them, and delete information from your database in the process.

The initial release of the Google Web Accelerator caused problems on some web sites that used query-string-less URLs for delete operations. A discussion of the issue is at http://radar.oreilly.com/archives/2005/05/google_web_acce_1.html.

Still, adding another get parameter is straightforward and requires minimal edits, as shown in Example 15-3.

Implementing a REST server with multiple operations

<?php // Add more action specific logic inside switch() switch ($_GET['action']) { case 'search':     $action = 'search';     break; case 'add':     $action = 'add';     break; case 'update':     $action = 'update';     break; case 'delete':     $action = 'delete';     break; default:     // invalid action     exit(); } // Music Database XML document moved to file $s = simplexml_load_string('music_database.xml'); if ($action == 'search') {     $artist = $_GET['artist'];     $query = "/music/album[artist = '$artist']";     $albums = $s->xpath($query);     // Display results here } elseif ($action == 'add') {     $artist = $_GET['artist'];     $album = $_GET['album'];     // Insert new node from input data } // ... other actions here ?>

At the top of the page, check $_GET['action'] for a valid set of actions, and set the $action variable when you find one.

Then, load in the data source (which is where the XML flat file is less of a good choice, since you don't get locking out of the box like you do with databases).

Now you can perform your operation. For a search, query your data and print it out, just like in Example 15-1.

For an addition, you should update the data store, and then reply with a brief message saying everything succeeded. For example:

<?xml version="1.0" encoding="UTF-8"?> <response code="200">Album added</response> 

Alternatively, if there's a failure, send an error message:

<?xml version="1.0" encoding="UTF-8"?> <response code="400">Invalid request</response> 

While most people use this method of checking an action query parameter to decide what action to take, your other option is to use HTTP verbs, such as get, post, put, and delete. This is more "true" REST style, and allows you to not only comfortably process larger requests, but is also safer because it's far less likely that your data will be accidentally deleted.

Table 15-1 shows the general link between between SQL commands and HTTP verbs.

Table 15-1. SQL commands, HTTP verbs, and REST actions

SQL

REST

CREATE

POST

SELECT

GET

UPDATE

PUT

DELETE

DELETE


To use HTTP verbs, check the value of $_SERVER['REQUEST_METHOD'] instead of $_GET['action'], as shown in Example 15-3.

Implementing a REST server that uses HTTP verbs

<?php // Add more action specific logic inside switch() // Convert to UPPER CASE $request_method = strtoupper($_SERVER['REQUEST_METHOD']); switch ($request_method) { case 'GET':     $action = 'search';     break; case 'POST':     $action = 'add';     break; case 'PUT':     $action = 'update';     break; case 'DELETE':     $action = 'delete';     break; default:     // invalid action     exit(); } // ... other actions here ?>

Beyond switching to use the REQUEST_METHOD at the top of Example 15-3, you must also update your code to check the HTTP verb names of get, post, put, and delete. And you must now use $_POST instead of $_GET when the verb isn't get.

Remember that $_SERVER['REQUEST_METHOD'] is just as secure as $_GET['action'], which is to say not secure at all. Both of these values are easy to set, so if you're exposing sensitive data or allowing operations that can destroy data, make sure that the person making the request has permission to do so.

15.1.4. See Also

Recipe 14.1 for how to call REST methods; Recipe 9.1 for more on checking the value of the REQUEST_METHOD.




PHP Cookbook, 2nd Edition
PHP Cookbook: Solutions and Examples for PHP Programmers
ISBN: 0596101015
EAN: 2147483647
Year: 2006
Pages: 445

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net