Tags:
create new tag
, view all tags

How to REST

2007-11-14 - 10:37:34 by CrawfordCurrie in Development
A Long, Long Time Ago RafaelAlvarez contributed a REST architecture for TWiki that until relatively recently had been largely ignored. That's a shame, because TWiki has fallen behind the curve on effective interaction, partly because it is so difficult and inefficient to interact with TWiki from Javascript. More recently we have been able to re-architect big sections of the core to make REST more useful. Writing a REST script still isn't all that easy, however. This post is intended to try and make it easier.

See the Wikipedia article for the full gory details of what REST is. TWiki developers can think of a REST as a way of calling a single function in a a plugin, in a place where TWiki::Func is available. REST handlers are designed primarily to be called from XmlHttpRequest, but can also be useful in IFRAMEs.

A REST handler is invoked via a URL to the rest bin script. For example, http://twiki.org/cgi-bin/rest/WysiwygPlugin/tml2html?topic=Blog.2007-11-14-how-to-rest&text=Some%20test%20text would invoke the TML to HTML translator on TWiki.org (if it was available).

REST handlers can be added piecemeal to address specific requirements. However there are two more structured approaches currently in development that the interested reader might be well advised to wait for:

  • OliverKrueger has developed a REST interface to the TWiki::Func module. This interface uses JSON and XML to communicate the results of calling Func methods back to the Javascript (or so Oliver tells me; I haven't seen the code yet)
  • SvenDowideit is developing a REST plugin that uses the content-access syntax develop for search queries and IF statements in 4.2 to provide access to data in the TWiki database. This is most promising work, as it is a strong move in the direction of the TopicObjectModel.

So much for the future; what about the present? How do I go about writing a REST handler? Well, I can only describe how I do it; I'm sure there will be other, probably better, approaches.

As an example, let's look in detail at the REST function that implements the attachments list in the WysiwygPlugin. This Javascript function will, when given the name of a topic, return a list of the attachments on that topic. It uses:

  • XmlHttpRequest from within Javascript to make the request
  • TWiki::Func to fetch the data that satisfies the request
  • HTTP status codes to transfer state and erros back to the client
  • JSON to transfer the data back to the client
First let's look at the server side of the solution. A REST handler is registered in initPlugin thus:
sub initPlugin {
    TWiki::Func::registerRESTHandler('attachments', \&_restAttachments);
}
Now for the handler body:
sub _restAttachments {
    my ($session) = @_;
    my $topic = TWiki::Func::getCgiQuery()->param('topic');
    my $web;
    ($web, $topic) = TWiki::Func::normalizeWebTopicName(undef, $topic);
This first section of the handler simply examines the compulsory topic parameter to the REST call to determine the topic we are interested in. Note that normalizeWebTopicName will also untaint the web and topic names. Now we have the topic we can check the access permissions:
    unless (TWiki::Func::checkAccessPermission(
        'VIEW', TWiki::Func::getWikiName(),
        undef, $topic, $web)) {
        my $error = "Access denied";
        print CGI::header(-status => 401);
        print $error;
        print STDERR $error;
        return undef;
    }
This demonstrates how to handle errors. If access is denied, we print a CGI header to STDOUT with a 401 (access denied) HTTP status code. We also print the message, and duplicate that print to STDERR so the message also goes to the Apache log. Returning undef from a REST function just causes the rest script to terminate without generating any more output, and is the recommended way to terminate on an error.
    my ($meta, $text) = TWiki::Func::readTopic($web, $topic);
    # Create a JSON list of attachment data, sorted by name
    my @atts;
    foreach my $att (sort { $a->{name} cmp $b->{name} }
                               $meta->find('FILEATTACHMENT')) {
        push(@atts, '{'.join(',',
                         map {
                             "\"$_\":\"$att->{$_}\""
                         } keys %$att).'}');

    }
    return '['.join(',',@atts).']';
}
Having passed the access control check, we now generate a list of attachment data in JSON format. We could have used the CPAN:JSON module to do this, but the data is so trivial in this case that the overhead of the extra module would have been excessive.

When we return non-null data from a REST handler, the rest script automatically generates a 200 HTTP (OK) status response, indicating correct termination of the call.

Now, let's look at the Javascript. First we need to build the REST url:

function attachments() {
    // Work out the rest URL from the location
    var scripturl = getTWikiVar("SCRIPTURL");
    var suffix = getTWikiVar("SCRIPTSUFFIX");
    if (suffix == null) suffix = '';
    var path = getTWikiVar("WEB") + '.' + getTWikiVar("TOPIC");
In this code we have used a function called getTWikiVar to obtain the values of some standard TWiki variables. How this function is implemented is outside the scope of this article, but a typical technique is to pass such variable values in META tags, as described in another blog post.

We now have sufficient information to build the URL for the REST script:

    var url = scripturl + "/rest" + suffix + "/WysiwygPlugin/attachments";
See the documentation accompanying EmptyPlugin for more information on constructing REST urls. Now we perform a standard XmlHttpRequest
    if (window.XMLHttpRequest) {
        // branch for native XMLHttpRequest object
        request = new XMLHttpRequest();
    } else {
        // branch for IE/Windows ActiveX version
        request = new ActiveXObject("Microsoft.XMLHTTP");
    }
    request.open("POST", url, true);
    request.setRequestHeader(
        "Content-type", "application/x-www-form-urlencoded");

    // nocache helps us defeat client-side cacheing of the result
    var params = "nocache=" + encodeURIComponent((new Date()).getTime())
        + "&topic=" + encodeURIComponent(path);
    
    request.setRequestHeader("Content-length", params.length);
    request.setRequestHeader("Connection", "close");
    request.onreadystatechange = function() {
        attachmentListCallback(request);
    };
    request.send(params);
This request will be sent to the server, and when a response is ready the Javascript function attachmentListCallback will be called.
function attachmentListCallback(request) {
    if (request.readyState == 4) { // only if "OK"
        if (request.status == 200) {
            var atts = request.responseText;
            if (atts != null) {
                atts = eval(atts);
                // atts is now an array of attachment objects, just like the perl
            }
        } else {
            alert("There was a problem retrieving the attachments list: "
                  + request.statusText);
        }
    }
}
And we are done!

About the author: CrawfordCurrie is the most active TWiki core developer, having been a regular contributor to TWiki over the last four years, and the main driving force behind TWiki-4. His contributions include several REST-intensive plugins, such as the EditRowPlugin and WysiwygPlugin. Crawford is a founding member of the WikiRing and is available for consultancy on all TWiki projects. Contact him via http://wikiring.com

(Cross posted from this blog, which might be easier to read, especially the code sections)

Comments

MichaelDaum - 14 Nov 2007:

Just want to mention that TWiki has got XML-RPC services using the XmlRpcContrib for quite some time now. Admitted, XML-RPC is not that hip as REST. There is also JSON-RPC for those that don't like XML.


PeterThoeny - 14 Nov 2007:

Thanks Crawford for shedding light on this. (I moved your cross-post info from the top to the bottom since it looked odd in the summary.)


.

Edit | Attach | Watch | Print version | History: r3 < r2 < r1 | Backlinks | Raw View | Raw edit | More topic actions
Topic revision: r3 - 2008-03-11 - WillNorris
 

Twitter Delicious Facebook Digg Google Bookmarks E-mail LinkedIn Reddit StumbleUpon    
  • Help
  • Learn about TWiki  
  • Download TWiki
This site is powered by the TWiki collaboration platform Powered by Perl Hosted by OICcam.com Ideas, requests, problems regarding TWiki? Send feedback. Ask community in the support forum.
Copyright © 1999-2017 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.