NAME

RPC::ExtDirect - Expose Perl code to Ext JS RIA applications through Ext.Direct remoting

SYNOPSIS

package Foo::Bar;

use RPC::ExtDirect Action => 'Fubar',
                   before => \&package_before_hook,
                   after  => \&package_after_hook,
                   ;
 
sub foo_custom_hook {
   # Check something, return true
   return 1;
}

sub foo : ExtDirect(2, before => \&foo_custom_hook) {
   my ($class, $arg1, $arg2) = @_;
 
   # do something, store results in scalar
   my $result = ...;
 
   return $result;
}
 
# This method doesn't need hooks for some reason
sub bar
   : ExtDirect(
       params => ['foo', 'bar'], before => 'NONE', after  => 'NONE',
     )
{
   my ($class, %arg) = @_;
 
   my $foo = $arg{foo};
   my $bar = $arg{bar};
 
   # do something, returning scalar
   my $result = eval { ... };
 
   # or throw an exception if something's wrong
   die "Houston, we've got a problem: $@\n" if $@;
 
   return $result;
}
 
sub baz : ExtDirect(formHandler) {
   my ($class, %arg) = @_;
 
   my @form_fields    = grep { !/^file_uploads$/  } keys %arg;
   my @uploaded_files = @{ $arg{file_uploads}     };
 
   # do something with form fields and files
   my $result = { ... };
 
   return $result;
}
 
sub package_before_hook {
   my ($class, %params) = @_;
 
   # Unpack parameters
   my ($method, $env) = @params{ qw/method _env/ };
 
   # Decide if user is authorized to call this method
   my $authorized = check_authorization($method, $env);
 
   # Positive
   return 1 if $authorized;
 
   # Negative, return error string
   return 'Not authorized';
}
 
sub package_after_hook {
   my ($class, %params) = @_;
 
   # Unpack parameters
   my ($method, $result, $ex) = @params{ qw/method result exception/ };
   
   # Log the action
   security_audit_log($method, $result, $ex);
}

DESCRIPTION

Abstract

This module provides an easy way to map Perl code to Ext.Direct RPC interface used with Ext JS JavaScript framework.

What Ext.Direct is for?

Ext.Direct is a high level RPC protocol that allows easy and fast integration of server components with JavaScript interface. Client side stack is built in Ext JS core and is used by many components like data Stores, Forms, Grids, Charts, etc. Ext.Direct supports request batching, file uploads, event polling and many other features.

Besides simplicity and ease of use, Ext.Direct allows to achieve very clean code and issue separation both on server and client sides, which in turn results in simplified code, greater overall software quality and shorter development times.

From Perl module developer perspective, Ext.Direct is just a method attribute; it doesn't matter if it's called from Perl code or through Ext.Direct. This approach, in particular, allows for multi-tiered testing: - Server side methods can be tested without setting up HTTP environment with the usual tools like Test::More - Server side classes can be tested as a whole via Ext.Direct calls using Perl client - Major application components are tested with browser automation tools like Selenium.

For more information on Ext.Direct, see http://www.sencha.com/products/extjs/extdirect/.

Terminology

Ext.Direct uses the following terms, followed by their descriptions: Configuration - Description of server side calls exposed to client side. Includes information on Action and Method names, as well as argument number and/or names.

API            - JavaScript chunk that encodes Configuration.
                 Usually generated by application server
                 called by client once upon startup.

Router         - Server side component that receives remoting
                 calls, dispatches requests, collects and
                 returns call Results or Exceptions.

Action         - Namespace unit; collection of Methods. The
                 nearest Perl analog is package, other 
                 languages may call it a Class. Since the
                 actual calling code is JavaScript, Action
                 names should conform to JavaScript naming
                 rules (i.e. no '::', use dots instead).

Method         - Subroutine exposed through Ext.Direct API
                 to be called by client side. Method is
                 fully qualified by Action and Method names
                 using dot as delimiter: Action.Method.

