NAME

POEx::HTTP::Server - POE HTTP server

SYNOPSIS

use POEx::HTTP::Server;

POEx::HTTP::Server->spawn( 
                inet => {
                            LocalPort => 80 
                        },
                handlers => [
                            '^/$' => 'poe:my-alias/root',
                            '^/static' => 'poe:my-alias/static',
                            '' => 'poe:my-alias/error'
                        ]
                );
            

# events of session my-alias:
sub root {
    my( $heap, $req, $resp ) = @_[HEAP,ARG0,ARG1];
    $resp->content_type( 'text/html' );
    $resp->content( << HTML );
<html>...</html>
HTML
    $resp->done;
}

sub static {
    my( $heap, $req, $resp ) = @_[HEAP,ARG0,ARG1];
    my $file = File::Spec->catfile( $heap->{root}, $req->path );
    $resp->sendfile( $file );
}

sub error {
    my( $heap, $req, resp ) = @_[HEAP,ARG0,ARG1];
    $resp->error( 404, "Nothing to do for ".$req->path );
}

DESCRIPTION

POEx::HTTP::Server is a clean POE implementation of an HTTP server. It uses POEx::URI to simplify event specification. It allows limiting connection concurrency and implements HTTP 1.1 keep-alive. It has built in compatibility with POE::Component::Daemon's "prefork" server support.

POEx::HTTP::Server also includes a method for easily sending a static file to the browser, with HEAD and If-Modified-Since support.

POEx::HTTP::Server enforces some of the HTTP 1.1 requirements, such as the Content-Length and Date headers.

POEx::HTTP::Server differs from POE::Component::Server::HTTP by having a cleaner code base and by being actively maintained.

POEx::HTTP::Server differs from POE::Component::Server::SimpleHTTP by not using Moose and not using the YELLING-STYLE of parameter passing.

METHODS

spawn

POEx::HTTP::Server->spawn( %CONFIG );

Spawns the server session. %CONFIG contains one or more of the following parameters:

inet

POEx::HTTP::Server->spawn( inet => $HASHREF );

Specify the parameters handed to POE::Wheel::SocketFactory when creating the listening socket.

As a convenience, LocalAddr is changed into BindAddr and LocalPort into BindPort.

Defaults to:

POEx::HTTP::Server->spawn( inet => { Listen=>1, BindPort=> 80 } );

handlers

POEx::HTTP::Server->spawn( handlers => $HASHREF );
POEx::HTTP::Server->spawn( handlers => $ARRAYREF );

Set the events that handle a request. Keys to $HASHREF are regexes which match on all or part of the request path. Values are url to the events that will handle the request.

The regexes are not anchored. This means that /foo will match the path /something/foo. Use ^ if that's what you mean; ^/foo.

Specifiying an $ARRAYREF allows you to control the order in which the regexes are matched:

POEx::HTTP::Server->spawn( handlers => [ 
                    'foo'  => 'poe:my-session/foo',
                    'onk'  => 'poe:my-session/onk',
                    'honk' => 'poe:my-session/honk',
                ] );

The handler for onk will always match before honk can.

Use '' if you want a catchall handler.

See "HANDLERS" below.

handler

POEx::HTTP::Server->spawn( handler => $uri );

Syntatic sugar for

POEx::HTTP::Server->spawn( handler => [ '' => $uri ] );

alias

POEx::HTTP::Server->spawn( alias => $ALIAS );

Sets the server session's alias. The alias defaults to 'HTTPd'.

concurrency

POEx::HTTP::Server->spawn( concurrency => $NUM );

Sets the request concurrency level; this is the number of connections that are allowed in parallel. Set to 1 if you want zero concurrency, that is only one connection at a time.

Be aware that by activating "keepalive", a connection may last for many seconds. If concurrency is low, this will severly limit the availability of the server. If only want one request to be handled at a time, either turn set keepalive off or use "prefork".

Defaults to (-1), unlimited concurrency.

headers

POEx::HTTP::Server->spawn( headers => $HASHREF );

All the key/value pairs in $HASHREF will be set as HTTP headers on all responses.

By default, the Server header is set.

keepalive

POEx::HTTP::Server->spawn( keepalive => $N );

keepalivetimeout

POEx::HTTP::Server->spawn( keepalivetimeout => $TIME );

options

POEx::HTTP::Server->spawn( options => $HASHREF );

Options passed POE::Session->create. You may also specify debug to turn on some debugging output.

prefork

