The REST API

Abstract

The REST Provider module provides a simple framework for creating RESTful web services using Drupal. It strives to be simple and unobtrusive, imposing as few constraints on developers as possible. Developers are free to create any kind of RESTful web service, not just "Drupalesque" services. This module also takes care of some of the more tedious aspects of creating a RESTful web service.

Difference From Other REST Modules

There are currently three other REST-related modules for Drupal. Here's how this module compares to those:

Architecture

At its heart, the REST Provider module provides a simple controller for automatically invoking the proper function for the given request (based on its URI and HTTP request method).

The REST Provider module relies heavily on hooks and conventions. All request handlers are hook functions and all controllers are represented with .inc files that contain their respective handler functions. By adhering to such a structure, RESTful web services are easy to create, understand, and maintain.

This is a standalone module. It doesn't use the Services module's API because the Services module treats REST as just another web services architecture. The basic REST model is unique enough that tools for implementing it should be designed specifically for that purpose.

This module is lightweight. It uses as few resources as possible. There are no database queries in the module itself (although the module does call user_access(), which queries the database the first time it's run). The module accesses one additional file per request.

Simplifying Assumptions

The REST Provider module imposes a few simplifying assumptions on services. They are:

  1. The service will be on the web. Drupal's natural home is the web and this module assumes that's where the service will be. And though REST, as described by Roy T. Fielding, is not limited to the web or HTTP, this module only works in this context.
  2. OPTIONS requests should be handled automatically. The response to HTTP OPTIONS requests is very simple: tell the user what HTTP methods are available on the requested resource. This module performs reflection on the service to determine which HTTP methods are available to each resource and automatically sends a response to OPTIONS requests. This requires no additional work on the part of service developers.
  3. Authentication has already been handled. This module provides no authenication mechanism, nor does it integrate with one. Authentication should be among the first things handled in the HTTP request. By the time the request finds its way to the REST API controller, the request should have already been authenticated.
  4. Requests are valid. This module does not prevent CSRFs or any other types of attacks by checking to see if a request is valid or if the authentication is valid. It assumes that these checks have already taken place and the request refused before it reaches the REST API controller.
  5. Proxies are not a concern. This module doesn't handle CONNECT or TRACE requests because they deal with HTTP proxies, which are outside of the scope of this module.

Using the API

Using the REST Provider module requires creating a custom module. This module will need to implement hook_menu() in order to invoke the controller. For each URI that the controller should handle, create a MENU_CALLBACK menu item.

Invoking the Controller

Let's assume that we're creating a module called example on a site hosted at http://example.com/ and look in the example.module file:

function example_menu() {
  $items['myuri'] = array(
    'title' => 'Example URI',
    'type' => MENU_CALLBACK,
    'page callback' => 'rest_provider_controller',
    'page arguments' => array('example', 'mycontroller'),
    'access callback' => 'rest_provider_access',
  );
}

In this example, the URI http://example.com/myuri is told to use a REST Provider controller. This controller is contained within the example module (in this case, the same module) and the controller is called mycontroller. These are the aguments that are required to be sent to rest_provider_controller().

Because rest_provider_controller() allows you to specify a module name as well as a controller name, you can create a custom module to link controllers defined in other modules to paths that you define.

Additionally, rest_provider_controller() supports wildcards in menu items. Say you want the URIs http://example.com/myuri/abc/show and http://example.com/myuri/xyz/show to be sent to the same controller with abc and xyz (respectively) sent as arguments to the controller. You might implement it like this:

function example_menu() {
  $items['myuri/%/show'] = array(
    'title' => 'Example URI',
    'type' => MENU_CALLBACK,
    'page callback' => 'rest_provider_controller',
    'page arguments' => array('example', 'mycontroller', 1),
    'access callback' => 'rest_provider_access',
  );
}

In this example, any other value between myuri/ and /list is sent as an argument to rest_provider_controller(). You can send an arbitrary number of arguments to this function in this manner. Just tack them onto the end of the page arguments array.

Handling Requests

The example module has linked a URI—or, in the latter example, a URI template—to a REST Provider controller. Implementing the controller is as simple as creating a file called example_mycontroller.inc in the example module's directory. The directory structure might now look like this:

sites/all/modules/example/example.info
sites/all/modules/example/example.module
sites/all/modules/example/example_mycontroller.inc

It might be obvious from the example, the REST Provider module looks in a module's directory for a file called MODULENAME_CONTROLLERNAME.inc.

Finally, handling the request is as simple as creating a function in the new controller's .inc file:

function example_mycontroller_GET($my_arg) {
  return array(
    'response_code' => '200', // OK
    'headers' => array(),
    'body' => $my_arg,
    'media_type' => 'text/plain',
    'charset' => 'utf-8',
  );
}

The function name follows a similar naming convention as the controller itself: MODULENAME_CONTROLLERNAME_METHOD() where METHOD is the name of an HTTP method. If you create a method called example_mycontroller_POST, all POST requests will automatically be sent to that function.

You'll note in the example that the function accepts an argument called $my_arg. This is populated with the argument from the menu item: that is, abc or xyz in the example URIs. So loading the URI http://example.com/myuri/abc/show will result in $my_arg having the value abc.

Responses

If your handler function doesn't return a value, an HTTP/1.1 200 response (OK) will be sent along with an empty response body. But because the example function above specified a return value (that is, an associative array), the following HTTP response would be sent:

200 OK
Content-Type: text/plain; utf-8

abc

Well, that's not entirely true. Because the REST Provider module will automatically gzip responses if the option is enabled in Drupal and it's supported by the server, the request would look more like this:

200 OK
Content-Type: text/plain; utf-8
Content-Encoding: gzip

[binary gzipped representation of "abc"]

The gzip handling is completely automatic so you don't even need to think about it.

Permissions

All of this is well and good if you want every HTTP request to be open to every client. But most of the time, you're going to want to restrict access to most, if not all, of your exposed resources. There are two ways to accomplish this.

First, in your menu items, you can use the default user_access function as your access callback function and specify a permission as your access arguments:

function example_menu() {
  $items['myuri/%/show'] = array(
    'title' => 'Example URI',
    'type' => MENU_CALLBACK,
    'page callback' => 'rest_provider_controller',
    'page arguments' => array('example', 'mycontroller', 1),
    'access callback' => 'user_access',
    'access arguments' => array('my permission'),
  );
}

But this can prove to be problematic: this permission will apply to all requests to that URI, regardless of the method used. Which means that, for example, GET, PUT, and DELETE requests would all share the same permissions. This is rarely desirable in a RESTful application.

Thus the rest_provider_access function exists. Using it will allow all requests to proceed past the menu system without checking for permissions. This point should be reiterated: If you use rest_provider_access, the menu system will not check for permissions. Using this carelessly could be a major security risk.

So if rest_provider_access could present a security risk, why use it? Because it will shift the responsibility of checking for permissions from the menu system to the REST Provider module, which gives you fine-grained control of permissions in each controller.

Implementing permissions is, again, very simple. Just add a hook_permissions() function to the controller file. In the example above, this code would go in the example_mycontroller.inc file:

function example_mycontroller_permissions() {
  return array(
    'GET' => array('GET permission'),
    'PUT' => array('PUT permission', 'another permission'),
  );
}

When a GET request is sent to http://example.com/myuri/abc/show, the REST Provider module will check to see if the current user has the permission GET permission. If the user doesn't have said permission, the REST Provider module will return a 403 Forbidden or 401 Unauthorized (depending on whether a user has been authenticated) and the request will never even reach the example_mycontroller_GET function. Similarly, PUT requests would have to pass permission checks for PUT permission and another permission for the example_mycontroller_PUT function to be called.

Please note: The default permission is allow. If you do not provide an entry in your hook_permissions() function for a corresponding method, users will not be denied access by the REST Provider module. This is by design. This allows you to use both the menu system's access check and the REST Provider's access check for the same request.