Result         - Any data returned by Method upon successful or
                 unsuccessful call completion. This includes
                 application logic errors. 'Not authenticated'
                 and alike events should be returned as Results,
                 not Exceptions.

Exception      - Fatal error, or any other unrecoverable event
                 in application code. Calls that produce
                 Exception instead of Result are considered
                 unsuccessful; Ext.Direct provides built in
                 mechanism for managing Exceptions.

                 Exceptions are not used to indicate errors in
                 application logic flow, only for catastrophic
                 conditions. Nearest analog is status code 500
                 for HTTP responses.

                 Examples of Exceptions are: request JSON is
                 broken and can't be decoded, called Method
                 dies because of internall error, Result
                 cannot be encoded in JSON, etc.

Event          - An asynchronous notification that can be
                 generated by server side and passed to
                 client side, resulting in some reaction.
                 Events are useful for status updates, progress
                 indicators and other predictably occuring
                 conditions and events.

Event Provider - Server side script that gets polled by
                 client side every N seconds; default N
                 is 3 but it can be changed in client side
                 configuration.

USING RPC::EXTDIRECT

In order to export subroutine to ExtDirect interface, use ExtDirect(n, ...) attribute in sub's declaration. Note that there can be no space between attribute name and opening parentheses. n is mandatory calling convention declaration; it may be one of the following options: - Number of arguments to be passed as ordered list - Names of arguments to be passed as hash - formHandler: method will receive hash of fields and file uploads - pollHandler: method that provides Events when polled by client

Optional method attributes can be specified after calling convention declaration, in hash-like key => value form. Optional attributes are: - before: code reference to use as "before" hook. See "HOOKS". - instead: code reference to "instead" hook. - after: code reference to "after" hook.

METHODS

Unlike Ext.Direct specification (and reference PHP implementation, too) RPC::ExtDirect does not impose strict architectural notation on server side code. There is no mandatory object instantiation and no assumption about the code called. That said, an RPC::ExtDirect Method should conform to the following conventions: - Be a class method, i.e. be aware that its first argument will be package name. Just ignore it if you don't want it.

   - Ordered (numbered) arguments are passed as list in @_, so
     $_[1] is the first argument. No more than number of arguments
     declared in ExtDirect attribute will be passed to Method; any
     extra will be dropped silently. Less actual arguments than
     declared will result in Exception returned to client side,
     and Method never gets called.

     The last argument is an environment object (see below). For
     methods that take 0 arguments, it will be the first argument
     after class name.

   - Named arguments are passed as hash in @_. No arguments other
     than declared will be passed to Method; extra arguments will
     be dropped silently. If not all arguments are present in
     actual call, an Exception will be returned and Method never
     gets called.

     Environment object will be passed in '_env' key.

   - Form handlers are passed their arguments as hash in @_.
     Standard Ext.Direct form fields are removed from argument
     hash; uploaded file(s) will be passed in file_uploads hash
     element. It will only be present when there are uploaded
     files. For more info, see "UPLOADS".

     Environment object will be passed in '_env' key.

   - All remoting Methods are called in scalar context. Returning
     one scalar value is OK; returning array- or hashref is OK too.
     Do not return blessed objects; it is almost always not
     obvious how to serialize them into JSON that is expected by
     client side; JSON encoder will choke and an Exception will
     be returned to the client.

   - If an error is encountered while processing request, throw
     an exception: die "My error string\n". Note that "\n" at
     the end of error string; if you don't add it, die() will
     append file name and line number to the error message;
     which is probably not the best idea for errors that are not
     shown in console but rather passed on to JavaScript client.

     RPC::ExtDirect will trim that last "\n" for you before
     sending Exception back to client side.

   - Poll handler methods are called in list context and do not
     receive any arguments except environment object. Return values
     must be instantiated Event object(s), see RPC::ExtDirect::Event
     for more detail.

