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( generate_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 "prefork" servers.

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

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

POEx::HTTP::Server has one public class method.

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 poe: urls 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( handlers => [ '' => $uri ] );

alias

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

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

blocksize

    POEx::HTTP::Server->spawn( blocksize => 5*$MTU );

Sets the block size used when sending a file to the browser. See "sendfile" in POEx::HTTP::Server::Response. See the "Note about MTU".

Default value is 7500 octets, or 5 ethernet fames, assuming the standard ethernet MTU of 1500 octets. This is useful for Interanet servers, or talking to a reverse proxy on the same LAN.

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 to $PACKAGE/$VERSION where $PACKAGE is POEx::HTTP::Server or any sub-class you might have crated and $VERSION is the current version of POEx::HTTP::Server.

keepalive

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

Activates the HTTP/1.1 persistent connection feature if true. Deactivates keep-alive if false.

Default is 0 (off).

If $N is a number, then it is used as the maximum number of requests per connection.

If $N isn't a number, or is simply 1, then the default is 100.

Note that HTTP/1.0 Keep-Alive extension is currently not supported.

keepalivetimeout

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

Sets the number of seconds to wait for a request before closing a connection. This aplies to the time between completing a request and receiving a new one.

Defaults to 5 seconds.

options

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

Options passed POE::Session->create.

prefork

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

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

Defaults to 0, no preforking support.

In a normal preforking server, only one request will 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.

timeout

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

Set the number of seconds to wait for the next TCP event. This timeout is used in the following circumstances :

  • between accepting a connection and receiving the full request;

  • between sending a response and flushing the output buffer;

  • while waiting for a streamed response chunk to flush.

Defaults to 300 seconds. Setting this timeout to 0 will allow the client to hold a connection open indefinately.

HANDLERS

A handler is a POE event that processes a given HTTP request and generates the response. It is invoked with:

ARG0 : a POEx::HTTP::Server::Request object.
ARG1 : 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 should set the response code approriately. A default HTTP status of RC_OK (200) is used. The response is sent to the browser with either "respond" in POEx::HTTP::Server::Response or "send" in POEx::HTTP::Server::Response. When the handler is finished, it must call "done" in POEx::HTTP::Server::Response on the response object.

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

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

    # Static file
    $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 response 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
            my $chunk = $poe_kernel->call( $heap->{session}, $h, $req )
            $resp->send( $chunk );
            $poe_kernel->yield( next_handler => $resp );
        }
        else {
            $poe_kernel->yield( 'last_handler', $resp );
        }
    }

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

    # Event handlers in the other session:
    sub one {
        # ....
        return $chunk;
    }

    sub two {
        # ....
        return $chunk;
    }

    sub three {
        # ....
        return $chunk;
    }

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' }
    }

Handler exceptions

Request handler invocations are wrapped in eval{}. If the handler throws an exception with die this will be reported to the browser as a short message. Obviously this only applies to the initial request handler. If you yield to other POE event handlers, they will not report exceptions to the browser.

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 about the connection. This connection object will be 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];
        # ...
    }

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;
        # ...
    }

If you use "keepalive", "pre_request" will be invoked more often then on_connect.

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;
        # ...
    }

stream_request

Invoked when a chunk has been flushed to the OS, if you are streaming a response to the browser. Streaming is turned on with "streaming" in POEx::HTTP::Server::Response.

Please remember that while a chunk might be flushed, the OS's network layer might still decide to combine several chunks into a single packet. And this even though we setup a hot socket with TCP_NODELAY set to 1 and SO_SNDBUF to 576.

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 may be distiguished by calling the error object's op method. If op returns undef(), it is an HTTP error, otherwise a network error. HTTP errors already have 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.

Unlike HTTP errors, network errors are never sent 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

The following POE events may be used to control POEx::HTTP::Server.

shutdown

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

Initiate server shutdown. Any pending requests will stay active, however. The session will exit when the last of the requests has finished. No further requests will be accepted, even if keepalive is in use.

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->post( HTTPD => handlers_set => $URI );
    $poe_kernel->post( HTTPD => handlers_set => $ARRAYREF );
    $poe_kernel->post( 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->post( HTTPD => handlers_add => $URI );
    $poe_kernel->post( HTTPD => handlers_add => $ARRAYREF );
    $poe_kernel->post( 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->post( HTTPD => handlers_remove => $RE );
    $poe_kernel->post( HTTPD => handlers_remove => $ARRAYREF );
    $poe_kernel->post( 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->post( HTTPD => handers_remove => '^/static' );

The handler associated with this regex is removed.

$ARRAYREF

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

Remove a list of handlers associated.

$HASHREF

    $poe_kernel->post( 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.

NOTES

Sending 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;    

The above causes the headers to be sent, allong with any content you might have added to $resp.

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 the headers will be flushed and the socket will be set to hot with TCP_NODELAY and SO_SNBUF. Another difference is that keepalive is deactivated for the connection. Finally difference is that you will see "stream_request" when you are allowed to send the next block. Look for "post_request" to find out when the last block has been sent to the browser.

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

When you want to send a chunk:

    $resp->send( $chunk );

This can be repeated as long as you want.

When you are finished:

    $resp->done;

This will provoke a "post_request" when the last chunk is flushed.

blocksize and MTU

If you are using sendfile, but do not have Sys::Sendfile installed you really should set "blocksize" to a whole multiple of the interface's MTU. Doing so automatically is currently beyond the scope of this module. Please see "mtu" in Net::Interface. But that won't help for servers available over the the Internet; your local ethernet interface's MTU (1500) is probably greater then your internet connection's MTU (1400-1492 for DSL). What's more, the MTU could be as low as 576.

SEE ALSO

POE, POEx::HTTP::Server::Request, POEx::HTTP::Server::Response, POEx::HTTP::Server::Error, POEx::HTTP::Server::Connection,

AUTHOR

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

COPYRIGHT AND LICENSE

Copyright (C) 2010, 2011 by Philip Gwyn. All rights reserved.

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.