POEx::HTTP::Server->spawn( prefork => 1 );

Turns on the POE::Component::Daemon pre-fork server support. You must spawn and configure the POE::Component::Daemon yourself.

Defaults to 0, no preforking support.

In a normal pre-forking server, only one request may be handled by a child process at the same time. This is equivalent to "concurrency" = 1. However, you may set concurrecy to another value, so that each child process may handle several request at the same time. This has not been tested.

retry

POEx::HTTP::Server->spawn( retry => $SECONDS );

If binding to the port fails, the server will wait $SECONDS to retry the operation.

Defaults to 60. Use 0 to turn retry off.

HANDLERS

A handler is a POE event that will handle a given HTTP request. ARG0 is a POEx::HTTP::Server::Request object. ARG1 is a POEx::HTTP::Server::Response object. The handler should query the request object for details or parameters of the request.

my $req = $_[ARG0];
my $file = File::Spec->catfile( $doc_root, $req->uri->path );

my $query = $req->uri->query_form;

my $conn = $req->connection;
my $ip   = $conn->remote_ip;
my $port = $conn->remote_port;

The handler must populate the response object with necessary headers and content. If the handler wishes to send an error to the browser, it may set the response code. A default code of RC_OK (200) is used. The response is the send to the browser with either respond or send. When the handler is done, done is called on the response object.

my $resp = $_[ARG1];
$resp->content_type( 'text/plain' );
$resp->content( "Hello world\n" );
$resp->respond;
$resp->done;

use HTTP::Status;
$resp->code( RC_FOUND );
$resp->header( 'Location' => $new_uri );

$resp->content_type( 'text/plain' );
my $io = IO::File->new( $file );
while( <$io> ) {
    $resp->send( $_ );
}
$resp->done;

The last example is silly. It would be better to use sendfile like so:

$resp->content_type( 'image/gif' );
$resp->sendfile( $file );
# Don't call ->done after sendfile

Handlers may chain to other event handlers, using normal POE events. You must keep track of at least the request handler so that you may call done when the request is finished.

Here is an example of an unrolled loop:

sub handler {
    my( $heap, $resp ) = $_[HEAP,ARG1];
    $heap->{todo} = [ qw( one two three ) ];
    $poe_kernel->yield( next_handler => $resp );
}

sub next_handler {
    my( $heap, $resp ) = $_[HEAP,ARG0];

    # Get the request object from the response
    my $req = $resp->request;
    # And you can get the connection object from the request

    my $h = shift @{ $heap->{todo} };
    if( $h ) {
        # Send the content returned by event handlers in another session
        $resp->send( $poe_kernel->call( $heap->{session}, $h, $req ) );
        $poe_kernel->yeild( next_handler => $resp );
    }
    else {
        $poe_kernel->yield( 'last_handler', $resp );
    }
}

sub last_handler {
    my( $heap, $resp ) = $_[HEAP,ARG0];
    $resp->done;
}

Handler parameters

POE URIs are allowed to have their own parameter. If you use them, they will appear as a hashref in ARG0 with the request and response objects as ARG1 and ARG2 respectively.

POEx::HTTP::Server->spawn( handler => 'poe:my-session/handler?honk=bonk' );

sub handler {
    my( $args, $req, $resp ) = @_[ARG0, ARG1, ARG2];
    # $args = { honk => 'bonk' }
}

Special handlers

There are 5 special handlers that are invoked when a browser connection is opened and closed, before and after each request and when an error occurs.

The note about "handler parameters" also aplies to special handlers.

on_connect

Invoked when a new connection is made to the server. ARG0 is a POEx::HTTP::Server::Connection object that may be queried for information. This connection object is shared by all requests objects that use this connection.

POEx::HTTP::Server->spawn( 
                    handlers => { on_connect => 'poe:my-session/on_connect' }
                 );
sub on_connect {
    my( $object, $connection ) = @_[OBJECT, ARG0];
    # ...
}

It goes without saying that if you use "keepalive" "pre_request" will be invoked more often then on_connect.

on_disconnect

Invoked when a connection is closed. ARG0 is the same POEx::HTTP::Server::Connection object that was passed to "on_connect".

pre_request

Invoked after a request is read from the browser but before it is processed. ARG0 is a POEx::HTTP::Server::Request object. There is no ARG1.

POEx::HTTP::Server->spawn( 
                    handlers => { pre_request => 'poe:my-session/pre' }
                 );
