fromMarch 2011
Column:

Making Drupal a go-to platform for building mobile applications

1

A few years ago, computer tablets similar to Apple's iPad were props in science fiction films. Only a couple of years from now, tablets might be among the most popular consumer electronics ever. It took less than three months to sell the first 3 million iPads. This has made the iPad the consumer electronics device with the fastest adoption rate of all time. And let’s not forget Android; Android devices are currently outselling iOS devices.

This begs the question: in a world of tablets and mobile handheld devices, what do we need to do to make Drupal a go-to platform for building mobile applications? This is one of the most important questions that we need to ask ourselves. Why? Because in the future it is likely that we’ll design for mobile first, and for a full-desktop site second. At the rate that tablets and mobile devices are being adopted, we might not have much time.

The state of mobile in Drupal

A mobile experience can mean many things. Often, there is a lot that can be accomplished by building a mobile theme. For Drupal 8, Larry “Crell” Garfield’s “Butler” project (http://groups.drupal.org/butler) could also help to provide the flexible page layouts and context-aware page building system that supports multi-device publishing. Making Drupal 8 output HTML5 would facilitate building mobile experiences.

At the end of the day, native applications can provide the best user experience though. To build a native application for Android or iOS, developers will want to leverage web service APIs. Web service APIs are also important for building Flash applications on top of Drupal, or for integrating Drupal with other systems.

The Services module (http://drupal.org/project/services) maintained by Greg ‘heyrocker’ Dunlap has been around for many years and has over 10,000 active installations. It is being used to power mobile and other applications for organizations like Warner Brothers and Ikea. The new version 3 is a full REST server that offers web service APIs for most Drupal core components. It also has a pluggable server architecture so you can plug in other servers like XML-RPC, SOAP, or AMFPHP.

The brand new RESTful services module “RESTWS” (http://drupal.org/project/restws), a contributed module for Drupal 7 developed by Klaus Purer (klausi) and Wolfgang Ziegler (fago) also takes a very interesting approach. The module builds on Drupal 7’s new Entity API to provide resource representations for all entity types (nodes, comments, users, taxonomy terms, etc).

Both modules respect the Content Accept/Content Type headers of the HTTP request and automatically returns data in the requested representation. If the client requests node/42 with the regular HTTP accept headers, the module will return Drupal's usual HTML output. If the request headers to node/42 only accept the MIME media type "application/json", it will get the JSON representation of the node. Ditto for XML. In addition, all CRUD operations on entities are exposed as RESTful services. The fact that we can expose Drupal’s data as web resources by simply installing a module is brilliant, and make it very easy to build RESTful web services.

The road ahead

We should consider integrating modules like Services or RESTWS into core Drupal 8. Before we dive into possible solutions for Drupal 8, it’s always interesting to see how other frameworks work, and further, to learn a set of new and different perspectives to some pretty common problems. Chances are we can make improvements to core so modules like Services and RESTWS can be simplified. Furthermore, not everything can be solved through these modules, and often people want to write a web service from scratch.

In this column, I’ll show how to build a basic web service in Java and compare it to how the equivalent is done in Drupal. Why a Java comparison, you may ask? Mollom, a comment spam protection service that I co-founded and help run, is written in Java. The Mollom backend is built on top of Glassfish, an open source application server for the Java EE platform. As a result, I’ve spent some of my weekends and evenings learning NetBeans, Glassfish, JAXB and most recently, Jersey.

First, you declare a class with some getters and setters.

@XmlRootElement
public class Person {
  private String name;
  private int age;

  public Person() {
  }

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }
}

The interesting part is the @XmlRootElement annotation at the top. In Java, classes can be marshaled to XML and unmarshaled from XML without extra programming work. The @XmlRootElement is used to guide the automatic mapping of a class to an XML document. In PHP speak, marshaling is a type of intelligent serialization optimized for sending objects across the network. The schema definitions are verified as part of the unmarshaling so an instance of a Java class can be interchanged safely.

Second, we define a handler class responsible for delivering the request.

@Path("person")
public class PersonHandler {

  @GET
  @Produces(value="application/xml, application/json")
  @Path("id/{id}")
  public Person getPerson(@PathParam("id") int id) {
    Person answer = new Person("Dries", 32);
    return answer;
  }
}

The @GET annotation is used to specify that the method will process HTTP GET requests. HTTP POST requests, for example, won’t be accepted.
The @Path annotation's value is a relative URI. In the example above, the Java class will be hosted at the URI path ‘/person’. The method getPerson has a @Path annotation as well, and is relative to the class’ @Path annotation. In other words, the method PersonHandler.getPerson corresponds with the relative URI path ’/person/id/{id}’. The string in curly braces denotes a URI path template and is substituted at run-time. The above example is very simple -- it’s also possible to associate validation mechanisms with the @Path annotations and to add default values for non-supplied parameters.

For those that are Drupal developers it is obvious that there is a lot of overlap with Drupal’s menu and router systems. At the same time, there is a lot that we can learn from this too. If we implement the Java example in Drupal, we would do it as follows (assuming we don’t want to use the Entity API):

/**
 * Implements hook_menu().
 */
function person_menu() {
  $items['person/id/%person'] = array(
    'page callback' => 'person_page',
    'page arguments' => array(2),
  );
  return $items;
}

/**
 * Loads a person by ID; Menu argument loader.
 */
function person_load($id) {
  $persons = entity_load(array($id));
  return isset($persons[$id]) ? $persons[$id] : FALSE;
}

/**
 * Menu page callback; outputs a person.
 */
function person_page($person) {
  $build['age'] = array(
    '#markup' => check_plain($person->age),
  );
  $build['name'] = array(
    '#markup' => check_plain($person->name),
  );
  return $build;
}

Hook _menu() is how modules register paths, defining how URL requests are handled. The ‘page callback’ attribute specifies the PHP function to call to render the web page at the corresponding URL. In other words, when a web browser makes a request to Drupal for the URL ‘/person/id/42’, Drupal’s menu system will decide that this request needs to be handled by the function person_page(). The ‘%’ in the page callback ‘person/id/%’ is a URI template similar to the one in Java. In our example, person_page() returns a structured array which Drupal will automatically convert to HTML before sending it to the client.

I say the Drupal example is “roughly” the same compared to what the Java example. First, in Java you don’t need to define the structure array – Java takes care of that for you. A second important difference is in the @Produces annotation in the PersonHandler class. The @Produces annotation specifies the MIME types of representations a resource may produce and return to the client. In this example, the Java method produces representations identified by the MIME media type "application/xml" and "application/json". Obviously other MIME types such as "text/plain" and “text/html" are also supported.

In the Java example, PersonHandler.getPerson() is invoked if either of the media types "application/xml" and "application/json" are requested, and the object Person is automatically returned in the corresponding format. If I do a request from the command line using Curl, I get:

$ curl -H "accept: application/json" "<a href="http://localhost:8080/person/id/42"
">http://localhost:8080/person/id/42"
</a>{"age":"32","name":"Dries"}
$ curl -H "accept: application/xml" "<a href="http://localhost:8080/person/id/42"
<?xml">http://localhost:8080/person/id/42"
<?xml</a> version="1.0" encoding="UTF-8" standalone="yes"?><person><age>32</age><name>Dries</name></person>

To do something equivalent in Drupal, we need to use “delivery callbacks”, a feature newly introduced in Drupal 7. As shown in the code example below, the delivery callback is specified in the menu router definition similar to how the page callback is specified. The delivery callback is invoked with the result of the page callback; it takes the structured array $person as input and can format the data before sending it to the client. If no delivery callback is specified (like in our example above), Drupal will use the default HTML delivery callback.

/**
 * Implements hook_menu().
 */
function person_menu() {
  $items['person/id/%person'] = array(
    'page callback' => 'person_page',
    'page arguments' => array(2),
    'delivery callback' => 'person_deliver',
  );
  return $items;
}

/**
 * Loads a person by ID; Menu argument loader.
 */
function person_load($id) {
  $persons = entity_load(array($id));
  return isset($persons[$id]) ? $persons[$id] : FALSE;
}

/**
 * Menu page callback; outputs a person.
 */
function person_page($person) {
  $build = array(
    '#pre_render' => 'person',
    '#person' => $person,
  );
  return $build;
}

/**
 * Delivery callback for a person.
 */
function person_deliver($build) {
  if ($_SERVER['HTTP_ACCEPT'] == 'application/json') {
    drupal_json_output($build['#person']);
    drupal_exit();
  }
  // @todo Unlike Java, this is not generic.
  if ($_SERVER['HTTP_ACCEPT'] == 'application/xml') {
    $writer = new XMLWriter();
    $writer->openURI('php://output');
    $writer->startDocument('1.0');
    $writer->startElement('person');
    foreach ($build['#person'] as $key => $value) {
      $writer->writeElement($key, $value);
    }
    $writer->endElement();
    $writer->endDocument();
    $writer->flush();
    drupal_exit();
  }
  // Fall back to HTML page delivery callback.
  return drupal_deliver_html_page($build);
}

/**
 * #pre_render callback to convert a person into a renderable array.
 */
function person_pre_render_person($element) {
  $element['age'] = array(
    '#markup' => check_plain($element['#person']->age),
  );
  $element['name'] = array(
    '#markup' => check_plain($element['#person']->name),
  );
  return $element;
}

Unfortunately, there is no built-in delivery callback in Drupal 7 core that can return JSON or XML. Drupal 7 core does not support multiple delivery callbacks with automatic MIME type negotiation either. Plus, to be able to fallback to our HTML page delivery callback, we had to rewrite the person_page() function and introduce a #pre_render callback.

When it comes to making a simple web service from scratch, it is clear that Drupal is not as elegant and powerful as Glassfish/Jersey. Conceptually, things are more or less the same, but Drupal’s approach in terms of code is more convoluted. The reason is that Drupal is built to deliver HTML pages, not to be a REST server.  All non-HTML responses are a bit of a hack, including the delivery callback mechanism.  The big change we need to make is removing the assumption of an HTML page and make that just one output type among many.  It fits under what the Butler project promises to deliver. Given the growing importance of mobile applications and the need to integrate with third-party systems, addressing this shortcoming should be one of the goals for Drupal 8.

Of course, the true power of Drupal is that it comes with a very rich administration backend and a set of very powerful tools that allow you to quickly build complex content types (e.g. CCK and Views) through the UI. The ability to easily manage your content as an end user and then expose it through RESTful APIs by simply installing a contributed module like the Services module or the RESTWS module saves the day. Effectively, the flexibility of Drupal core allows us to work around some of its own shortcomings with contributed modules. Thanks to these contributed modules, Drupal is already one of the easiest platforms for building mobile applications today.

Comments

Glad to see Drupal tackling mobility as a high priority. It's critically important for businesses & organizations to be competitive, while maintaining their own independence as well as the privacy of their own users.

Regards..
Alina from Halosys.