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.
There are currently three other REST-related modules for Drupal. Here's how this module compares to those:
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.
The REST Provider module imposes a few simplifying assumptions on services. They are:
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.
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.
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
.
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.
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.