sub pre {
    my( $object, $request ) = @_[OBJECT, ARG0];
    my $connection = $request->connection;
    # ...
}

post_request

Invoked after a response has been sent to the browser. ARG0 is a POEx::HTTP::Server::Request object. ARG1 is a POEx::HTTP::Server::Response object, with it's content cleared.

POEx::HTTP::Server->spawn( 
                    handlers => { pre_request => 'poe:my-session/post' }
                 );
sub post {
    my( $self, $request, $response ) = @_[OBJECT, ARG0, ARG1];
    my $connection = $request->connection;
    # ...
}

on_error

Invoked when the server detects an error. ARG0 is a POEx::HTTP::Server::Error object. There are 2 types of errors: network errors and HTTP errors. They are distiguished by calling the error object's op method. If op returns undef(), it is an HTTP error, otherwise a network error. HTTP error already has a message to the browser with HTML content. You may modify the HTTP error's content and headers before they get sent back to the browser.

POEx::HTTP::Server->spawn( 
                    handlers => { on_error => 'poe:my-session/error' }
                 );
sub error {
    my( $self, $err ) = @_[OBJECT, ARG0];
    if( $err->op ) {    # network error
        $self->LOG( $err->op." error [".$err->errnum, "] ".$err->errstr );
        # or the equivalent
        $self->LOG( $err->content );
    }
    else {              # HTTP error
        $self->LOG( $err->status_line );
        $self->content_type( 'text/plain' );
        $self->content( "Don't do that!" );
    }
}

EVENTS

shutdown

$poe_kernel->signal( $poe_kernel => 'shutdown' );
$poe_kernel->post( HTTPd => 'shutdown' );

Initiate server shutdown. Note however that any pending requests will stay active. The session will exit when the last of the requests has exited.

handlers_get

my $handlers = $poe_kernel->call( HTTPd => 'handlers_get' );

Fetch a hashref of handlers and their URIs. This list contains both the special handlers and the HTTP handlers.

handlers_set

$poe_kernel->call( HTTPD => handlers_set => $URI );
$poe_kernel->call( HTTPD => handlers_set => $ARRAYREF );
$poe_kernel->call( HTTPD => handlers_set => $HASHREF );

Change all the handlers at once. The sole parameter is the same as "handlers" passed to "spawn".

Note that modifying the set of handlers will only modify the handlers for new connections, not currently open connections.

handlers_add

$poe_kernel->call( HTTPD => handlers_add => $URI );
$poe_kernel->call( HTTPD => handlers_add => $ARRAYREF );
$poe_kernel->call( HTTPD => handlers_add => $HASHREF );

Add new handlers to the server, overriding any that might already exist. The ordering of handlers is preserved, with all new handlers added to the end of the list. The sole parameter is the same as "handlers" passed to "spawn".

Note that modifying the set of handlers will only modify the handlers for new connections, not currently open connections.

handlers_remove

$poe_kernel->call( HTTPD => handlers_remove => $RE );
$poe_kernel->call( HTTPD => handlers_remove => $ARRAYREF );
$poe_kernel->call( HTTPD => handlers_remove => $HASHREF );

Remove one or more handlers from the server. The handlers are removed based on the regex, not the handler's URI. The regex must be exactly identical to the regex supplied to "handlers".

The sole parameter may be :

$RE

$poe_kernel->call( HTTPD => handers_remove => '^/static' );

The handler associated with this regex is removed.

$ARRAYREF

$poe_kernel->call( HTTPD => handers_remove => 
                        [ '^/static', '^/static/bigger' ] );

Remove a list of handlers associated.

$HASHREF

$poe_kernel->call( HTTPD => handers_remove => 
                        { '^/static' => 1, '^/static/bigger' => 1 } );

The hash's keys are a list of regexes to remove. The values are ignored.

Note that modifying the set of handlers will not modify the handlers for currently open connections.

SEND HEADERS

If you wish to send the headers right away, but send the body later, you may do:

$resp->header( 'Content-Length' => $size );
$resp->send;

When you want to send the body:

$resp->send( $content );

When you are finished:

$resp->done;

STREAMING

Streaming is very similar to sending the headers and body seperately. See above. One difference is that you will want to be advised when a block has been sent. This needs TO BE WRITEN.

SEE ALSO

POE

AUTHOR

Philip Gwyn, <gwyn -at- cpan.org>

COPYRIGHT AND LICENSE

Copyright (C) 2010 by Philip Gwyn

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.8 or, at your option, any later version of Perl 5 you may have available.