[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

SF.net SVN: ledger-smb:[4744] trunk/rest-handler.pl



Revision: 4744
          http://ledger-smb.svn.sourceforge.net/ledger-smb/?rev=4744&view=rev
Author:   einhverfr
Date:     2012-05-22 03:55:12 +0000 (Tue, 22 May 2012)
Log Message:
-----------
Initial implementation of REST handler

Added Paths:
-----------
    trunk/rest-handler.pl

Added: trunk/rest-handler.pl
===================================================================
--- trunk/rest-handler.pl	                        (rev 0)
+++ trunk/rest-handler.pl	2012-05-22 03:55:12 UTC (rev 4744)
@@ -0,0 +1,293 @@
+=head1 NAME
+
+LedgerSMB::Handlers::REST_Handler - REST handler for new code sections
+
+=head1 SYNPOSIS
+
+This is invoked via http, for example, to get a customer:
+
+  GET www.myhost.com/ledgersmb/rest/1.4/my_company/customer/242.xml
+
+To attach a new note, you might post an xml document to:
+
+  POST www.myhost.com/ledgersmb/rest/1.4/my_company/customer/242/note.xml
+
+Similarly to retrieve all notes for this customer:
+
+  GET www.myhost.com/ledgersmb/rest/1.4/my_company/customer/242/note.xml
+
+=head1 DESCRIPTION
+
+=head2 URL Syntax
+
+Everything before the /rest/ part of the URL is ignored.  After the /rest/ 
+the url is given fixed semantic meaning based on the following pattern:
+[company_name]/[object class][/id][/subresource][.format]
+
+=head2 Authentication
+
+Authentication information is provided using HTTP authentication headers. 
+Currently only HTTP Basic is supported though Kerberos could be supported with
+a little effort. Please use it over SSL.
+
+=head2 Delegated Format Parsing API
+
+Every supported format handler supports two methods:  from_input and to_output.
+The former transforms the format into a hashref, and the latter transforms a
+hashref into a file of this format.  It could do so using Template Toolkit or
+some other manner and so arbitrary formats are thus supported.
+
+The format can be determined either by the CONTENT_TYPE header or the
+extension.  Currently the system just checks the portion following the slash
+and uses that.  Future versions may perform a lookup against the mime_types
+table to look up handlers.  Sub-types can be indicated using an underscore, so
+text/xml_mydialect would dispatch to the XML_mydialect handler if available and
+if not to the XML handler.  This would be eqivaent to an extension of
+.xml_mydialect.
+
+The reason for the subtyping options is that this allows for one to create, for
+example, an EDI handler and specify subtypes either as subclasses or as
+additional options within the same class.
+
+=head2 Delegated Object Handling API
+
+Every supported object class MUST support the following methods (even if they
+do nothing but throw an error):
+
+=over
+
+=item GET
+
+Retrieves one or more records.
+
+=item PUT
+
+Insert or updates a record.
+
+=item POST
+
+If an ID is provided this should check to see if the record exists, and if so
+throw an error.  If it does not, or if it does not exist, then create it.
+
+=item DELETE (optional)
+
+Deletes a resource if this is supported.
+
+=back
+
+If any of the above are not supported, and a request comes in that would hit
+that method, a 403 Forbidden error is returned.
+
+In addition each subresource should get its own method, optionally suffixed
+with _GET, _PUT, _POST, or _DELETE.  These are deleaged first to the suffixed
+version, and if that i snot found to the subresource name directly.  If that is
+not found, we throw a 404 error.
+
+Each method is pashed a hashref which contains:
+
+=over
+
+=item dbh
+
+Database connection handle to the company db
+
+=item class_name
+
+Top level class.
+
+=item id
+
+ID field for that object
+
+=item subresource
+
+Subresource field name
+
+=item args
+
+Hashref of query string args
+
+=item payload
+
+This is a hashref returned from the format handler.
+
+=back
+
+=head2 Error Handling
+
+Other scripts in this workflow should throw errors using die, and include both
+the http error number and a brief description.  Examples might be:
+
+ die '401  Unauthorized';
+
+or 
+
+ die "500  Error from function: $DBH->errstr";
+
+=head1 NOTES
+
+This is currently only supported ober CGI.  Once the old code is eliminated, we
+plan to port the entire application over to a more flexible framework.
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2012 LedgerSMB Core Team.  This file is licensed under the GNU 
+General Public License version 2, or at your option any later version.  Please
+see the included License.txt for details.
+
+=cut
+
+package LedgerSMB::REST_Handler;
+
+use CGI::Simple;
+use Try::Tiny;
+use strict;
+use warnings;
+
+return process_request();
+
+# Note:  Indenting try/catch only two characters here because it wraps all
+# substantive logic in the function.  -CT
+sub process_request{
+
+  try {
+    my $request = get_request_properties();
+
+    my $format = lc($request->{format});
+    if (! eval "require LedgerSMB::REST_Format::" . $format) {
+       eval "require LedgerSMB::REST_Format::" . $format;
+    }
+    my $fmtpackage = "LedgerSMB::REST_Format::" . $format;
+
+    if ($request->{payload}){
+        if ($fmtpackage->can('from_input')){
+            $request->{payload} = $fmtpackage->can('from_input')->($request);
+        } else {
+            die '404 Unsupported Format';
+        }
+    }
+
+    my $classpkg = "LedgerSMB::REST_Class::" . $request->{class};
+    eval "require $classpkg" || return error_handler('404 Resource Not Found');
+
+    my $restobj;
+    if ($classpkg->can('new')){
+        $restobj = $classpkg->can('new')->($request);
+    } else {
+        die '500 Bad Class Definition';
+    }
+
+    if ($request->{subresource}){
+
+       my $suffixed_method = "$request->{subresource}_" 
+             . lc($request->{method});
+       if ($restobj->can($suffixed_method)){
+           $restobj->can($suffixed_method)->($request);
+       } else {
+           $restobj->can($request->{subresource}) 
+                  || die '404 Resource Not Found';
+       }
+
+    } else {
+
+        if ($classpkg->can($request->{method})){
+            $classpkg->can($request->{method})->($request);
+        } else {
+            die '401 Method Not Allowed';
+        }
+
+    }
+
+    my $content;
+    my $ctype;
+    if ($request->{payload}){
+        if ($fmtpackage->can('to_output')){
+            $content = $fmtpackage->can('to_output')->($request);
+        } else {
+            return error_handler('404 Unsupported Format');
+        }
+    }
+    if ($fmtpackage->can('mime_type')){
+        $ctype = $fmtpackage->can('mime_type')->();
+    }
+
+    return output({state => '200 Success', 
+                 content => $content, 
+                 content_type => $ctype});
+  } catch {
+    return error_handler($_);
+  }
+}
+
+sub error_handler {
+    my ($error) = @_;
+    my $content = $error;
+    $content =~ s/^\d\d\d\s//;
+    $error =~ s/(.*$).*/$1/m;
+    if ($error =~ /^\d\d\d\s/){
+        $error = "500 $error";
+    }
+    output({state => $error, content => $content});
+}
+
+# Isolating request-> hashref logic so that it is easier to port to other
+# environments --CT
+
+sub get_request_properties {
+    my $cgi = CGI::Simple->new();
+    use LedgerSMB::Auth;
+
+    my $creds = LedgerSMB::Auth::get_credentials();
+    my $request = {};
+    my $url = $cgi->self_url();
+
+
+    $request->{args} = $cgi->Vars();
+    $request->{method} = $ENV{REQUEST_METHOD};
+    $request->{payload} = $cgi->param( "$request->{method}DATA" );
+
+    $url =~ s|/rest/(.*)|$1|;
+    $url =~ s|(\.[^/]$)||;
+    $request->{format} = $1;
+
+    my @components = split /\//, $url;
+
+    $request->{dbh} = DBI->connect(
+        "dbi:Pg:dbname=$components[0]", 
+        "$creds->{login}", "$creds->{password}", 
+            { AutoCommit => 0 }
+    );
+
+    if (!$request->{dbh}) {
+           die '403 Authentication Failed';
+    }
+
+    if (!$request->{format}){
+        my $fmt = $ENV{CONTENT_TYPE};
+        $fmt =~ /([^\/]*$)/;
+        $request->{format} = $1;
+    }
+
+    $request->{class_name} = $components[1];
+    $request->{id} = $components[2];
+    $request->{subresource} = $components[3];
+    
+    return $request;
+}
+
+# Isolating output routine 
+sub output {
+    my ($args) = $_;
+    my $ctype;
+    my $cgi = CGI::Simple->new();
+
+    if ($args->{content_type}){
+        $ctype = $args->{content_type};
+    } else {
+        $ctype = 'text/text';
+    }
+    $cgi->header($ctype, $args->{state});
+    $cgi->put($args->{content});
+}
+
+1;

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.