HOOKS

Hooks provide an option to intercept method calls and modify arguments passed to the methods, or cancel their execution. Hooks are intended to be used as a shim between task-oriented Methods and Web specifics.

Methods should not, to the reasonable extent, be aware of their environment or care about it; Hooks are expected to know how to deal with Web intricacies but not be task oriented.

The best uses for Hooks are: application or package-wide pre-call setup, user authorization, logging, cleanup, testing, etc.

A hook is a Perl subroutine (can be anonymous, too). Hooks can be of three types: - "Before" hook is called before the Method, and can be used to change Method arguments or cancel Method execution. This hook must return numeric value 1 to allow Method call. Any other value will be interpreted as Ext.Direct Result; it will be returned to client side and Method never gets called.

     Note that RPC::ExtDirect will not make any assumptions about
     this hook's return value; returning a false value like '' or 0
     will probably look not too helpful from client side code.

     If this hook throws an exception, it is returned as Ext.Direct
     Exception to the client side, and the Method does not execute.
 
   - "Instead" hook replaces the Method it is assigned to. It is
     the hook sub's responsibility to call (or not call) the Method
     and return appropriate Result.

     If this hook throws an exception, it is interpreted as if the
     Method trew it.
 
   - "After" hook is called after the Method or "instead" hook. This
     hook cannot affect Method execution, it is intended mostly for
     logging and testing purposes; its input include Method's
     Result or Exception. This hook's return value and
     thrown exceptions are ignored.

Hooks can be defined on three levels, in order of precedence: method, package and global. For each Method, only one hook of each type can be applied. Hooks specified in Method definition take precedence over all other; if no method hook is found then package hook applies; and if there is no package hook then global hook gets called, if any. To avoid using hooks for a particular method, use 'NONE' instead of coderef; this way you can specify global and/or package hooks and exclude some specific Methods piecemeal.

Hooks are subject to the following calling conventions: - Hook subroutine is called as class method, i.e. first argument is name of the package in which this sub was defined. Ignore it if you don't need it.

   - Hooks receive a hash of the following arguments:
       action        => Ext.Direct Action name for the Method

       method        => Ext.Direct Method name

       package       => Name of the package (not Action) where
                        the Method is declared

       code          => Coderef to the Method subroutine

       param_no      => Number of parameters when Method accepts
                        ordered arguments

       param_names   => Arrayref with names of parameters when Method
                        accepts named arguments

       formHandler   => True if Method handles form submits

       pollHandler   => True if Method handles Event poll requests

       arg           => Arrayref with actual arguments when Method
                        accepts ordered args, single Environment
                        object for poll handlers, hashref otherwise.

                        Note that this is a direct link to Method's @_
                        so it is possible to modify the arguments
                        in "before" hook

       env           => Environment object, see below. Like arg,
                        this is direct reference to the same object
                        that will be passed to Method, so it's
                        possible to modify it in "before" hook

       before        => Coderef to "before" hook for that Method,
                        or undef

       instead       => Coderef to "instead" hook for that Method,
                        or undef

       after         => Coderef to "after" hook for that Method,
                        or undef

       result        => For "after" hooks, the Result returned by
                        Method or "instead" hook, if any. Does not
                        exist for "before" and "instead" hooks

       exception     => For "after" hooks, an exception ($@) thrown
                        by Method or "instead" hook, if any. Does
                        not exist for "before" and "instead" hooks

       method_called => For "after" hooks, the reference to actual
                        code called as Method, if any. Can be either
                        Method itself, "instead" hook or undef if
                        the call was canceled.

       orig          => A closure that binds Method coderef to
                        its current arguments, allowing to call it
                        as easily as $params{orig}->()

ENVIRONMENT OBJECTS

Since Hooks, and sometimes Methods too, need to be aware of their Web environment, it is necessary to give them access to it in some way without locking on platform specifics. The answer for this problem is environment objects.

