NAME

POEx::HTTP::Server - HTTP server in pure POE

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 reimplementation of an HTTP server. It uses POEx::URI to simplify event specification. It allows limiting connection concurrency.

POEx::HTTP::Server differs from POE::Component::Server::HTTP by having a cleaner code base and still being 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 BindAddress 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 requests that may be serviced in parallel. Defaults to (-1) infinit concurrency.

headers

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

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

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.

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

TODO

handlers_set

TODO

handlers_add

TODO

handlers_remove

TODO

SEND HEADERS

TO BE WRITEN

STREAMING

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.