Chapter 16. Web Services

Historically, every time there’s been a need for two systems to communicate, a new protocol has been created (for example, SMTP for sending mail, POP3 for receiving mail, and the numerous protocols that database clients and servers use). The idea of web services is to remove the need to create new protocols by providing a standardized mechanism for remote procedure calls, based on XML and HTTP.

Web services make it easy to integrate heterogeneous systems. Say you’re writing a web interface to a library system that already exists. It has a complex system of database tables, and lots of business logic embedded in the program code that manipulates those tables. And it’s written in C++. You could reimplement the business logic in PHP, writing a lot of code to manipulate tables in the correct way, or you could write a little code in C++ to expose the library operations (e.g., check out a book to a user, see when this book is due back, see what the overdue fines are for this user) as a web service. Now your PHP code simply has to handle the web frontend; it can use the library service to do all the heavy lifting.

REST Clients

A RESTful web service is a loose description of web APIs implemented using HTTP and the principles of REST. It refers to a collection of resources, along with basic operations a client can perform on those resources through the API.

For example, an API might describe a collection of authors and the books to which those authors have contributed. The data within each object type is arbitrary. In this case, a “resource” is each individual author, each individual book, and the collections of all authors, all books, and the books to which each author has contributed. Each resource must have a unique identifier so calls into the API know what resource is being retrieved or acted upon.

You might represent a simple set of classes to represent the book and author resources, as in Example 16-1.

Example 16-1. Book and Author classes
class Book {
 public $id;
 public $name;
 public $edition;

 public function __construct($id) {
 $this->id = $id;
 }
}

class Author {
 public $id;
 public $name;
 public $books = array();

 public function __construct($id) {
 $this->id = $id;
 }
}

Because HTTP was built with the REST architecture in mind, it provides a set of “verbs” that you use to interact with the API. We’ve already seen GET and POST verbs, which websites often use to represent “retrieve data” and “perform an action,” respectively. RESTful web services introduce two additional verbs, PUT and DELETE:

GET
Retrieve information about a resource or collection of resources.
POST
Create a new resource.
PUT
Update a resource with new data, or replace a collection of resources with new ones.
DELETE
Delete a resource or a collection of resources.

For example, the Books and Authors API might consist of the following REST endpoints, based on the data contained within the object classes:

GET /api/authors
Return a list of identifiers for each author in the collection.
POST /api/authors
Given information about a new author, create a new author in the collection.
GET /api/authors/id
Retrieve the author with identifier id from the collection and return it.
PUT /api/authors/id
Given updated information about an author with identifier id, update that author’s information in the collection.
DELETE /api/authors/id
Delete the author with identifier id from the collection.
GET /api/authors/id/books
Retrieve a list of identifiers for each book to which the author with identifier id has contributed.
POST /api/authors/id/books
Given information about a new book, create a new book in the collection under the author with identifier id.
GET /api/books/id
Retrieve the book with identifier id from the collection and return it.

The GET, POST, PUT, and DELETE verbs provided by RESTful web services can be thought of as roughly equivalent to the create, retrieve, update, and delete (CRUD) operations typical to a database, although they can correlate to collections, not just entities as is typical with CRUD implementations.

Responses

In each of the preceding API endpoints, the HTTP status code is used to provide the result of the request. HTTP provides a long list of standard status codes: for example, 201 Created would be returned when you create a resource, and 501 Not Implemented would be returned when you send a request to an endpoint that doesn’t exist.

While the full list of HTTP codes is beyond the scope of this chapter, some common ones include:

200 OK
The request was successfully completed.
201 Created
A request for creating a new resource was completed successfully.
400 Bad Request
The request hit a valid endpoint, but was malformed and could not be completed.
401 Unauthorized
Along with 403 Forbidden, represents a valid request, but one that could not be completed due to a lack of permissions. Typically, this response indicates that authorization is required but has not yet been provided.
403 Forbidden
Similar to 401 Unauthorized, this response indicates a valid request, but one that could not be completed due to a lack of permissions. Typically, this response indicates that authorization was available but that the user lacks permission to perform the requested action.
404 Not Found
The resource was not found (for example, attempting to delete an author with an ID that does not exist).
500 Internal Server Error
An error occurred on the server side.

These codes are mere guidelines and typical responses; the exact responses provided by a RESTful API are detailed by the API itself.

Retrieving Resources

Retrieving information for a resource involves a straightforward GET request. Example 16-2 uses the curl extension to format an HTTP request, set parameters on it, send the request, and get the returned information.

Example 16-2. Retrieving author data
$authorID = "ktatroe";
$url = "http://example.com/api/authors/{$authorID}";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);

