NAME
RPC::ExtDirect::Server - A tiny but capable Ext.Direct server
SYNOPSIS
use RPC::ExtDirect::Server;
my $server = RPC::ExtDirect::Server->new(
port => 8080,
static_dir => 'htdocs',
);
print "Ext.Direct server is running on port 8080\n";
$server->run();
DESCRIPTION
This module implements a minimal Ext.Direct capable server in pure Perl. Its main purpose is to be used as a lightweight drop-in replacement for more complex production environments like Plack or Apache/mod_perl, e.g. for testing and mockups.
It can also be used successfully as the basis for production application servers when feature richness is not a requirement, or when backwards compatibility is the primary concern. Another possible application for RPC::ExtDirect::Server is embedded systems; due to its small footprint and pure Perl dependencies it can be used easily in resource constrained platforms.
Finally, many legacy computing systems can be retrofitted with modern HTML5 Web interface applications with Ext.Direct back end built on top of RPC::ExtDirect::Server; it is compatible with (and is tested against!) Perl 5.6 and newer.
TESTING USAGE
This section is only of interest to the people interested in technical detail; if you just want to know how to write tests for your Ext.Direct Perl code, head over to Test::ExtDirect.
Perl to Perl
RPC::ExtDirect::Server is most commonly used as a test bed for modules that provide Ext.Direct API in client-server applications. Usually, a test script is created for every Ext.Direct Action package. At the start of the test script, an instance of RPC::ExtDirect::Server is started in background; when the test calls have been made and the script is finishing, the server instance will be automatically shut down.
An example of such script:
# t/01_test.t
use Test::More tests => 2;
use RPC::ExtDirect::Server::Util;
# Ext.Direct Action package
use MyApp::Server::Foo;
my ($host, $port) = maybe_start_server();
ok $port, "Got host: $host with port: $port";
# Test something involving MyApp::Server::Foo
...
# Optional, the server will be shut down automatically
# when the script is finished
stop_server();
RPC::ExtDirect::Server::Util package provides utility functions for starting and stopping the server. Special attention is paid to starting the server correctly, avoiding duplicate starts and potential collisions on ports that may be already listened on by other applications.
It is also possible to bypass starting an instance of RPC::ExtDirect::Server when another server is listening at a known port; this is useful for debugging server side Ext.Direct Methods. The complement is starting the server in foreground, to debug the code:
# In terminal session 1 (-f means don't fork, start in foreground)
perl -d t/01_foo.t -f
RPC::ExtDirect::Server is listening on 127.0.0.1:39680
# In terminal session 2 (-p means don't start server, use port)
perl t/01_foo.t -p 39680
1..x
...
See also RPC::ExtDirect::Client for details on making Ext.Direct calls from a pure Perl client, and Test::ExtDirect for examples of test scripts built on top of both Server and Client.
Perl to JavaScript
A similar usage pattern involves starting an RPC::ExtDirect::Server instance with one or more Ext.Direct Action packages loaded, and running JavaScript client tests against it with a headless browser using a module like WWW::Mechanize::PhantomJS or similar.
Alternatively you can use a browser control tool like Selenium to run the tests in a live browser environment. See WWW::Selenium and more modern Selenium::Remote::Driver for details and examples.
PRODUCTION USAGE
Overview
At this point you may be asking: why would I even think about using RPC::ExtDirect::Server in production environment? Isn't Plack what is considered the industry standard for Perl Web applications these days? And you would be absolutely right.
Except one tiny thing: Plack comes with a cost. It depends on many modules that may not build on your system, it does not support Perls older than 5.8, and it adds overhead (albeit small) to HTTP request processing. But the main reason is: Plack gives nothing substantial for HTML5 applications.
In traditional Web sites and applications, you may have needed to generate HTML markup for the pages; usually with a templating tool like Template::Toolkit or similar. You also needed to create URI handlers to respond to user input; you may have used a fully featured framework like Catalyst, Dancer, or Mojolicious to do that.
In HTML5 applications built with Ext JS or Sencha Touch, you don't create HTML pages anymore. You write JavaScript instead, that will be downloaded to the client browser and executed; in turn, that JavaScript code will create the HTML markup for the browser to render, and handle user interaction. It can also use Ext.Direct to communicate with the server side.
While this approach places heavier computational burden on the client browser, it also simplifies things enormously on the server side. In fact, the server tasks are reduced to only two: serving static content (JavaScript files, CSS stylesheets, images, etc), and handling Ext.Direct requests.
That also means that there is no need to have a glue framework like Plack between raw HTTP requests and the actual code that implements Ext.Direct Methods, unless you plan to deploy a hybrid old-school/HTML5 RIA application, or migrate to RIA architecture in stages.
This approach has been successfully proven in production with an application server that was later generalized into this module and released to CPAN (not the other way around).
Handling Ext.Direct requests
The main purpose of RPC::ExtDirect::Server is serving Ext.Direct requests. It does not need any particular configuration to do that; just use
the modules that implement your Ext.Direct Actions in the script that runs the server.
Sometimes you need to adjust some configuration option, or create the Ext.Direct API dynamically instead of using attributes; you can pass several options that affect server behavior to the server's constructor:
use RPC::ExtDirect::API;
use RPC::ExtDirect::Config;
use RPC::ExtDirect::Server;
my $config = RPC::ExtDirect::Config->new(
foo => 'bar',
);
my $api = RPC::ExtDirect::API->new_from_hashref(
config => $config,
api_href => {
'MyApp::Server::Foo' => {
...
}
}
);
my $server = RPC::ExtDirect::Server->new(
api => $api,
config => $config,
static_dir => '/var/run',
...
);
$server->run();
RPC::ExtDirect::Server has three public methods that can be overridden in a subclass to enhance or augment its default behavior; see "handle_extdirect_api", "handle_extdirect_router", and "handle_extdirect_poll".
Serving static documents
Sometimes the application server you are creating is really tiny and does not justify setting up a fully-fledged front end HTTP server like Apache or Nginx; or you may be working on a prototype and want do avoid the hassle of setting up and configuring reverse proxy deployment.
For situations like this, RPC::ExtDirect::Server has a built in static document handler. It is very basic and not very performant but is perfectly capable of serving any content including HTML files, images, JavaScript files, etc.
If HTTP::Date module is installed, the server will honor the If-Modified-Since
HTTP header sent by clients; if the document requested has not been changed since that time, "304 Not Modified" will be served instead of the actual content. This helps greatly with downloading multiple JavaScript files that Ext JS or Sencha Touch projects are usually comprised of, without the need to implement a forking back end.
If File::LibMagic or File::MimeInfo is installed, it will be used to detect the content type of the files served. If both are installed, File::LibMagic takes precedence. If neither is available, the built in guesstimator will look at the file extension (suffix) and match it to the list of most common MIME types.
There are several configuration options that affect the static handler, see constructor.
Custom URI handlers
In certain cases, you may want to run some code matched to some URI instead of serving a document; an example would be serving plain Ajax requests, or creating a well-known alias for a document.
In such cases, a custom URI handler can be useful. A handler is a piece of Perl code that will be called as if it was an RPC::ExtDirect::Server instance method, passing the server and CGI objects to it:
sub custom_uri {
my ($self, $cgi) = @_;
my $path_info = $cgi->path_info();
# logit() is a dummy logger in RPC::ExtDirect::Server
$self->logit("Called for URI: $path_info");
# Do something, print full HTTP headers and content
print $cgi->header(
-status => '200 OK',
...
);
...
return 1;
}
The handlers are installed by passing "dispatch" configuration option to the RPC::ExtDirect::Server constructor:
my $server = RPC::ExtDirect::Server->new(
...
dispatch => [
# Format is simple: URI_matcher => coderef
qr{^/foo} => \&custom_uri, # Regex objects are ok
'^/bar' => \&custom_uri, # String patterns are also ok
],
);
The handlers are matched top to bottom and the first one wins; this means that if more than one handlers will match an URI, only the first one will be called.
Note that Ext.Direct calls to the API generator ("handle_extdirect_api"), Router ("handle_extdirect_router"), and Event Provider ("handle_extdirect_poll") are going through the same dispatch mechanism; these handlers are matched to the URIs specified by api_path, router_path, and poll_path Config options, respectively. The Ext.Direct handlers are installed after all custom handlers and it is possible to intercept requests to Ext.Direct URIs if any of your handlers will match these URIs before the standard handlers have a chance to kick in.
CGI.pm or else?
While CGI.pm has been one of the most venerable Perl modules since time immemorial, it has long been touted for deprecation - and for a host of very valid reasons! It is not recommended to be used in new projects anymore and is going to be removed from the Perl core in 5.22. It is already available on CPAN and can be installed from there if you really need it.
RPC::ExtDirect::Server is built around CGI.pm API but that does not mean that CGI.pm is the only module it can work with. CGI::Simple can be used instead, and is tested against to ensure future proofness. In fact when CGI::Simple is detected, it will be used instead of CGI.pm with no action required on your part.
It is also possible that other modules may be implementing API that is compatible with CGI.pm; in that case RPC::ExtDirect::Server should also work with these modules with minimal or no modifications. If you would like to use a custom module, you will need to pass the "cgi_class" configuration option to the server constructor:
my $server = RPC::ExtDirect::Server->new(
cgi_class => 'CGI::Foo',
...
);
Doing this will assign cgi_class
and set cgi_init
method for the underlying HTTP::Server::Simple::CGI to use.
You can also set the "cgi_class" config option to use CGI.pm even if CGI::Simple is installed:
my $server = RPC::ExtDirect::Server->new(
cgi_class => 'CGI',
...
);
Handling errors
As with any HTTP server handling static content, error conditions are unavoidable and have to be treated properly to be recognized by client browsers. RPC::ExtDirect::Server provides only basic handling for 403 (Forbidden) and 404 (Not found) errors: a log message will be printed to STDERR if debugging is turned on, and a header with corresponding error status will be printed out, but no response body. Anything more advanced you would have to implement in a subclass.
By convention, the methods that handle errors are named handle_xxx
, where xxx
is the error code. You can override the handle_403
and handle_404
methods; they receive the CGI object and URI that caused the error as positional arguments.
PRODUCTION DEPLOYMENT
If you plan to use RPC::ExtDirect::Server in production environment, there are some things to consider with regards to server deployment.
The first thing is choosing the deployment architecture. Some popular options are standalone application server and reverse proxy configuration.
Standalone application server
In this configuration, RPC::ExtDirect::Server is the only HTTP server component, combining both application server and static document server. This option is well suited for small deployments serving applications that will produce low amount of HTTP requests, especially for static documents.
Unfortunately there is no hard and fast rule for determining what exactly is low amount of requests, as that would depend greatly on the hardware capabilities of the server system; as a rule of thumb, a system that gets less than 10 requests per second at peak could be considered a good fit for the standalone deployment option.
If you plan to have the server listening at port 80 (HTTP) or 443 (HTTPS) in Unix-like systems, you will need to start it with elevated privilege level (root); it is also recommended to drop the privilege level to a restricted user immediately after binding to the port. Do not write the code to do that yourself, use Net::Server instead.
It is also very helpful to have HTTP::Date module available in @INC
, as this will turn on If-Modified-Since
header support in RPC::ExtDirect::Server. This helps greatly by avoiding serving rarely changed documents; for a typical HTML5 RIA application that would be its HTML and JavaScript files, CSS stylesheets, and image assets that comprise 99% of all application content.
Reverse proxy configuration
In this case, RPC::ExtDirect::Server is placed behind a front end HTTP server like Apache or Nginx that will listen to incoming requests and serve all documents except preconfigured set of URIs that it will relay, or proxy, to the application server. This option works well for larger applications where you would anticipate the need for load balancing, or a lot of static HTTP traffic hitting the server.
In reverse proxy setups, RPC::ExtDirect::Server is usually responsible only for serving Ext.Direct requests; occasionally you may also want to map certain URIs to a custom URI handler.
It is not recommended to run the application server with elevated privilege level in this configuration; when both the front end server and application server are running on the same host it is also advisable to have the application server listening only on the loopback interface (127.0.0.1 or similar) so that it would not be accessible directly but only through the front end server.
For an example of reverse proxy configuration setup with Nginx, see this article: http://nginx.com/resources/admin-guide/reverse-proxy.
Increasing server performance
For production deployment you should also consider the application server performance. When testing your code, the bulk of the RPC::ExtDirect::Server run time is spent starting up and tearing down; rarely the server speed would become an issue. Not so for production environment where you expect the server process to spend most of its time handling actual requests.
The biggest limiting factor is the one-thread one-process architecture of HTTP::Server::Simple on which RPC::ExtDirect::Server is based. However, this problem can be easily remedied by using Net::Server module with one of its personalities, e.g. Net::Server::PreFork:
package My::Server;
use Net::Server::PreFork;
use RPC::ExtDirect::Server;
use base 'RPC::ExtDirect::Server';
# This should return the class name of a Net::Server personality
sub net_server { 'Net::Server::PreFork' }
package main;
my $server = My::Server->new(
host => '127.0.0.1',
port => 8080,
static_dir => '/var/run',
);
$server->run();
Another easy speed gain can be had by installing JSON::XS module; it will be used automatically when detected.
Troubleshooting server issues
Sometimes you may need to turn on debugging output to troubleshoot issues. To do that, pass debug
option to server constructor:
my $server = RPC::ExtDirect::Server->new(
debug => 1,
...
);
Logging is done by the "logit" method that prints messages to STDERR. You can override the method to do something more advanced.
SERVER OBJECT INTERFACE
RPC::ExtDirect::Server provides several public methods:
new
-
Constructor. Returns a new server instance but does not start listening to requests (call </run> for that). Accepts named arguments in a hash.
Parameters:
api
-
Optional RPC::ExtDirect::API instance to be used instead of the default global API tree.
config
-
Optional RPC::ExtDirect::Config instance to be used. If not provided, the Config instance in the API object (either default or passed in "api" parameter) will be used.
host
-
Optional hostname or IP address to bind to. Defaults to 127.0.0.1.
port
-
Optional port to bind to. Defaults to 8080.
static_dir
-
Optional path to the root directory for the static content. Defaults to
/tmp
. cgi_class
-
Optional class name to use instead of CGI to instantiate CGI objects.
dispatch
-
Optional arrayref with custom URI handlers. Use the following format:
[ # URI_matcher => coderef qr{^/foo} => \&custom_uri, # Regex objects are ok '^/bar' => \&custom_uri, # String patterns are also ok ]
index_file
-
Name of the index file that clients will be redirected to when they request a directory instead of a document. Defaults to
index.html
. expires_after
-
Expiration interval for the static documents to set in the
Expires
header, in seconds. Defaults to 3 days. buffer_size
-
Buffer size in bytes to use when reading files from disk and writing them to the socket. Defaults to 256 kilobytes.
run
-
Instance method. Runs the server, never returns.
There are also several instance methods not intended to be called directly that can be overridden in a subclass to augment their behavior:
handle_request
-
The topmost method that handles every request. Accepts only one positional argument, which is a new CGI object (or similar). Tries to match the request URI to any of the handlers and run it if found; if not, runs "handle_default".
You will rarely need to override this method, if ever.
handle_default
-
This method will process requests that are not handled by any custom URI handlers or default Ext.Direct handlers. Usually this means a request for static content, or an error;
handle_default
will call a corresponding handler (see below). handle_directory
-
This method will process requests for URIs that match directories in the file system and issue redirects to the corresponding index files (see "index_file") with HTTP code 301.
handle_static
-
This method will process requests for URIs that match files in the file system, with root defined by "static_dir" parameter. If the file is not readable, "handle_403" will be called to serve an error response; otherwise, the file content will be served with status code 200.
handle_extdirect_api
-
This method will process requests for Ext.Direct API; it is in fact a thin wrapper for "api" in CGI::ExtDirect method.
handle_extdirect_router
-
This method will process Ext.Direct Router requests. This is a wrapper for "route" in CGI::ExtDirect method.
handle_extdirect_poll
-
This method will process Ext.Direct Event poll requests. This is a wrapper for "poll" in CGI::ExtDirect method.
handle_403
-
This method is called when an URI was matched to a file in the file system, but it's unreadable by the server process. The default action is to print HTTP headers with "403 Forbidden" status code and an empty body.
handle_404
-
This method is called when an URI was not matched to either a handler or static file or directory. The default action is to print HTTP headers with "404 Not Found" status code and an empty body.
logit
-
Print
@_
as debug output to STDERR, but only if "debug" flag is set to truthy value. -
Print welcoming banner to STDOUT if debugging is on. The default banner is:
__PACKAGE__: You can connect to your server at http://host:port/
ACCESSOR METHODS
For RPC::ExtDirect::Server, the following accessor methods are provided:
api
-
Return the current RPC::ExtDirect::API instance held in the server object, or set a new one.
config
-
Return the current RPC::ExtDirect::Config instance held in the server object, or set a new one.
dispatch
-
Return the current arrayref with URI handlers, or set a new one. Note that this arrayref is different from the "dispatch" constructor parameter, and should not be changed unless you know what you are doing.
static_dir
-
Return the current static content root directory, or set a new one.
index_file
-
Return the current index file name, or set a new one.
expires_after
-
Return the current value for
Expires
header to set when serving static content, or set a new one. buffer_size
-
Return the buffer size to use when reading files from disk, or set a new one.
ACKNOWLEDGEMENTS
I would like to thank IntelliSurvey, Inc for sponsoring my work on this module.
BUGS AND LIMITATIONS
At this time there are no known bugs in this module. Please report problems to the author, patches are always welcome.
Use Github tracker to open bug reports, this is the easiest and quickest way to get your issue fixed.
COPYRIGHT AND LICENSE
Copyright (c) 2012-2015 Alex Tokarev <tokarev@cpan.org>.
Portions of the code that were taken from HTTP::Date are copyright (c) 1995-1999, Gisle Aas.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.