RESTful Webservices module, an Ode to Drupal and Open Source

This is an Ode to the RESTful Webservices Project (http://drupal.org/project/restws) on Drupal.org with some instructions on how to actually create nodes, or any other entity type (users for example).

We were about to embark on upgrading all our webservices to Drupal 7, when i started researching what has changed in the Drupal restful services space. When i created the site for Babson about 2 years ago, web services was in a useful state, and not much else dotted the landscape. Low and behold i found the RESTful Webservices Project and believe it or not you just install the module, no configuration, no coding, and anything that's an "entity" in Drupal, ie a node, a user, etc. is available for CRUD operations using POST, PUT, DELETE or GET. BAM! HOLY CR_P BATMAT, now that is the power of Drupal and a case study of how Drupal and open sourced saved Babson 3 weeks of development work. 3 weeks of work replaced by 1 day of figuring out how to use restws (the drush name for this new rest project). Nice.

I may craft poems and Hobbit songs about this module, Klausi, and sepgil who authored it, I'm now sold on using entity_ids in the database as a standard for foreign keys -- that's what makes creating web services dynamically possible. There is documentation here for the module (this wiki post does not replace your need to read it): http://drupal.org/node/1860564 and I recommend the short video from the page above http://www.youtube.com/watch?v=Mr4jwnvUyaU but there was no complete (with examples) documentation for doing creates or updates to nodes (or other entities), so I spent a half day hacking away at getting it working. The readme was essential http://drupalcode.org/project/restws.git/blob/refs/heads/7.x-2.x:/README... I eventually got it working by changing line 456 of restws.module to (yes I changed it back after I figured out what I was missing)

if (user_is_logged_in() && empty($_COOKIE[session_name()])) {

from what it was supposed to be

if (user_is_logged_in() && !empty($_COOKIE[session_name()])) {

and if i didn't change the line I'd get 403 forbidden errors. I traced through the code further and figured out how to set the X-CSRF-Token, and stumbled upon Klausi's release notes http://drupal.org/node/1890216 and then noticed in the readme I had missed the line warning I would need to set this token to avoid CSRF hacks. My bet is many are struggling with the same trying to get POSTS (creating new nodes -- or any other entity) and PUTS (updating nodes -- or any other entity)working and so here is a working example of creating a node with restws module.  This is hardly a criticism of the brilliant work done here in restws, consider this a lyrical poem from a software developer who considers there to be beauty in how he can now easily have web services by only installing a single contrib module.  

Ode to Drupal and open source, and saving thousands of dollars per project due to community code.  Let this documentation of restws be a geeky Hobbit song in honor of this fine module which allows me to succeed and enjoy the break room and a couple more social conversations instead of 3 weeks of hard work, and of course the module allows me to also move on to all the other work to do in the Dwarven mines  -- less stress, more productivity.  After a slightly longer lunch I can now turn to face the upgrading of the database all the quicker.

NOTE:  When using curl you should only go to https not http, the example below goes to http (non secure) only because it is on my local machine.

 

echo 'hello';  
$url = "http://bell7:8888/node";

// This is example data from when you do a get, not needed for a post to create
// a node.  You can do a get of data from any entity by going to
// http://bell7:8888/node/1.json  which is the same url to view the node for
// example but you stick the .json onto the end.  Below is example output I used
// to construct the $new_node array below.
/* {"body":{"value":"\u003Cp\u003EJSON Group\u003C\/p\u003E\n","summary":"","format":"filtered_html"},"group_group":true,"field_end_date":"1425495600","field_start_date":"1362337200","members":[{"uri":"http:\/\/bell7:8888\/user\/1","id":"1","resource":"user"},{"uri":"http:\/\/bell7:8888\/user\/2","id":"2","resource":"user"},{"uri":"http:\/\/bell7:8888\/user\/23","id":"23","resource":"user"},{"uri":"http:\/\/bell7:8888\/user\/22","id":"22","resource":"user"}],"members__1":[{"uri":"http:\/\/bell7:8888\/user\/1","id":"1","resource":"user"},{"uri":"http:\/\/bell7:8888\/user\/2","id":"2","resource":"user"},{"uri":"http:\/\/bell7:8888\/user\/23","id":"23","resource":"user"},{"uri":"http:\/\/bell7:8888\/user\/22","id":"22","resource":"user"}],"members__2":[],"members__3":[],"nid":"1","vid":"1","is_new":false,"type":"group","title":"First Group","language":"und","url":"http:\/\/bell7:8888\/node\/1","status":"1","promote":"1","sticky":"0","author":{"uri":"http:\/\/bell7:8888\/user\/1","id":"1","resource":"user"},"log":"","revision":null,"comment":"1","comment_count":"0","comment_count_new":"0","views":0,"day_views":0,"last_view":null} */

$new_node = array(
      'title'     => 'hello from restful services',
      'body' => array(
        'value' => 'hi there',
        'summary' => 'hi there', 
        'format' => 'filtered_html'
      ),
      'comment'   => 2,
      'promote'   => 0,
      'revision'  => 1,
      'log'       => '',
      'status'    => 1,
      'sticky'    => 0,
      'type'      => 'group',
      'language'  => 'und',
      'author'    => 1,
    );
$json = json_encode($new_node);

$result = add_node($json);

function add_node($post_data){
  //global $user;
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL,"http://bell7:8888/user/login");
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_USERAGENT, 'PHP script');
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  curl_setopt($ch, CURLOPT_COOKIEJAR, "session_cookie.txt");
  curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookie.txt');
  curl_setopt($ch, CURLOPT_POST, TRUE);
  curl_setopt($ch, CURLOPT_POSTFIELDS, "name=username&pass=your_password&form_id=user_login");
  curl_setopt($ch, CURLOPT_VERBOSE, true);
  curl_setopt($ch, CURLOPT_COOKIE, session_name() . '=' . session_id());
  ob_start();  //dont print out this output
  curl_exec ($ch);
  curl_setopt($ch, CURLOPT_URL, "http://bell7:8888/restws/session/token");
  $HTTP_X_CSRF_TOKEN = curl_exec($ch);
  ob_end_clean();  //resume printout output to screen

  curl_setopt($ch, CURLOPT_URL, "http://bell7:8888/node");
  curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // output to command line
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'X-CSRF-Token: ' . $HTTP_X_CSRF_TOKEN));

  $ret = new stdClass;
  $ret->response = curl_exec($ch); // execute and get response
  $ret->error    = curl_error($ch);
  $ret->info     = curl_getinfo($ch);
  print_r($ret);

