1POE::Component::Server:U:sSeirmpCloenHtTrTiPb(u3t)ed PerPlOED:o:cCuommepnotnaetnito:n:Server::SimpleHTTP(3)
2
3
4
6 POE::Component::Server::SimpleHTTP - Perl extension to serve HTTP
7 requests in POE.
8
10 use POE;
11 use POE::Component::Server::SimpleHTTP;
12
13 # Start the server!
14 POE::Component::Server::SimpleHTTP->new(
15 'ALIAS' => 'HTTPD',
16 'ADDRESS' => '192.168.1.1',
17 'PORT' => 11111,
18 'HOSTNAME' => 'MySite.com',
19 'HANDLERS' => [
20 {
21 'DIR' => '^/bar/.*',
22 'SESSION' => 'HTTP_GET',
23 'EVENT' => 'GOT_BAR',
24 },
25 {
26 'DIR' => '^/$',
27 'SESSION' => 'HTTP_GET',
28 'EVENT' => 'GOT_MAIN',
29 },
30 {
31 'DIR' => '^/foo/.*',
32 'SESSION' => 'HTTP_GET',
33 'EVENT' => 'GOT_NULL',
34 },
35 {
36 'DIR' => '.*',
37 'SESSION' => 'HTTP_GET',
38 'EVENT' => 'GOT_ERROR',
39 },
40 ],
41
42 'LOGHANDLER' => { 'SESSION' => 'HTTP_GET',
43 'EVENT' => 'GOT_LOG',
44 },
45
46 # In the testing phase...
47 'SSLKEYCERT' => [ 'public-key.pem', 'public-cert.pem' ],
48 ) or die 'Unable to create the HTTP Server';
49
50 # Create our own session to receive events from SimpleHTTP
51 POE::Session->create(
52 inline_states => {
53 '_start' => sub { $_[KERNEL]->alias_set( 'HTTP_GET' );
54 $_[KERNEL]->post( 'HTTPD', 'GETHANDLERS', $_[SESSION], 'GOT_HANDLERS' );
55 },
56
57 'GOT_BAR' => \&GOT_REQ,
58 'GOT_MAIN' => \&GOT_REQ,
59 'GOT_ERROR' => \&GOT_ERR,
60 'GOT_NULL' => \&GOT_NULL,
61 'GOT_HANDLERS' => \&GOT_HANDLERS,
62 'GOT_LOG' => \&GOT_LOG,
63 },
64 );
65
66 # Start POE!
67 POE::Kernel->run();
68
69 sub GOT_HANDLERS {
70 # ARG0 = HANDLERS array
71 my $handlers = $_[ ARG0 ];
72
73 # Move the first handler to the last one
74 push( @$handlers, shift( @$handlers ) );
75
76 # Send it off!
77 $_[KERNEL]->post( 'HTTPD', 'SETHANDLERS', $handlers );
78 }
79
80 sub GOT_NULL {
81 # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
82 my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
83
84 # Kill this!
85 $_[KERNEL]->post( 'HTTPD', 'CLOSE', $response );
86 }
87
88 sub GOT_REQ {
89 # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
90 my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
91
92 # Do our stuff to HTTP::Response
93 $response->code( 200 );
94 $response->content( 'Some funky HTML here' );
95
96 # We are done!
97 # For speed, you could use $_[KERNEL]->call( ... )
98 $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
99 }
100
101 sub GOT_ERR {
102 # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
103 my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
104
105 # Check for errors
106 if ( ! defined $request ) {
107 $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
108 return;
109 }
110
111 # Do our stuff to HTTP::Response
112 $response->code( 404 );
113 $response->content( "Hi visitor from " . $response->connection->remote_ip . ", Page not found -> '" . $request->uri->path . "'" );
114
115 # We are done!
116 # For speed, you could use $_[KERNEL]->call( ... )
117 $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
118 }
119
120 sub GOT_LOG {
121 # ARG0 = HTTP::Request object, ARG1 = remote IP
122 my ($request, $remote_ip) = @_[ARG0,ARG1];
123
124 # Do some sort of logging activity.
125 # If the request was malformed, $request = undef
126 # CHECK FOR A REQUEST OBJECT BEFORE USING IT.
127 if( $request ) {
128 {
129 warn join(' ', time(), $remote_ip, $request->uri ), "\n";
130 } else {
131 warn join(' ', time(), $remote_ip, 'Bad request' ), "\n";
132 }
133
134 return;
135 }
136
138 An easy to use HTTP daemon for POE-enabled programs
139
141 This module makes serving up HTTP requests a breeze in POE.
142
143 The hardest thing to understand in this module is the HANDLERS. That's
144 it!
145
146 The standard way to use this module is to do this:
147
148 use POE;
149 use POE::Component::Server::SimpleHTTP;
150
151 POE::Component::Server::SimpleHTTP->new( ... );
152
153 POE::Session->create( ... );
154
155 POE::Kernel->run();
156
157 Starting SimpleHTTP
158
159 To start SimpleHTTP, just call it's new method:
160
161 POE::Component::Server::SimpleHTTP->new(
162 'ALIAS' => 'HTTPD',
163 'ADDRESS' => '192.168.1.1',
164 'PORT' => 11111,
165 'HOSTNAME' => 'MySite.com',
166 'HEADERS' => {},
167 'HANDLERS' => [ ],
168 );
169
170 This method will die on error or return success.
171
172 This constructor accepts only 7 options.
173
174 "ALIAS"
175 This will set the alias SimpleHTTP uses in the POE Kernel. This
176 will default to "SimpleHTTP"
177
178 "ADDRESS"
179 This value will be passed to POE::Component::Server::TCP to bind
180 to.
181
182 "PORT"
183 This value will be passed to POE::Component::Server::TCP to bind
184 to.
185
186 "HOSTNAME"
187 This value is for the HTTP::Request's URI to point to. If this is
188 not supplied, SimpleHTTP will use Sys::Hostname to find it.
189
190 "HEADERS"
191 This should be a hashref, that will become the default headers on
192 all HTTP::Response objects. You can override this in individual
193 requests by setting it via $request->header( ... )
194
195 For more information, consult the HTTP::Headers module.
196
197 "HANDLERS"
198 This is the hardest part of SimpleHTTP :)
199
200 You supply an array, with each element being a hash. All the hashes
201 should contain those 3 keys:
202
203 DIR -> The regexp that will be used, more later.
204
205 SESSION -> The session to send the input
206
207 EVENT -> The event to trigger
208
209 The DIR key should be a valid regexp. This will be matched against
210 the current request path. Pseudocode is: if ( $path =~ /$DIR/ )
211
212 NOTE: The path is UNIX style, not MSWIN style ( /blah/foo not
213 \blah\foo )
214
215 Now, if you supply 100 handlers, how will SimpleHTTP know what to
216 do? Simple! By passing in an array in the first place, you have
217 already told SimpleHTTP the order of your handlers. They will be
218 tried in order, and if a match is not found, SimpleHTTP will return
219 a 404 response.
220
221 This allows some cool things like specifying 3 handlers with DIR
222 of: '^/foo/.*', '^/$', '.*'
223
224 Now, if the request is not in /foo or not root, your 3rd handler
225 will catch it, becoming the "404 not found" handler!
226
227 NOTE: You might get weird Session/Events, make sure your handlers
228 are in order, for example: '^/', '^/foo/.*' The 2nd handler will
229 NEVER get any requests, as the first one will match ( no $ in the
230 regex )
231
232 Now, here's what a handler receives:
233
234 ARG0 -> HTTP::Request object
235
236 ARG1 -> POE::Component::Server::SimpleHTTP::Response object
237
238 ARG2 -> The exact DIR that matched, so you can see what triggered
239 what
240
241 NOTE: If ARG0 is undef, that means POE::Filter::HTTPD encountered
242 an error parsing the client request, simply modify the
243 HTTP::Response object and send some sort of generic error. Simpleā
244 HTTP will set the path used in matching the DIR regexes to an empty
245 string, so if there is a "catch-all" DIR regex like '.*', it will
246 catch the errors, and only that one.
247
248 NOTE: The only way SimpleHTTP will leak memory ( hopefully heh ) is
249 if you discard the SimpleHTTP::Response object without sending it
250 back to SimpleHTTP via the DONE/CLOSE events, so never do that!
251
252 "LOGHANDLER"
253 Expects a hashref with the following key, values:
254
255 SESSION -> The session to send the input
256
257 EVENT -> The event to trigger
258
259 You will receive an event for each request to the server from
260 clients. Malformed client requests will not be passed into the
261 handler. Instead undef will be passed.
262
263 The event will have the following parameters:
264
265 ARG0 -> HTTP::Request object/undef if client request was malformed.
266
267 ARG1 -> the IP address of the client
268
269 "SSLKEYCERT"
270 This should be an arrayref of only 2 elements - the public key and
271 certificate location. Now, this is still in the experimental stage,
272 and testing is greatly welcome!
273
274 Again, this will automatically turn every incoming connection into
275 a SSL socket. Once enough testing has been done, this option will
276 be augmented with more SSL stuff!
277
278 Events
279
280 SimpleHTTP is so simple, there are only 8 events available.
281
282 "DONE"
283 This event accepts only one argument: the HTTP::Response object we sent to the handler.
284
285 Calling this event implies that this particular request is done, and will proceed to close the socket.
286
287 NOTE: This method automatically sets those 3 headers if they are not already set:
288 Date -> Current date stringified via HTTP::Date->time2str
289 Content-Type -> text/html
290 Content-Length -> length( $response->content )
291
292 To get greater throughput and response time, do not post() to the DONE event, call() it!
293 However, this will force your program to block while servicing web requests...
294
295 "CLOSE"
296 This event accepts only one argument: the HTTP::Response object we sent to the handler.
297
298 Calling this event will close the socket, not sending any output
299
300 "GETHANDLERS"
301 This event accepts 2 arguments: The session + event to send the response to
302
303 This event will send back the current HANDLERS array ( deep-cloned via Storable::dclone )
304
305 The resulting array can be played around to your tastes, then once you are done...
306
307 "SETHANDLERS"
308 This event accepts only one argument: pointer to HANDLERS array
309
310 BEWARE: if there is an error in the HANDLERS, SimpleHTTP will die!
311
312 "STARTLISTEN"
313 Starts the listening socket, if it was shut down
314
315 "STOPLISTEN"
316 Simply a wrapper for SHUTDOWN GRACEFUL, but will not shutdown SimpleHTTP if there is no more requests
317
318 "SHUTDOWN"
319 Without arguments, SimpleHTTP does this:
320 Close the listening socket
321 Kills all pending requests by closing their sockets
322 Removes it's alias
323
324 With an argument of 'GRACEFUL', SimpleHTTP does this:
325 Close the listening socket
326 Waits for all pending requests to come in via DONE/CLOSE, then removes it's alias
327
328 Streaming with SimpleHTTP
329
330 It's now possible to send data as a stream to clients (unbuffered and
331 integrated in the POE loop).
332
333 Just create your sessions as usually and add a streaming event, this
334 event will be triggered each time you set the $response to a streaming
335 state:
336
337 # sets the response as streamed within our session with the stream event
338 $response->stream(
339 session => 'HTTP_GET',
340 event => 'GOT_STREAM'
341 );
342
343 This will call the GOT_STREAM event of the HTTP_GET session with as
344 first arg (ARG0) bundled within a hash the wheel, request, response and
345 id.
346
347 You can now send data by chunks and either call yourself back (via POE)
348 or shutdown when your streaming is done (EOF for example).
349
350 sub GOT_STREAM {
351 my ( $kernel, $heap, $stream ) = @_[KERNEL, HEAP, ARG0];
352
353 # $stream contains the wheel, the request, the response
354 # and an id associated the the wheel
355 $stream->{'wheel'}->put("Hello World\n");
356
357 # lets go on streaming ... with some delay actually but
358 # that should be a post unless the client needs the data
359 # slowly ..
360 POE::Kernel->delay('GOT_STREAM', 1, $stream );
361 }
362
363 SimpleHTTP Notes
364
365 This module is very picky about capitalization!
366
367 All of the options are uppercase, to avoid confusion.
368
369 You can enable debugging mode by doing this:
370
371 sub POE::Component::Server::SimpleHTTP::DEBUG () { 1 }
372 use POE::Component::Server::SimpleHTTP;
373
374 Also, this module will try to keep the Listening socket alive. if it
375 dies, it will open it again for a max of 5 retries.
376
377 You can override this behavior by doing this:
378
379 sub POE::Component::Server::SimpleHTTP::MAX_RETRIES () { 10 }
380 use POE::Component::Server::SimpleHTTP;
381
382 For those who are pondering about basic-authentication, here's a tiny
383 snippet to put in the Event handler
384
385 # Contributed by Rocco Caputo
386 sub Got_Request {
387 # ARG0 = HTTP::Request, ARG1 = HTTP::Response
388 my( $request, $response ) = @_[ ARG0, ARG1 ];
389
390 # Get the login
391 my ( $login, $password ) = $request->authorization_basic();
392
393 # Decide what to do
394 if ( ! defined $login or ! defined $password ) {
395 # Set the authorization
396 $response->header( 'WWW-Authenticate' => 'Basic realm="MyRealm"' );
397 $response->code( 401 );
398 $response->content( 'FORBIDDEN.' );
399
400 # Send it off!
401 $_[KERNEL]->post( 'SimpleHTTP', 'DONE', $response );
402 } else {
403 # Authenticate the user and move on
404 }
405 }
406
407 EXPORT
408
409 Nothing.
410
412 L<POE>
413
414 L<POE::Filter::HTTPD>
415
416 L<HTTP::Request>
417
418 L<HTTP::Response>
419
420 L<POE::Component::Server::SimpleHTTP::Connection>
421
422 L<POE::Component::Server::SimpleHTTP::Response>
423
424 L<POE::Component::Server::SimpleHTTP::PreFork>
425
426 L<POE::Component::SSLify>
427
429 Apocalypse <apocal@cpan.org>
430
432 Copyright 2006 by Apocalypse
433
434 This library is free software; you can redistribute it and/or modify it
435 under the same terms as Perl itself.
436
437
438
439perl v5.8.8 2007-03-P2O1E::Component::Server::SimpleHTTP(3)