$response = curl_exec($ch);
$resultInfo = curl_getinfo($ch);

curl_close($ch);

// decode the JSON and use a Factory to instantiate an Author object
$authorJSON = json_decode($response);
$author = ResourceFactory::authorFromJSON($authorJSON);

To retrieve information about an author, this script first constructs a URL representing the endpoint for the resource. Then, it initializes a curl resource and provides the constructed URL to it. Finally, the curl object is executed, which sends the HTTP request, waits for the response, and returns it.

In this case, the response is JSON data, which is decoded and handed off to a Factory method of Author to construct an instance of the Author class.

Updating Resources

Updating an existing resource is a bit trickier than retrieving information about a resource. In this case, you need to use the PUT verb. As PUT was originally intended to handle file uploads, PUT requests require that you stream data to the remote service from a file.

Rather than creating a file on disk and streaming from it, the script in Example 16-3 uses the 'memory' stream provided by PHP, first filling it with the data to send, then rewinding it to the start of the data it just wrote, and finally pointing the curl object at the file.

Example 16-3. Updating book data
$bookID = "ProgrammingPHP";
$url = "http://example.com/api/books/{$bookID}";

$data = json_encode(array(
 'edition' => 4,
));

$requestData = http_build_query($data, '', '&');

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);

$fh = fopen("php://memory", 'rw');
fwrite($fh, $requestData);
rewind($fh);

curl_setopt($ch, CURLOPT_INFILE, $fh);
curl_setopt($ch, CURLOPT_INFILESIZE, mb_strlen($requestData));
curl_setopt($ch, CURLOPT_PUT, true);

$response = curl_exec($ch);
$resultInfo = curl_getinfo($ch);

curl_close($ch);
fclose($fh);

Creating Resources

To create a new resource, call the appropriate endpoint with the POST verb. The data for the request is put into the typical key-value form for POST requests.

In Example 16-4, the Author API endpoint for creating a new author takes the information to create the new author as a JSON-formatted object under the key 'data'.

Example 16-4. Creating an author
<?php $newAuthor = new Author('pbmacintyre');
$newAuthor->name = "Peter Macintyre";

$url = "http://example.com/api/authors";

$data = array(
 'data' => json_encode($newAuthor)
);

$requestData = http_build_query($data, '', '&');

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);

curl_setopt($ch, CURLOPT_POSTFIELDS, $requestData);
curl_setopt($ch, CURLOPT_POST, true);

$response = curl_exec($ch);
$resultInfo = curl_getinfo($ch);

curl_close($ch);

This script first constructs a new Author instance and encodes its values as a JSON-formatted string. Then, it constructs the key-value data in the appropriate format, provides that data to the curl object, and sends the request.

Deleting Resources

Deleting a resource is similarly straightforward. Example 16-5 creates a request, sets the verb on that request to 'DELETE' via the curl_setopt() function, and sends it.

Example 16-5. Deleting a book
<?php $authorID = "ktatroe";
$bookID = "ProgrammingPHP";
$url = "http://example.com/api/authors/{$authorID}/books/{$bookID}";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);

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

$result = curl_exec($ch);
$resultInfo = curl_getinfo($ch);

curl_close($ch);

XML-RPC

While less popular nowadays than REST, XML-RPC and SOAP are two older, standard protocols used to create web services. XML-RPC is the older and simpler of the two, while SOAP is newer and more complex.

