Perl Backend for Qooxdoo with Mojolicious
by Tobi Oetiker, OETIKER+PARTNER AG
Write the JsonRPC backend for your qooxdoo in perl, using the MojoX::Dispatcher::Qooxdoo module.
Out of the Box you get …
- The ability to run both source and build version of your qooxdoo application with the integrated webserver. No need to put everything onto an actual webserver or running from the filesystem.
- Once your are ready to deploy, your application runs as CGI, FastCGI, PSGI or via reverse proxy.
- … and you get all the other Mojolicious features like a complete testing framework on top of it
Installation
In all likelihood your local perl setup will not yet contain a copy of Mojolicious. Since all the required software is hosted on CPAN, the easiest way to install it is to use App::cpanminus. If you run cpanminus as root, the extra modules will be integrated into your local perl setup. If you run it as a normal user, the installation will go to ~/perl5.
You do not even have to install App::cpanminus in order to run it. Just pipe it into perl.
$ curl -L cpanmin.us | perl - MojoX::Dispatcher::Qooxdoo::Jsonrpc Mojo::Server::FastCGI
or if you want to install to a particular directory:
$ curl -L cpanmin.us | perl - --local-lib ~/scratch/mojo \
MojoX::Dispatcher::Qooxdoo::Jsonrpc Mojo::Server::FastCGI
all dependencies will be downloaded and installed automatically.
The Mojo::Server::FastCGI bit is only necessary if you intend to run your rpc service as a FastCGI. Mojolicious supports other deployment options too.
Using the Dispatcher with Mojolicious
Mojolicious applications are written as perl modules which get executed by a pretty generic starter application: Our application will consist of the following files:
qserv/backend/bin/qserv.pl qserv/backend/lib/QxServ/MojoApp.pm qserv/backend/lib/QxServ/JsonRpcService.pm qserv/backend/public/<the qooxdoo build> qserv/backend/t/simple.t qserv/frontend/<the qooxdoo application directory>
Lets start with the initial perl script:
#!/usr/bin/perl # if mojo is installed in a non-standard location # tell perl where to find it. use lib $ENV{HOME}.'/scratch/mojo/lib/perl5'; # to find our own application we use the FindBin module # to locate the library relative to the location of the perl # script use FindBin; use lib "$FindBin::Bin/../lib"; # then load our application use QxServ::MojoApp; # and the Mojolicious application starter command use Mojolicious::Commands; # Mojo expects to find an instance of the application in the # MOJO_APP environment variable. $ENV{MOJO_APP} = QxServ::MojoApp->new; # finally launch Mojolicious::Commands->start;
Now for the actual Mojolicious application. It is built upon routes which get loaded at startup time and tell Mojolicious where to send web requests as they come in. To make things simple for Qooxdoo, the Mojolicious::Plugin::QooxdooJsonrpc plugin takes care of installing the necessary routes.
package QxServ::MojoApp; # Inherit from the Mojolicious package use Mojo::Base 'Mojolicious'; # Load our JsonRpcService module (see below) use QxServ::JsonRpcService; sub startup { my $self = shift; # load the Mojolicious::Plugin::QooxdooJsonrpc module $self->plugin('qooxdoo_jsonrpc', { services => { rpc => QxServ::JsonRpcService->new, } }); } 1;
Writing a JsonRPC service
With "MojoApp.pm" in place, all that is left todo, is to write the actual service module: "JsonRpcService.pm":
package QxServ::JsonRpcService; use Mojo::Base -base; # whenever a service method is about to be executed, the dispatcher calls the # "allow_rpc_access" method to determine if the incoming request should be satisfied. # You can use this facility to enforce access control to the methods of your service. # Make sure to only allow access to the things you want to be public. Also the answers # of the "allow_rpc_access" method could depend on the session status. our %allow_access = ( echo => 1 ); sub allow_rpc_access { my $self = shift; my $method = shift; return $allow_access{$method}; } # the echo method returns what it hears except when it hears 'die' then # it dies. Here we use a hash pointer with a code and a message property # with this we can control what gets sent back to the qooxdoo application. sub echo { my $self = shift; my $arg = shift; if ($arg eq 'die'){ die {code=>666,message=>"Aaaargh!"}; } return "I hear: $arg"; }
Creating Tests
Before throwing a real qooxdoo application at the new service, we can easily write a test application to check that everything is working as expected:
#!/usr/bin/perl use lib $ENV{HOME}.'/scratch/mojo/lib/perl5'; use FindBin; use lib $FindBin::Bin.'/../lib'; use Test::More tests => 5; use Test::Mojo; # check if MojoApp loads ok use_ok 'QxServ::MojoApp'; # create a test harness for the application my $t = Test::Mojo->new(app =>QxServ::MojoApp->new()); # check if normal operations work $t->post_ok('/jsonrpc','{"id":1,"service":"rpc", "method":"echo","params":["hello"]}') ->json_content_is({id=>1,result=>'I hear: hello'}); # check if the exception works $t->post_ok('/jsonrpc','{"id":2,"service":"rpc","method":"echo","params":["die"]}') ->json_content_is({error=>{origin=>2,code=>666, message=>"Aaaargh!"},id=>2});
Now you can run the test script to see if your new service performs as expected.
$ perl simple.t 1..5 ok 1 - use MojoApp; ok 2 - post /jsonrpc ok 3 - exact match for JSON structure ok 4 - post /jsonrpc ok 5 - exact match for JSON structure
The Qooxdoo Application
I guess if you are reading this guide you have already setup the qooxdoo SDK and managed to get the qooxdoo hello world application up and running. Lets create the frontend of our application in an appropriately named directory.
$ cd ~/scratch/qserv $ create-application.py -n frontend -s qserv -t gui
Modify "./frontend/source/class/qserv/Application.js" a bit to include a JsonRPC call:
qx.Class.define("qserv.Application", { extend : qx.application.Standalone, members : { main: function() { this.base(arguments); if (qx.core.Variant.isSet("qx.debug", "on")) { qx.log.appender.Native; qx.log.appender.Console; } var doc = this.getRoot(); var input = new qx.ui.form.TextField("Hello World"); doc.add(input,{left: 10, top: 50}); var send = new qx.ui.form.Button("Get Echo", "qserv/test.png"); doc.add(send, {left: 200, top: 50}); send.addListener("execute", function(e) { var rpc=new qx.io.remote.Rpc().set({ url: 'jsonrpc/', serviceName : 'rpc' }); rpc.callAsync(function(ret,exc){ if (exc){ alert("ERROR " + exc.code + ": " + exc.message); } else { alert("ECHO: " + ret); } },'echo',input.getValue()); }); } } });
Now compile and run the backend server on the source version:
$ cd frontend $ ./generate.py source-hybrid $ env QX_SRC_MODE=1 ../backend/bin/qserv.pl daemon
Now surf to http://localhost:3000 and enjoy. Try to get an echo when typing 'die' into the textfield.
Into Production
Once you are done with developing your application, it is time to wrap things up for production.
./generate.py build cp -rp build ../backend/public
Again you can run
backend/bin/qserv.pl daemon
and access the server on http://localhost:3000
You probably want to run your application from your webserver. You can run "qserv.pl" in CGI or fastCGI mode. If you want good performance use the fastCGI interface.
Configure your apache webserver with fastcgi support and place a ".htaccess" into the webtree:
RewriteEngine On
RewriteBase /qserver-web
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule (.*) qserv.fcgi/$1
By setting the MOJO_MODE to 'production' in the "qserv.fcgi" your code will produce less debug output.
#!/bin/sh export MOJO_MODE=production exec "$HOME/scratch/qserv/backend/bin/qserv.pl" fastcgi