Perl Programming/HTTP::Engine

HTTP::Engine is made by Kazuhiro Osawa (yappo). It is an generalized interface for application developers that makes it easy to deploy under different kinds of server configurations, including CGI, FastCGI, mod_perl and standalone pure-Perl HTTP Server. It is not, however, a full-stack web application framework, but it'll be easy to write a framework based on it.

One application core with multiple interfacesEdit

For example, this is a little application just outputs "Hello World":

# MyApp.pm
package MyApp;
use strict;
use warnings;

use HTTP::Engine::Response;

sub run {
  my ($class, $request) = @_;
  my $response = HTTP::Engine::Response->new;

  $response->body("Hello World");
  $response->status(200);

  return $response;
}

1;

The run method here takes an instance of HTTP::Engine::Request as its input, and returns an HTTP::Engine::Response object as its output. This sub-routine is the first-handler from HTTP server. In a more complex application, it usually just dispatches the real work to some other sub-routine based on the URI or parameters.

To deploy it as a CGI script, you then write an "hello.cgi" like this:

#!/usr/bin/perl
use MyApp;
use HTTP::Engine;

HTTP::Engine->new(
    interface => {
        module => 'CGI',
        request_handler => sub {
            MyApp->run(@_);
        }
    }
)->run;

If someday, you need to deploy this application under mod_perl environment, you can just write a MyApp::ModPerl handler class with code like this:

package MyApp::ModPerl;
use Any::Moose;
extends 'HTTP::Engine::Interface::ModPerl';

use MyApp;
use HTTP::Engine;

sub create_engine {
    my($class, $r, $context_key) = @_;

    HTTP::Engine->new(
        interface => {
            module => "ModPerl",
            request_handler => sub {
                MyApp->run(@_);
            }
        }
    );
}

__PACKAGE__->meta->make_immutable;
no Any::Moose;
1;

Your core application code does not need to be modified, so long as you only use HTTP::Engine::* classes to construct your response.

TestingEdit

You might question that why bother having an abstraction layer instead of just choose stick to a good deploy environment. One good answer is that might not be easy to test the app without this abstraction layer.

If your deployment configuration of choice is like mod_perl, then your test programs would need to start up an apache httpd with mod_perl.so loaded, and then try to hit it with maybe Test::WWW::Mechanize. That would work, too, but it's too troublesome.

HTTP::Engine comes with a specialized Test interface that makes this very easy. To test if our Hello world application works, you write a test program like this:


# test.pl
use strict;
use warnings;

use Test::More tests => 1;

use MyApp;
use HTTP::Engine;
use HTTP::Request;
use HTTP::Response;

my $engine = HTTP::Engine->new(
    interface => {
        module => 'Test',
        request_handler => sub {
            return MyApp->run(@_);
        }
    }
);

my $response = $engine->run(
    HTTP::Request->new(GET => 'http://localhost/'),
    env => \%ENV,
    connection_info => {
        request_uri => "/"
    }
);

is($response->content, "Hello World", "The application does output Hello World!");

If you compared the construction statement of $engine object, you will see the only difference is the "module" value. This "Test" module makes the $engine object a totally mocked-up environment that basically just prepares every environment variables needed, and just execute your application code.

This makes it very lightweight and easy to run and write the test program. You can then write functional tests just like that way you write unit tests.

Last modified on 10 December 2009, at 20:58