PHP provides access to both SOAP and XML-RPC through the xmlrpc extension, which is based on the xmlrpc-epi project (http://xmlrpc-epi.sourceforge.net). The xmlrpc extension is not compiled in by default, so you’ll need to add --with-xmlrpc to your configure line when you compile PHP.

Servers

Example 16-6 shows a very basic XML-RPC server that exposes only one function (which XML-RPC calls a “method”). That function, multiply(), multiplies two numbers and returns the result. It’s not a very exciting example, but it shows the basic structure of an XML-RPC server.

Example 16-6. Multiplier XML-RPC server
<?php
// expose this function via RPC as "multiply()"
function times ($method, $args) {
 return $args[0] * $args[1];
}

$request = $HTTP_RAW_POST_DATA;

if (!$request) {
 $requestXml = $_POST['xml'];
}

$server = xmlrpc_server_create() or die("Couldn't create server");
xmlrpc_server_register_method($server, "multiply", "times");

$options = array(
 'output_type' => 'xml',
 'version' => 'auto',
);

echo xmlrpc_server_call_method($server, $request, null, $options);

xmlrpc_server_destroy($server);

The xmlrpc extension handles the dispatch for you. That is, it works out which method the client was trying to call, decodes the arguments, and calls the corresponding PHP function. It then returns an XML response that encodes any values returned by the function that can be decoded by an XML-RPC client.

Create a server with xmlrpc_server_create():

$server = xmlrpc_server_create();

Once you’ve created a server, expose functions through the XML-RPC dispatch mechanism using xmlrpc_server_register_method():

xmlrpc_server_register_method(server, method, function);

The method parameter is the name the XML-RPC client knows. The function parameter is the PHP function implementing that XML-RPC method. In the case of Example 16-6, the multiply() XML-RPC client method is implemented by the times() function in PHP. Often a server will call xmlrpc_server_register_method() many times to expose many functions.

When you’ve registered all your methods, call xmlrpc_server_call_method() to dispatch the incoming request to the appropriate function:

$response = xmlrpc_server_call_method(server, request, user_data [, options]);

The request is the XML-RPC request, which is typically sent as HTTP POST data. We fetch that through the $HTTP_RAW_POST_DATA variable. It contains the name of the method to be called, and parameters to that method. The parameters are decoded into PHP data types, and the function (times(), in this case) is called.

A function exposed as an XML-RPC method takes two or three parameters:

$retval = exposedFunction(method, args [, user_data]);

The method parameter contains the name of the XML-RPC method (so you can have one PHP function exposed under many names). The arguments to the method are passed in the array args, and the optional user_data parameter is whatever the xmlrpc_server_call_method() function’s user_data parameter was.

The options parameter to xmlrpc_server_call_method() is an array mapping option names to their values. The options are:

output_type
Controls the data encoding used. Permissible values are "php" or "xml" (default).
verbosity
Controls how much whitespace is added to the output XML to make it readable to humans. Permissible values are "no_white_space", "newlines_only", and "pretty" (default).
escaping
Controls which characters are escaped, and how they are escaped. Multiple values may be given as a subarray. Permissible values are "cdata", "non-ascii" (default), "non-print" (default), and "markup" (default).
versioning
Controls which web service system to use. Permissible values are "simple", "soap 1.1", "xmlrpc" (default for clients), and "auto" (default for servers, meaning “whatever format the request came in”).
encoding
Controls the character encoding of the data. Permissible values include any valid encoding identifiers, but you’ll rarely want to change it from "iso-8859-1" (the default).

Clients

An XML-RPC client issues an HTTP request and parses the response. The xmlrpc extension that ships with PHP can work with the XML that encodes an XML-RPC request, but it doesn’t know how to issue HTTP requests. For that functionality, you must download the xmlrpc-epi distribution (http://xmlrpc-epi.sourceforge.net) and install the sample/utils/utils.php file. This file contains a function to perform the HTTP request.

Example 16-7 shows a client for the multiply XML-RPC service.

Example 16-7. Multiply XML-RPC client
<?php
require_once("utils.php");

$options = array('output_type' => "xml", 'version' => "xmlrpc");

$result = xu_rpc_http_concise(
 array(
 'method' => "multiply",
 'args' => array(5, 6),
 'host' => "192.168.0.1",
 'uri' => "/~gnat/test/ch11/xmlrpc-server.php",
 'options' => $options,
 )
);

echo "5 * 6 is {$result}";

We begin by loading the XML-RPC convenience utilities library. This gives us the xu_rpc_http_concise() function, which constructs a POST request for us:

$response = xu_rpc_http_concise(hash);

The hash array contains the various attributes of the XML-RPC call as an associative array:

method
Name of the method to call
args
Array of arguments to the method
host
Hostname of the web service offering the method
url
URL path to the web service
options
Associative array of options, as for the server
debug
If nonzero, prints debugging information (default is 0)

The value returned by xu_rpc_http_concise() is the decoded return value from the called method.

There are several features of XML-RPC we haven’t covered. For example, XML-RPC’s data types do not always map precisely onto those of PHP, and there are ways to encode values as a particular data type rather than as the xmlrpc extension’s best guess. Also, there are features of the xmlrpc extension we haven’t covered, such as SOAP faults. See the xmlrpc extension’s documentation (http://www.php.net) for the full details.

For more information on XML-RPC, see Programming Web Services in XML-RPC (http://oreil.ly/Web_Services_XML-RPC) by Simon St.Laurent et al. (O’Reilly). See Programming Web Services with SOAP (http://oreil.ly/Web_Services_SOAP) by James Snell et al. (O’Reilly) for more information on SOAP.

What’s Next

Now that we’ve covered the majority of the syntax, features, and application of PHP, the next chapter explores what to do when things go wrong: how to debug problems that arise in your PHP applications and scripts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset