જલધર હ. વ્યાસ

NAME

CGI::Application::Plugin::REST - Helps implement RESTful architecture in CGI applications

SYNOPSIS

    package WidgetView;
    use base qw( CGI::Application );
    use CGI::Application::Plugin::REST qw( rest_route rest_param );

    sub setup {
        my ($self) = @_;

        $self->rest_route({
            '/widget'                   => {
                'GET'    => 'showlist',
                'POST'   => {
                    'application/xml' => 'new_widget',
                },
            },
            '/widget/:id'               => {
                'GET'    => 'showdetail',
            },
        };
    }

    sub new_widget {
        my ($self) = @_;

        # make a new widget
   }

    sub showdetail {
        my ($self) = @_;

        my $id = $self->rest_param('id');

        # display the widget with the id $id.
   }

    sub showlist {
        my ($self) = @_;

        # show the entire list of widgets.
   }

   1;

ABSTRACT

If you use the CGI::Application framework, this plugin will help you create a RESTful (that's the common term for "using REST") architecture by abstracting out a lot of the busy work needed to make it happen.

VERSION

This document describes CGI::Application::Plugin::REST Version 0.3

DESCRIPTION

REST stands for REpresentational State Transfer. It is an architecture for web applications that tries to leverage the existing infrastructure of the World Wide Web such as URIs, MIME media types, and HTTP instead of building up protocols and functions on top of them.

This plugin contains a number of functions to support the various REST concepts. They try to use existing CGI::Application functionality wherever possible.

use'ing this plugin will intercept CGI::Application's standard dispatch mechanism. Instead of being selected based on a query parameter like rm, the run mode will be determined by comparing URI patterns defined in your app with the rest_route method. (Referred from here on, as "routes".) Optionally, specific HTTP methods or MIME media types can be defined in a route too. One by one, each entry in the reverse asciibetically sorted table of defined routes is compared to the incoming HTTP request and the first successful match is selected. The run mode mapped to that route is then called.

This is done via overriding CGI::Application's mode_param() function so it should be compatible with other CGI::Application plugins.

DevPopup Support

If you are using C::A::P::DevPopup (i.e. the environment variable CAP_DEVPOPUP_EXEC is set,) use'ing this module will register a callback which will add debug information about the current route (See rest_route_info), parameters (See rest_param) etc.

FUNCTIONS

The following functions are available. None of them are exported by default. You can use the :all tag to import all public functions.

rest_error_mode()

This function gets or sets the run mode which is called if an error occurs during the dispatch process. In this run mode, you can do whatever error processing or clean up is needed by your application.

If no error mode is defined, the start mode will be returned.

Example 1:

    $self->rest_error_mode('my_error_mode');
    my $em = $self->rest_error_mode; # $em equals 'my_error_mode'.

Why isn't the standard CGI::Application error mode mechanism used? The problem is that at the point C::A::P::REST plugs into the dispatch process, the error mode has not been defined. You might also want to use rest_error_mode in your own code to do a different sort of handling for errors in your REST API (which will typically only require setting the HTTP status code) as opposed to handling for end user errors.

Your rest_error_mode handler function will receive as a parameter the value of $@ if any.

rest_param()

The rest_param function is used to retrieve or set named parameters defined by the rest_route function. it can be called in three ways.

with no arguments.

Returns a sorted list of the defined parameters in list context or the number of defined parameters in scalar context.

    my @params     = $self->rest_param();
    my $num_params = $self->rest_param();
with a single scalar argument.

The value of the parameter with the name of the argument will be returned.

    my $color = $self->rest_param('color');
with named arguments

Although you will mostly use this function to retrieve parameters, they can also be set for one or more sets of keys and values.

    $self->rest_param(filename => 'logo.jpg', height => 50, width => 100);

You could also use a hashref.

    my $arg_ref = { filename => 'logo.jpg', height => 50, width => 100 };
    $self->rest_param($arg_ref);

The value of a parameter need not be a scalar, it could be any any sort of reference even a coderef.

    $self->rest_param(number => \&pick_a_random_number);

In this case, the function does not return anything.

rest_resource()

This function will set up a complete REST API for a collection of items with all the CRUD (Create, Read, Update, Delete) operations in one call. A collection could be rows in a database, files etc. The only assumption is that each item has a unique identifier.

Example 1: basic usage of rest_resource()

    $self->rest_resource('widget');

is exactly equal to the following invocation of rest_route:

    $self->rest_route(
        '/widget'                   => {
            'GET'     => 'widget_index',
            'POST'    => 'widget_create',
            'OPTIONS' => 'widget_options',
        },
        '/widget/:id'               => {
            'DELETE' => 'widget_destroy',
            'GET'    => 'widget_show',
            'PUT'    => 'widget_update',
        },
        '/widget/:id/edit'          => {
            'GET'    => 'widget_edit',
        },
        '/widget/new'               => {
            'GET'    => 'widget_new',
        },
    );

You are responsible for defining the widget_index, widget_create etc. run modes in your app.

*_create

Should be used to add a new item to the collection.

*_destroy

Should be used to remove the item with the id :id from the collection.

*_edit

Should return a temporary copy of the resource with the id :id which can be changed by the user and then sent to *_update.

*_index

Should be used to list the resources in the collection.

*_new

Should be used to return an input mechanism (such as an HTML form) which can be filled in by the user and sent to *_create to add a new resource to the collection.

*_show

Should be used to display resource with the id :id.

*_update

Should be used to alter the existing resource with the id :id.

*_options

Should be used to retrieve metadata that describes the resource's available interactions.

Various aspects of the generated routes can be customized by passing this method a hash (or hashref) of parameters instead of a scalar.

resource

This parameter is required. It is used to form the URI the route will match to.

HINT: use rest_route_prefix for more complicated URIs.

identifier

This parameter sets the name assigned to the unique identifier of an item in the collection which is used in some generated routes. It can be retrieved with rest_param. It defaults to id.

prefix

This parameter is prepended to an action to form a run mode name. It defaults to resource.

in_types, out_types

Both these parameters represent arrayrefs of MIME media types. in_type defines acceptable MIME media types for data incoming to your API (i.e. POSTs and PUTs) and out_type does the same for outgoing data (i.e. GETs and OPTIONS.) DELETE requests do not need MIME media types so they are not covered.

The reason there are two separate parameters is that typically the number of data formats a REST API will serve is different to the number and kind of incoming data formats.

Both of these parameters default to '*/*' i.e. any MIME media type is accepted.

Example 2: advanced usage of rest_resource()

    $self->rest_resource(resource => 'fidget', prefix => 'foo',
        identifier => 'num', in_types => [ 'application/xml' ],
        out_types => [ 'text/html', 'text/plain' ], );

is equal to the following invocation of rest_route:

    $self->rest_route(
        '/fidget'                   => {
            'GET'    => {
                'text/html'  => 'foo_index',
                'text/plain' => 'foo_index',
            },
            'POST'   => {
                'application/xml' => 'foo_create',
            },
        },
        '/fidget/:num'               => {
            'DELETE' => {
                '*/*' => 'foo_destroy',
            },
            'GET'    => {
                'text/html'  => 'foo_show',
                'text/plain' => 'foo_show',
            },
            'PUT'    => {
                'application/xml' => 'foo_update',
            },
        },
        '/fidget/:num/edit'          => {
            'GET'    => {
                'text/html'  => 'foo_edit',
                'text/plain' => 'foo_edit',
            },
        },
        '/fidget/new'               => {
            'GET'    => {
                'text/html'  => 'foo_new',
                'text/plain' => 'foo_new',
            },
        },
    );

If you need more complicated mappings then this, use rest_route.

rest_resource returns the map of routes and handlers that was created.

rest_route()

When this function is given a hash or hashref, it configures the mapping of routes to handlers (run modes within your CGI::Application).

It returns the map of routes and handlers.

Routes

Assume for the purpose of the following examples that your instance script has a base URI of http://localhost/

HINT: Your web server might not execute CGI scripts unless they have an extension of .cgi so your actual script might be http://localhost/app.cgi. However it is considered unRESTful to include infrastructural details in your URLs. Use your web servers URL rewriting features (i.e. mod_rewrite in Apache) to hide the extension.

A route looks like a URI with segments seperated by /'s.

Example 1: a simple route

    /foo

A segment in a route is matched literally. So if a request URI matches http://localhost/foo, the run mode that handles the route in example 1 will be used.

If you want to match the URI base itself, you can do it like this:

Example 2: route to a URI base

    /

This matches http://localhost/. Some people don't like the trailing slash; they can be accomodated by using an empty string as the route as in Example 3.

Example 3: route to a URI base without the trailing / ''

This matches http://localhost.

Routes can have more complex specifications.

Example 4: a more complex route

    /bar/:name/:id?/:email

If a segment of a route is prefixed with a :, it is not matched literally but treated as a parameter name. The value of the parameter is whatever actually got matched. If the segment ends with a ?, it is optional otherwise it is required. The values of these named parameters can be retrieved with the rest_param method.

In example 2, http://localhost/bar/jaldhar/76/jaldhar@braincells.com would match. rest_param('name') would return 'jaldhar', rest_param('id') would return 76, and rest_param('email') would return 'jaldhar@braincells.com'.

If the request URI was http://localhost/bar/jaldhar/jaldhar@braincells.com/, rest_param('email') would return 'jaldhar@braincells.com' and rest_param('name') would return 'jaldhar'. rest_param('id') would return undef.

If the request URI was http://localhost/bar/jaldhar/76 or http://localhost/jaldhar/, there would be no match at all because the required parameter ':email' is missing.

Note: Each named parameter is returned as a scalar. If you want ':email' to actually be an email address, it is up to your code to validate it before use.

Example 5: a wild card route

    /baz/string/*

If the route specification contains /*, everything from then on will be put into the special parameter 'dispatch_uri_remainder' which you can retrieve with rest_param just like any other parameter. Only one wildcard can be specified per route. Given the request URI http://localhost/baz/string/good, rest_param('dispatch_uri_remainder') would return 'good', with http://localhost/baz/string/evil it would return 'evil' and with http://localhost/baz/string/lawful/neutral/ it would return 'lawful/neutral/'.

Handlers

The most basic handler is a scalar or coderef.

Example 4: Basic Handlers

    my $routes = {
       '/foo'                    => 'wibble',
       '/bar/:name/:id?/:email'  => \&wobble,
       '/baz/string/*/'          => 'woop',
    };
    $self->rest_route($routes);

In example 4, a request to http://localhost/app/foo will be dispatched to wibble(). (It is upto you to make sure such a method exists.) A request to http://localhost/app/bar/jaldhar/76/jaldhar@braincells.com will dispatch to wobble(). A request to http://localhost/login will raise an error.

Example 5: More complex handlers

    $self->rest_route(
        '/quux'                   => {
            'GET'    => 'ptang',
            'DELETE' => 'krrang',
        },
        '/edna'                   => {
            'POST'   => 'blip',
            '*'      => 'blop',
        },
        '/grudnuk'                => {
            'GET'    => {
                'application/xml' => 'zip',
                '*/*'             => 'zap',
            },
            PUT      => {
                'application/xml' => 'zoom',
            },
        },
    );

If the handler is a hashref, the keys of the second-level hash are HTTP methods and the values if scalars or coderefs, are run modes. Supported methods are HEAD, GET, POST, PUT, DELETE, and OPTIONS. The key can also be * which matches all methods not explicitly specified. If a valid method cannot be matched, an error is raised and the HTTP status of the response is set to 405. (See "DIAGNOSTICS".)

In example 5, a GET request to http://localhost/quux will be dispatched to ptang(). A DELETE to http://localhost/quux will dispatch to krrang(). An OPTIONS) POST, PUT or HEAD will cause an error.

A POST request to http://localhost/edna will dispatch to zip() while any other type of request to that URL will dispatch to blop()

The values of the second-level hash can also be hashes. In this case the keys of the third-level hash represent MIME media types. The values are run modes. The best possible match is made use best_match() from REST::Utils. according to the HTTP Accept header sent in the request. If a valid MIME media type cannot be matched */* is tried as a last resort. If there is no handler for even that, an error is raised and the HTTP status of the response is set to 415. (See "DIAGNOSTICS")

In example 5, a GET request to http://localhost/grudnuk with MIME media type application/xml will dispatch to zip(). If the same request is made with any other MIME media type, the method zap() will be called instead. A PUT request made to the same URL with MIME media type application/xml will dispatch to zoom(). Any other combination of HTTP methods or MIME media types will cause an error to be raised.

If no URI can be matched, an error is raised and the HTTP status of the response is set to 404 (See "DIAGNOSTICS".)

rest_route_info()

This function can be called in a route handler. It returns a reference to a hash which contains some information about the current route.

path_received

The value of the PATH_INFO environment variable.

rule_matched

The rule that was successfully matched to determine this route.

runmode

The name of the function being called by this route.

method

The HTTP method that was matched by this route.

mimetype

The MIME media type that was matched by this route.

Example 1:

    $self->rest_route(
         '/foo' => {
             'GET' => 'bar',
         },
    );

    ...

    sub bar() {
        my ($self) = @_;

        my $info = $self->rest_route_info;

        say $info->{method};  # prints 'GET'
        }

rest_route_prefix()

Use this function to set a prefix for routes to avoid unnecessary repetition when you have a number of similar ones.

Example 1:

    # matches requests to /zing
    $self->rest_route(
         '/zing' => {
             'GET' => 'zap',
         },
    );

    $self->rest_route_prefix('/app')
    # from now on requests to /app/zing will match instead of /zing

    my $prefix = $self->rest_route_prefix # $prefix equals '/app'

OTHER DISPATCH PLUGINS COMPARED

There are several other modules that allow CGI::Application to dispatch to a run mode based on the PATH_INFO environment variable instead of the traditional CGI parameter. They each take a markedly different approach to implementation. Here is a comparison.

Executive summary: C::A::P::REST is the best :-)

CGI::Application Itself

You can set the run mode with the path_info option to mode_param(). This is limited to one segment (i.e. between /'s) of the path info.

Dispatch based on HTTP method or MIME media type is not supported.

CGI::Application::Dispatch

This module has influenced most of the other dispatchers including this one. It replaces CGI::Application as the base class for your application.

It has extensive capabilities for matching path info. It can capture variable segments in the URI with : ? and * tokens. They are retrievable in run modes as CGI::Application parameters (i.e. via $self->param().

You can also dispatch by HTTP method but not by MIME media type. The HTTP method is determined by looking at the HTTP_REQUEST_METHOD environment variable only. Methods called auto_rest() and auto_rest_lc() append the the HTTP method (all upper case and all lower case respectively) to a run mode that is determined by a dispatch rule which provides a limited version of C::A::P::REST's rest_resource function.

C::A::P::ActionDispatch

This module adds an attribute handler to run modes of your choice which enable parsing of the path info with regular expressions and dispatch to the run mode matched. Capturing parentheses in the regex can be accessed via the action_args() method.

Dispatch based on HTTP method or MIME media type is not supported.

C::A::P::Routes

This module installs a prerun hook that matches path info segments with support for capturing variable with the : ? and * tokens. They are retrievable in run modes as CGI parameters (i.e. via $self->query->param()

Dispatch based on HTTP method or MIME media type is not supported.

DIAGNOSTICS

During the dispatch process, errors can occur in certain circumstances. If an error occurs the appropriate HTTP status is set and execution passes to the run mode set by rest_error_mode. Here is a list of status codes and messages.

  • 404 No Route Found

    None of the specified routes matched the request URI.

  • 405 Method '$method' Not Allowed

    The route you specified with rest_route does not allow this HTTP request method. An HTTP Allow header is added to the response specifying which methods can be used.

  • 415 Unsupported Media Type

    None of the MIME media types requested by the client can be returned by this route and there is no handler for */*.

  • 500 No Dispatch Table

    This error can occur if rest_route was not called.

  • 500 Application Error

    The function that was called for this run_mode die'd somewhere.

  • 501 Function '$function_name' Doesn't Exist

    The function that you wanted to call from rest_route for this run_mode doesn't exist in your application.

BUGS AND LIMITATIONS

There are no known problems with this module.

Please report any bugs or feature requests to bug-cgi-application-plugin-rest at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CGI-Application-Plugin-REST. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SEE ALSO

THANKS

Much of the code in this module is based on C::A::P::Routes by Julián Porta who in turn credits Michael Peter's CGI::Application::Dispatch.

AUTHOR

Jaldhar H. Vyas, <jaldhar at braincells.com>

LICENSE AND COPYRIGHT

Copyright (c) 2010 Consolidated Braincells Inc., all rights reserved.

This distribution is free software; you can redistribute it and/or modify it under the terms of either:

a) the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version, or

b) the Artistic License version 2.0.

The full text of the license can be found in the LICENSE file included with this distribution.