An environment object provides platform-agnostic interface for accessing HTTP headers, cookies, form fields, etc, by duck typing. Such object is guaranteed to have the same set of methods that behave the same way across all platforms supported by RPC::ExtDirect, avoiding portability issues.

The interface is modeled after de facto standard CGI.pm: - $value = $env->param('name') will retrieve parameter by name - @list = $env->param() will get the list of available parameters - $cookie = $env->cookie('name') will retrieve a cookie - @cookies = $env->cookie() will return the list of cookies - $header = $env->http('name') will return HTTP header - @headers = $env->http() will return the list of HTTP headers

Of course it is possible to use environment object in a more sophisticated way if you like to, however do not rely on it having a well-known class name as it is not guaranteed.

FILE UPLOADS

Ext.Direct offers native support for file uploading by using temporary forms. RPC::ExtDirect supports this feature; upload requests can be processed in a formHandler Method. The interface aims to be platform agnostic and will try to do its best to provide the same results in all HTTP environments supported by RPC::ExtDirect.

In a formHandler Method, arguments are passed as a hash. If one or more file uploads were associated with request, the argument hash will contain 'file_uploads' key with value set to arrayref of file hashrefs. Each file hashref will have the following keys: - type => MIME type of the file - size => file size, in octets - path => path to temporary file that holds uploaded content - handle => opened IO::Handle for temporary file - basename => name portion of original file name - filename => full original path as sent by client

All files passed to a Method need to be processed in that Method; existence of temporary files is not guaranteed after Method returns.

CAVEATS

In order to keep this module as simple as possible, I had to sacrifice the ability to automatically distinguish inherited class methods. In order to declare inherited class methods as Ext.Direct exportable you have to override them in subclass, like that:

package foo;
use RPC::ExtDirect;

sub foo_sub : ExtDirect(1) {
    my ($class, $arg) = @_;

    # do something
    ...
}

package bar;
use base 'foo';

sub foo_sub : ExtDirect(1) {
    my ($class, $arg) = @_;

    # call inherited method
    return __PACKAGE__->SUPER::foo_sub($arg);
}

sub bar_sub : ExtDirect(2) {
    my ($class, $arg1, $arg2) = @_;

    # do something
    ...
}

On the other hand if you don't like class-based approach, just don't inherit your packages from one another. In any case, declare your Methods explicitly every time and there never will be any doubt about what Method gets called in any given Action.

DEPENDENCIES

RPC::ExtDirect is dependent on the following modules: Attribute::Handlers, JSON.

BUGS AND LIMITATIONS

In version 2.0, ExtDirect attribute was moved to BEGIN phase instead of default CHECK phase. While this improves compatibility with Plack and Apache/mod_perl environments, this also causes backwards compatibility problems with Perl older than 5.12. Please let me know if you need to run RPC::ExtDirect 2.0 with older Perls; meanwhile RPC::ExtDirect 1.x will provide compatibility with Perl 5.6.0 and newer.

There are no known bugs in this module. Please report problems to author, patches are welcome.

SEE ALSO

Alternative Ext.Direct implementations for Perl: CatalystX::ExtJS::Direct by Moritz Onken, http://github.com/scottp/extjs-direct-perl by Scott Penrose, Dancer::Plugin::ExtDirect by Alessandro Ranellucci.

For Web server gateway implementations, see CGI::ExtDirect and Plack::Middleware::ExtDirect modules based on RPC::ExtDirect engine.

For configurable Ext.Direct API options, see RPC::ExtDirect::API module.

AUTHOR

Alexander Tokarev <tokarev@cpan.org>

ACKNOWLEDGEMENTS

I would like to thank IntelliSurvey, Inc for sponsoring my work on version 2.0 of RPC::ExtDirect suite of modules.

LICENSE AND COPYRIGHT

Copyright (c) 2011-2012 by Alexander Tokarev.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.