RESTful Web Services Module Basics
A Solution for Working with Entities over the Network
By Michael Ross
This article was published in the print magazine Drupal Watchdog, Volume 4 Issue 2, 2014-09, on pages 30-33, by Tag1 Publishing. The magazine was distributed at Drupalcon Amsterdam 2014, 2014-09-29.
Drupal 7 does not have built-in support for representational state transfer (REST) functionality. However, the RESTful Web Services module is arguably the most efficient way to provide resource representations for all the entity types, by leveraging Drupal's powerful Entity API. Unmodified, the module makes it possible to output the instances of the core entity types — node, file, and user — in JSON or XML format. Further entity type resources and formats are possible utilizing hooks in added code.
As with any REST solution, the RESTful Web Services module supports all four of the fundamental operations of data manipulation: create, read, update, and delete (CRUD). The corresponding RESTful API HTTP methods are POST
, GET
, PUT
, and DELETE
, respectively.
Anyone hoping to learn and make use of this module — especially for the first time — will likely be frustrated by the current project documentation, which is incomplete, uneven, and lacking clear examples. This article — a brief overview — is intended to introduce what is possible with this module, and help anyone getting started with it.
We begin with a clean Drupal 7 installation (using the Standard profile) running on a virtual host with the domain name "drupal_7_test". After installing and enabling the module, we find that it does not have the configuration user interface one might expect. In the demonstration code below, we focus on the node entity type for brevity.
Nabbing a Node
The simplest operation — reading an entity instance — is performed using a simple GET
request containing the machine name of the entity type and the entity's ID.
To allow an anonymous user to read the node using REST, it is insufficient to grant that role the "View published content". Moreover, the "Bypass content access control" permission has no effect. Rather, the module establishes an "Access the resource" permission for each entity type, which also must be enabled for anonymous users. (When testing anonymous access from a web page, be certain you're not logged into the website in the same browser, because then you are already authenticated.)
In this example, we read the fields in the first page in the Drupal database (node/1), which has a title of "Page Title" and a body field of only "Body text". To display the information in JSON format, in a web browser, the URL would be: http://drupal_7_test/node/1.json
The results are, as expected, in JSON:
{"body":{"value":"\u003Cp\u003EBody text.\u003C\/p\u003E\n","summary":"","format":"full_html"},"nid":"1","vid":"1","is_new":false,"type":"page","title":"Page Title","language":"und","url":"http:\/\/drupal_7_test\/node\/1","edit_url":"http:\/\/drupal_7_test\/node\/1\/edit","status":"1","promote":"0","sticky":"0","created":"1402821494","changed":"1403394617","log":"","revision":null,"comment":"1","comments":[],"comment_count":"0","comment_count_new":"0"}
If you get an HTTP 403 error ("Forbidden"), verify the two required permission settings for anonymous users accessing nodes.
To display the same information as XML, we need only alter the path extension: http://drupal_7_test/node/1.xml
The information is the same, but in a substantially different format:
<node> <body> <value> Body text </value> <summary/> <format>full_html</format> </body> <nid>1</nid> <vid>1</vid> <is_new/> <type>page</type> <title>Page Title</title> <language>und</language> <url>http://drupal_7_test/node/1</url> <edit_url>http://drupal_7_test/node/1/edit</edit_url> <status>1</status> <promote>0</promote> <sticky>0</sticky> <created>1402821494</created> <changed>1403394617</changed> <log/> <comment>1</comment> <comments/> <comment_count>0</comment_count> <comment_count_new>0</comment_count_new></node>
To get all of the nodes — or at least the first 100, by default — remove the entity ID: http://drupal_7_test/node.json
If we again want to access that first node only, but not use a URL, then we can employ cURL on the commandline:
curl -X GET http://drupal_7_test/node/1.json
More descriptive options, such as --request GET
, can be chosen. To see details of operations that may be helpful for debugging, add the -v
option (a.k.a. --verbose
).
If you do not want anonymous REST requests to have access to your website's content, use HTTP authorization. Apparently, the simplest way to do so is to enable the Basic Authentication Login submodule (restws_basic_auth). Connect by utilizing a username that has the appropriate "Access the resource" permission and begins with the (mandatory lowercase) string "restws". Assuming that the user "restws_user" has a password of "password", then we can display the first node with this command:
curl -X GET http://drupal_7_test/node/1.json -u restws_user:password
The aforesaid username pattern could be represented as the regular expression /^restws.*/
. That pattern can be customized in the website's settings.php file. For instance, to grant authorization only to those usernames beginning with the string "WS_", one adds the following (case-sensitive) regex to the settings.php
file:
$conf[ 'restws_basic_auth_user_regex' ] = '/^WS_.*/';
Authentication is a substantial topic unto itself, and here we will not delve into cookie and session handling.
Creating or Changing a Node
Rather than using the conventional content management UI for adding an entity resource, we can do it with REST — specifically, a POST
request. There are several ways to accomplish this: using the core function drupal_http_request()
, or cURL PHP code, or cURL on the commandline, which we will use. Returning to the XML format, we can use a file based upon the one output earlier, containing only those elements needed to create a new node with some content in the body field:
<node> <type>page</type> <title>New Page Title</title> <body> <value> New body text </value> </body></node>
Assuming that the file is named create.xml
, we can create a new node with this command:
curl -X POST -H "Content-Type: application/xml" -d @create.xml http://drupal_7_test/node -u restws_user:password
Assuming the AUTO_INCREMENT
value of the nid
field in the node
table is 1, for example, then the output in the command window would indicate that a second node, node/2
, had been created:
<?xml version="1.0" encoding="utf-8"?><uri resource="node" id="2">http://drupal_7_test/node/2</uri>
If the XML file were missing an essential element, such as the node type, then we would receive an error message such as: 406 Not Acceptable: Missing bundle: type
Modifying an existing node is similar, but instead of using the POST
method, use PUT
and specify a node ID. The XML file needs even fewer elements:
<node> <title>Modified Page Title</title> <body> <value> Modified body text </value> </body></node>
The cURL command is straightforward:
curl -X PUT -H "Content-Type: application/xml" -d @modify.xml http://drupal_7_test/node/2 -u restws_user:password
The output will not include any <uri>
element containing the node ID, since no new one was created.
Nuking a Node
To remove any entity resource — in this case, the first node in the database — use the DELETE
method:
curl -X DELETE http://drupal_7_test/node/1 -u restws_user:password
The baffling yet correct output is merely:
[]
Using this module, one can also: filter the results when querying for multiple resources (by specifying the user ID or taxonomy ID); sort by one of the properties (either in ascending or descending direction); change the default limit of 100 resource references. For debugging purposes, you can enable the logging of the details of all web service requests to a specified file, with a simple addition to the Drupal settings.php file, for example:
$conf[ 'restws_debug_log' ] = DRUPAL_ROOT . '/restws_debug.log';
If in your own explorations, you discover additional techniques and pitfalls, please consider publishing them, as there appears to be both interest and confusion as to how to leverage this module fully.
Copyright © 2014 Michael J. Ross. All rights reserved.