File Coverage

blib/lib/POE/Component/Server/HTTPServer.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package POE::Component::Server::HTTPServer;
2 5     5   435497 use strict;
  5         15  
  5         302  
3 5     5   28 use Carp;
  5         250  
  5         502  
4 5     5   2266 use POE qw( Component::Server::TCP Filter::HTTPD );
  0            
  0            
5             use HTTP::Status;
6             use HTTP::Response;
7             use POE::Component::Server::HTTPServer::Handler qw( H_CONT H_FINAL );
8             use base 'Exporter';
9             our @EXPORT = qw( new_handler );
10              
11             our $VERSION = '0.9.2';
12              
13             sub new_handler {
14             my $package_suffix = shift;
15             eval "use POE::Component::Server::HTTPServer::$package_suffix;";
16             if ($@) {
17             warn $@;
18             die "Failed to intialize handler";
19             }
20             return "POE::Component::Server::HTTPServer::$package_suffix"->new(@_);
21             }
22              
23             sub new {
24             my $class = shift;
25             my $self = bless {
26             _port => 8080,
27             _handlers => [],
28             _log_file => "httpserver.log",
29             _backstop_handler => new_handler('NotFoundHandler'),
30             _debug => sub {},
31             }, $class;
32             $self->_init(@_);
33             return $self;
34             }
35              
36             sub _init {
37             my $self = shift;
38             my %args = @_;
39             if ( defined( $args{_debug} ) ) {
40             $self->{_debug} = $args{_debug};
41             $self->{_debug}->("Debugging: on.\n");
42             }
43             if ( defined( $args{handlers} ) ) {
44             $self->handlers( $args{handlers} );
45             }
46             if ( defined( $args{port} ) ) {
47             $self->port( $args{port} );
48             }
49             if ( defined( $args{log_file} ) ) {
50             $self->log_file( $args{log_file} );
51             }
52             if ( defined( $args{backstop_handler} ) ) {
53             $self->backstop_handler( $args{backstop_handler} );
54             }
55             }
56              
57             sub log_file {
58             my $self = shift;
59             my $log_file = shift;
60             if ( defined($log_file) ) {
61             $self->{_debug}->("Log file: $log_file\n");
62             return $self->{_log_file} = $log_file;
63             } else {
64             return $self->{_log_file};
65             }
66             }
67              
68             sub port {
69             my $self = shift;
70             my $port = shift;
71             if ( defined($port) ) {
72             $self->{_debug}->("Port: $port\n");
73             return $self->{_port} = $port;
74             } else {
75             return $self->{_port};
76             }
77             }
78              
79             sub handlers {
80             my $self = shift;
81             my $handlers = shift;
82             if ( defined($handlers) ) {
83             $self->{_debug}->("Handlers: ", map(" $_\n", @$handlers), "\n");
84             return $self->{_handlers} = $handlers;
85             } else {
86             return $self->{_handlers};
87             }
88             }
89              
90             # usage: $s->add_handler( '/foo' => new_handler('NotFoundHandler') )
91             sub add_handler {
92             my $self = shift;
93             my( $path, $handler ) = @_;
94             push( @{$self->{_handlers}}, $path, $handler );
95             }
96              
97             sub create_server {
98             my $self = shift;
99             unless ( @{$self->handlers()} ) {
100             $self->{_debug}->("No handlers: setting NotFoundHandler for all\n");
101             $self->handlers( ['/' => new_handler('NotFoundHandler')] );
102             }
103             if ( defined($self->{_log_file}) ) {
104             $self->{_debug}->("Opening log: $self->{_log_file}\n");
105             open( $self->{_log_fh}, ">> $self->{_log_file}" ) ||
106             warn "Could not open log file '$self->{_log_file}' ($!)\n";
107             }
108             $self->{_debug}->("Creating server component\n");
109             return
110             POE::Component::Server::TCP->new( Port => $self->port,
111             ClientInput => $self->_get_dispatcher,
112             ClientFilter => 'POE::Filter::HTTPD',
113             );
114             }
115              
116             # dispatch( $context [, $fullpath] )
117             # can be used by handlers ($context->{dispatcher}->dispatch) for re-dispatch
118             sub dispatch {
119             my $self = shift;
120             my $context = shift;
121             my $fullpath = shift;
122             if ( defined($fullpath) ) {
123             $context->{fullpath} = $fullpath;
124             }
125              
126             if ($context->{_dispatch_count}++ > 10) {
127             warn "Detected deep dispatch for '$context->{fullpath}', aborting!\n";
128             return H_FINAL;
129             }
130              
131             eval {
132             $self->{_debug}->("Dispatching request\n");
133             # copy handler list for splicing
134             my @handlers = ( @{$self->handlers}, '.' => $self->backstop_handler );
135             while ( @handlers ) {
136             # shift two elts from handlers in to prefix, handler
137             my( $prefix, $handler ) = splice( @handlers, 0, 2 );
138             $self->{_debug}->("Checking path:$context->{fullpath} =~ prefix:$prefix ($handler)\n");
139             if ( $context->{fullpath} =~ /^$prefix/ ) {
140             $self->{_debug}->("Fullpath: $context->{fullpath}\n");
141             $self->{_debug}->("Prefix: $prefix\n");
142             ($context->{contextpath} = $context->{fullpath}) =~ s/^$prefix//;
143             my $retval;
144             if (UNIVERSAL::can($handler, 'handle')) {
145             $retval = $handler->handle($context);
146             } else {
147             # assume handler's a coderef (might wanna check that sometime)
148             $retval = $handler->($context);
149             }
150             if ( $retval == H_FINAL ) {
151             $self->{_debug}->("Handler returned H_FINAL, stopping\n");
152             last;
153             }
154             }
155             }
156             };
157             if ($@) { # internal server error
158             my $error = $@;
159             warn "Caught error: $@\n";
160             $context->{response}->code(500);
161             $context->{response}->content_type("text/plain");
162             $context->{response}->content("An error occured while processing request:\n$error\n");
163             }
164             return H_FINAL; # in case this is being called from a handler
165              
166             } # dispatch()
167              
168             sub backstop_handler {
169             my $self = shift;
170             $self->{_backstop_handler} = shift if @_;
171             return $self->{_backstop_handler};
172             }
173              
174             sub _get_dispatcher {
175             my $self = shift;
176             return sub {
177             my( $kernel, $heap, $request ) = @_[ KERNEL, HEAP, ARG0 ];
178              
179             if ( $request->isa('HTTP::Response') ) {
180             # if a processing error occurrs, POE::Filter::HTTPD generates
181             # a suitable response for you to send back
182             $heap->{client}->put( $request );
183             $kernel->yield('shutdown');
184             return;
185             }
186              
187             $self->{_debug}->("Handling request\n");
188              
189             my $context = { request => $request,
190             response => HTTP::Response->new( RC_OK ),
191             remote_ip => $heap->{remote_ip},
192             fullpath => $request->uri->path,
193             dispatcher => $self,
194             _dispatch_count => 0,
195             };
196              
197             $self->dispatch($context);
198              
199             $self->_request_log( $context );
200             $heap->{client}->put( $context->{response} );
201             undef($context);
202             $kernel->yield( 'shutdown' ); # signal that we're done sending to client
203             };
204              
205             } # _get_dispatcher
206              
207             # pretty lame, so far
208             sub _request_log {
209             my $self = shift;
210             my $context = shift;
211             my($req,$resp) = ($context->{request}, $context->{response});
212             my @log;
213             push(@log, $context->{remote_ip});
214             if ( defined($context->{username}) ) {
215             push(@log, $context->{username});
216             } else {
217             push(@log, '-');
218             }
219             push(@log, "[".scalar(localtime())."]"); # wrong format
220             push(@log, $req->method);
221             push(@log, '"'.$req->uri.'"');
222             push(@log, $resp->code);
223             push(@log, $resp->content_length);
224             my $fh = $self->{_log_fh};
225             print $fh join(" ", @log), "\n";
226             }
227              
228              
229             1;
230             __END__