curl_close ($ch);

unset($ch);

return;
}

The restws module also supports returning more than one record, & it supports filtering, sorting and "direction" which works like order by does in MYSQL. This code for example on my local machine will get my user information (filtering by username):

$url = "http://bell7:8888/user.json?name=jbarnett";
$result = get_node($url);

function get_node($url){
  //global $user;
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL,"http://bell7:8888/user/login");
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_USERAGENT, 'PHP script');
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  curl_setopt($ch, CURLOPT_COOKIEJAR, "session_cookie.txt");
  curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookie.txt');
  curl_setopt($ch, CURLOPT_POST, TRUE);
  curl_setopt($ch, CURLOPT_POSTFIELDS, "name=username&pass=password&form_id=user_login");
  curl_setopt($ch, CURLOPT_VERBOSE, true);
  curl_setopt($ch, CURLOPT_COOKIE, session_name() . '=' . session_id());
  ob_start();  //dont print out this output
  curl_exec ($ch);
  curl_setopt($ch, CURLOPT_URL, "http://bell7:8888/restws/session/token");
  $HTTP_X_CSRF_TOKEN = curl_exec($ch);
  ob_end_clean();  //resume printout output to screen
  curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // output to command line
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'X-CSRF-Token: ' . $HTTP_X_CSRF_TOKEN));

  $ret = new stdClass;
  $ret--->response = curl_exec($ch); // execute and get response
  $ret->error    = curl_error($ch);
  $ret->info     = curl_getinfo($ch);
  // uncomment the below line to get more verbosity on response;
  print_r($ret);

curl_close ($ch);

unset($ch);

return;
}

Comments

I had the same "wait,... I just turn it on... WHAT!?" moment when I stumbled on this project.  Great and simple idea.  It works very well in conjunction with Views XML Backend module as well (I did a screencast on the two here -- https://drupal.psu.edu/content/powering-education-restws-and-views-xml-b...).
 
Also if you did this for babson I assume you mean the college, in which case we should really connect about our use-cases surrounding this project :).  I've put a decent amount of work into making a drupal_http_request wrapper that allows for cached restWS calls as well http://drupal.org/project/cis_connector for use with the edtech distributions I'm working on.
 
Great post, hope we can hook up some time.

<p>Does this extend or rely on the "services" module, or is it a different approach/specialized separate module?</p>

This restws module in fact is a replacement for the services module.  Believe it or not, 17K installs of services module and only 500 of this one.  It is an unsung module, I'm sure folks would use if they knew about it.

I had just this morning started to dive into adding the services module for a site I'm working on when I stumbled across your planet post. I'm ready to burst out in song myself. My guess is restful access to entities is all most sites will ever need and to not have to spend untold hours trying to decode the complexities of the services modules is worth it's wait in elven gold.
 
bravo!

rest_ws is the model for Drupal 8's web services in core. Thanks for telling folks about it. Code that uses rest_ws will continue to work even after an upgrade to d8.

Thanks for this great post. I am getting "403 Access Denied: CSRF validation failed". Any tips what I could be doing wrong? ALso, how would this code look with the restws_simple_auth. I was able to login with

$process = curl_init("http://mysite.com/services/session/token");
curl_setopt($process, CURLOPT_HTTPHEADER, array('Content-Type: application/xml')); curl_setopt($process, CURLOPT_HEADER, 1);
curl_setopt($process, CURLOPT_USERPWD, "restwsxyz:pass"); curl_setopt($process, CURLOPT_TIMEOUT, 30); curl_setopt($process, CURLOPT_POST, 1);
curl_setopt($process, CURLOPT_RETURNTRANSFER, TRUE);
$return = curl_exec($process);
curl_close($process);
print_r($return);

I think this line which is in the above code sample fixes that problem:  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json', 'X-CSRF-Token: ' . $HTTP_X_CSRF_TOKEN));