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 version 2.30
11
13 use POE;
14 use POE::Component::Server::SimpleHTTP;
15
16 # Start the server!
17 POE::Component::Server::SimpleHTTP->new(
18 'ALIAS' => 'HTTPD',
19 'PORT' => 11111,
20 'HOSTNAME' => 'MySite.com',
21 'HANDLERS' => [
22 {
23 'DIR' => '^/bar/.*',
24 'SESSION' => 'HTTP_GET',
25 'EVENT' => 'GOT_BAR',
26 },
27 {
28 'DIR' => '^/$',
29 'SESSION' => 'HTTP_GET',
30 'EVENT' => 'GOT_MAIN',
31 },
32 {
33 'DIR' => '^/foo/.*',
34 'SESSION' => 'HTTP_GET',
35 'EVENT' => 'GOT_NULL',
36 },
37 {
38 'DIR' => '.*',
39 'SESSION' => 'HTTP_GET',
40 'EVENT' => 'GOT_ERROR',
41 },
42 ],
43
44 'LOGHANDLER' => { 'SESSION' => 'HTTP_GET',
45 'EVENT' => 'GOT_LOG',
46 },
47
48 'LOG2HANDLER' => { 'SESSION' => 'HTTP_GET',
49 'EVENT' => 'POSTLOG',
50 },
51
52 # In the testing phase...
53 'SSLKEYCERT' => [ 'private-key.pem', 'public-cert.pem' ],
54 'SSLINTERMEDIATECACERT' => 'intermediate-ca-cert.pem',
55 ) or die 'Unable to create the HTTP Server';
56
57 # Create our own session to receive events from SimpleHTTP
58 POE::Session->create(
59 inline_states => {
60 '_start' => sub { $_[KERNEL]->alias_set( 'HTTP_GET' );
61 $_[KERNEL]->post( 'HTTPD', 'GETHANDLERS', $_[SESSION], 'GOT_HANDLERS' );
62 },
63
64 'GOT_BAR' => \&GOT_REQ,
65 'GOT_MAIN' => \&GOT_REQ,
66 'GOT_ERROR' => \&GOT_ERR,
67 'GOT_NULL' => \&GOT_NULL,
68 'GOT_HANDLERS' => \&GOT_HANDLERS,
69 'GOT_LOG' => \&GOT_LOG,
70 },
71 );
72
73 # Start POE!
74 POE::Kernel->run();
75
76 sub GOT_HANDLERS {
77 # ARG0 = HANDLERS array
78 my $handlers = $_[ ARG0 ];
79
80 # Move the first handler to the last one
81 push( @$handlers, shift( @$handlers ) );
82
83 # Send it off!
84 $_[KERNEL]->post( 'HTTPD', 'SETHANDLERS', $handlers );
85 }
86
87 sub GOT_NULL {
88 # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
89 my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
90
91 # Kill this!
92 $_[KERNEL]->post( 'HTTPD', 'CLOSE', $response );
93 }
94
95 sub GOT_REQ {
96 # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
97 my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
98
99 # Do our stuff to HTTP::Response
100 $response->code( 200 );
101 $response->content( 'Some funky HTML here' );
102
103 # We are done!
104 # For speed, you could use $_[KERNEL]->call( ... )
105 $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
106 }
107
108 sub GOT_ERR {
109 # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
110 my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
111
112 # Check for errors
113 if ( ! defined $request ) {
114 $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
115 return;
116 }
117
118 # Do our stuff to HTTP::Response
119 $response->code( 404 );
120 $response->content( "Hi visitor from " . $response->connection->remote_ip . ", Page not found -> '" . $request->uri->path . "'" );
121
122 # We are done!
123 # For speed, you could use $_[KERNEL]->call( ... )
124 $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
125 }
126
127 sub GOT_LOG {
128 # ARG0 = HTTP::Request object, ARG1 = remote IP
129 my ($request, $remote_ip) = @_[ARG0,ARG1];
130
131 # Do some sort of logging activity.
132 # If the request was malformed, $request = undef
133 # CHECK FOR A REQUEST OBJECT BEFORE USING IT.
134 if( $request ) {
135 {
136 warn join(' ', time(), $remote_ip, $request->uri ), "\n";
137 } else {
138 warn join(' ', time(), $remote_ip, 'Bad request' ), "\n";
139 }
140
141 return;
142 }
143
145 This module makes serving up HTTP requests a breeze in POE.
146
147 The hardest thing to understand in this module is the HANDLERS. That's
148 it!
149
150 The standard way to use this module is to do this:
151
152 use POE;
153 use POE::Component::Server::SimpleHTTP;
154
155 POE::Component::Server::SimpleHTTP->new( ... );
156
157 POE::Session->create( ... );
158
159 POE::Kernel->run();
160
161 Starting SimpleHTTP
162 To start SimpleHTTP, just call it's new method:
163
164 POE::Component::Server::SimpleHTTP->new(
165 'ALIAS' => 'HTTPD',
166 'ADDRESS' => '192.168.1.1',
167 'PORT' => 11111,
168 'HOSTNAME' => 'MySite.com',
169 'HEADERS' => {},
170 'HANDLERS' => [ ],
171 );
172
173 This method will die on error or return success.
174
175 This constructor accepts only 7 options.
176
177 "ALIAS"
178 This will set the alias SimpleHTTP uses in the POE Kernel. This
179 will default to "SimpleHTTP"
180
181 "ADDRESS"
182 This value will be passed to POE::Wheel::SocketFactory to bind to,
183 will use INADDR_ANY if it is nothing is provided (or IN6ADDR_ANY if
184 DOMAIN is AF_INET6). For UNIX domain sockets, it should be a path
185 describing the socket's filename.
186
187 If neither DOMAIN nor ADDRESS are specified, it will use
188 IN6ADDR_ANY and AF_INET6.
189
190 "PORT"
191 This value will be passed to POE::Wheel::SocketFactory to bind to.
192
193 "DOMAIN"
194 This value will be passed to POE::Wheel::SocketFactory to define
195 the socket domain used (AF_INET, AF_INET6, AF_UNIX).
196
197 "HOSTNAME"
198 This value is for the HTTP::Request's URI to point to. If this is
199 not supplied, SimpleHTTP will use Sys::Hostname to find it.
200
201 "HEADERS"
202 This should be a hashref, that will become the default headers on
203 all HTTP::Response objects. You can override this in individual
204 requests by setting it via $request->header( ... )
205
206 For more information, consult the HTTP::Headers module.
207
208 "HANDLERS"
209 This is the hardest part of SimpleHTTP :)
210
211 You supply an array, with each element being a hash. All the hashes
212 should contain those 3 keys:
213
214 DIR -> The regexp that will be used, more later.
215
216 SESSION -> The session to send the input
217
218 EVENT -> The event to trigger
219
220 The DIR key should be a valid regexp. This will be matched against
221 the current request path. Pseudocode is: if ( $path =~ /$DIR/ )
222
223 NOTE: The path is UNIX style, not MSWIN style ( /blah/foo not
224 \blah\foo )
225
226 Now, if you supply 100 handlers, how will SimpleHTTP know what to
227 do? Simple! By passing in an array in the first place, you have
228 already told SimpleHTTP the order of your handlers. They will be
229 tried in order, and if a match is not found, SimpleHTTP will return
230 a 404 response.
231
232 This allows some cool things like specifying 3 handlers with DIR
233 of: '^/foo/.*', '^/$', '.*'
234
235 Now, if the request is not in /foo or not root, your 3rd handler
236 will catch it, becoming the "404 not found" handler!
237
238 NOTE: You might get weird Session/Events, make sure your handlers
239 are in order, for example: '^/', '^/foo/.*' The 2nd handler will
240 NEVER get any requests, as the first one will match ( no $ in the
241 regex )
242
243 Now, here's what a handler receives:
244
245 ARG0 -> HTTP::Request object
246
247 ARG1 -> POE::Component::Server::SimpleHTTP::Response object
248
249 ARG2 -> The exact DIR that matched, so you can see what triggered
250 what
251
252 NOTE: If ARG0 is undef, that means POE::Filter::HTTPD encountered
253 an error parsing the client request, simply modify the
254 HTTP::Response object and send some sort of generic error.
255 SimpleHTTP will set the path used in matching the DIR regexes to an
256 empty string, so if there is a "catch-all" DIR regex like '.*', it
257 will catch the errors, and only that one.
258
259 NOTE: The only way SimpleHTTP will leak memory ( hopefully heh ) is
260 if you discard the SimpleHTTP::Response object without sending it
261 back to SimpleHTTP via the DONE/CLOSE events, so never do that!
262
263 "KEEPALIVE"
264 Set to true to enable HTTP keep-alive support. Connections will be
265 kept alive until the client closes the connection. All HTTP/1.1
266 connections are kept-open, unless you set the response "Connection"
267 header to "close".
268
269 $response->header( Connection => 'close' );
270
271 If you want more control, use
272 POE::Component::Server::HTTP::KeepAlive.
273
274 "LOGHANDLER"
275 Expects a hashref with the following key, values:
276
277 SESSION -> The session to send the input
278
279 EVENT -> The event to trigger
280
281 You will receive an event for each request to the server from
282 clients. Malformed client requests will not be passed into the
283 handler. Instead undef will be passed. Event is called before ANY
284 content handler is called.
285
286 The event will have the following parameters:
287
288 ARG0 -> HTTP::Request object/undef if client request was malformed.
289
290 ARG1 -> the IP address of the client
291
292 "LOG2HANDLER"
293 Expect a hashref with the following key, values:
294
295 SESSION -> The session to send the input
296
297 EVENT -> The event to trigger
298
299 You will receive an event for each response that hit DONE call.
300 Malformed client requests will not be passed into the handler.
301 Event is after processing all content handlers.
302
303 The event will have the following parameters:
304
305 ARG0 -> HTTP::Request object
306
307 ARG1 -> HTTP::Response object
308
309 That makes possible following code:
310
311 my ($login, $password) = $request->authorization_basic();
312 printf STDERR "%s - %s [%s] \"%s %s %s\" %d %d\n",
313 $response->connection->remote_ip, $login||'-', POSIX::strftime("%d/%b/%Y:%T %z",localtime(time())),
314 $request->method(), $request->uri()->path(), $request->protocol(),
315 $response->code(), length($response->content());
316
317 Emulate apache-like logs for PoCo::Server::SimpleHTTP
318
319 "SETUPHANDLER"
320 Expects a hashref with the following key, values:
321
322 SESSION -> The session to send the input
323
324 EVENT -> The event to trigger
325
326 You will receive an event when the listener wheel has been setup.
327
328 Currently there are no parameters returned.
329
330 "SSLKEYCERT"
331 This should be an arrayref of only 2 elements - the private key and
332 public certificate locations. Now, this is still in the
333 experimental stage, and testing is greatly welcome!
334
335 Again, this will automatically turn every incoming connection into
336 a SSL socket. Once enough testing has been done, this option will
337 be augmented with more SSL stuff!
338
339 "SSLINTERMEDIATECACERT"
340 This option is needed in case the SSL certificate references an
341 intermediate certification authority certificate.
342
343 "PROXYMODE"
344 Set this to a true value to enable the server to act as a proxy
345 server, ie. it won't mangle the HTTP::Request URI.
346
347 Events
348 SimpleHTTP is so simple, there are only 8 events available.
349
350 "DONE"
351 This event accepts only one argument: the HTTP::Response object we sent to the handler.
352
353 Calling this event implies that this particular request is done, and will proceed to close the socket.
354
355 NOTE: This method automatically sets those 3 headers if they are not already set:
356 Date -> Current date stringified via HTTP::Date->time2str
357 Content-Type -> text/html
358 Content-Length -> length( $response->content )
359
360 To get greater throughput and response time, do not post() to the DONE event, call() it!
361 However, this will force your program to block while servicing web requests...
362
363 "CLOSE"
364 This event accepts only one argument: the HTTP::Response object we sent to the handler.
365
366 Calling this event will close the socket, not sending any output
367
368 "GETHANDLERS"
369 This event accepts 2 arguments: The session + event to send the response to
370
371 This event will send back the current HANDLERS array ( deep-cloned via Storable::dclone )
372
373 The resulting array can be played around to your tastes, then once you are done...
374
375 "SETHANDLERS"
376 This event accepts only one argument: pointer to HANDLERS array
377
378 BEWARE: if there is an error in the HANDLERS, SimpleHTTP will die!
379
380 "SETCLOSEHANDLER"
381 $_[KERNEL]->call( $_[SENDER], 'SETCLOSEHANDLER', $connection,
382 $event, @args );
383
384 Calls $event in the current session when $connection is closed.
385 You could use for persistent connection handling.
386
387 Multiple session may register close handlers.
388
389 Calling SETCLOSEHANDLER without $event to remove the current
390 session's handler:
391
392 $_[KERNEL]->call( $_[SENDER], 'SETCLOSEHANDLER', $connection );
393
394 You must make sure that @args doesn't cause a circular reference.
395 Ideally, use "$connection-"ID> or some other unique value
396 associated with this $connection.
397
398 "STARTLISTEN"
399 Starts the listening socket, if it was shut down
400
401 "STOPLISTEN"
402 Simply a wrapper for SHUTDOWN GRACEFUL, but will not shutdown SimpleHTTP if there is no more requests
403
404 "SHUTDOWN"
405 Without arguments, SimpleHTTP does this:
406 Close the listening socket
407 Kills all pending requests by closing their sockets
408 Removes it's alias
409
410 With an argument of 'GRACEFUL', SimpleHTTP does this:
411 Close the listening socket
412 Waits for all pending requests to come in via DONE/CLOSE, then removes it's alias
413
414 "STREAM"
415 With a $response argument it streams the content and calls back the streaming event
416 of the user's session (or with the dont_flush option you're responsible for calling
417 back your session's streaming event).
418
419 To use the streaming feature see below.
420
421 Streaming with SimpleHTTP
422 It's possible to send data as a stream to clients (unbuffered and
423 integrated in the POE loop).
424
425 Just create your session to receive events from SimpleHTTP as usually
426 and add a streaming event, this event will be triggered over and over
427 each time you set the $response to a streaming state and once you
428 trigger it:
429
430 # sets the response as streamed within our session which alias is HTTP_GET
431 # with the event GOT_STREAM
432 $response->stream(
433 session => 'HTTP_GET',
434 event => 'GOT_STREAM',
435 dont_flush => 1
436 );
437
438 # then you can simply yield your streaming event, once the GOT_STREAM event
439 # has reached its end it will be triggered again and again, until you
440 # send a CLOSE event to the kernel with the appropriate response as parameter
441 $kernel->yield('GOT_STREAM', $response);
442
443 The optional dont_flush option gives the user the ability to control
444 the callback to the streaming event, which means once your stream event
445 has reached its end it won't be called, you have to call it back.
446
447 You can now send data by chunks and either call yourself back (via POE)
448 or shutdown when your streaming is done (EOF for example).
449
450 sub GOT_STREAM {
451 my ( $kernel, $heap, $response ) = @_[KERNEL, HEAP, ARG0];
452
453 # sets the content of the response
454 $response->content("Hello World\n");
455
456 # send it to the client
457 POE::Kernel->post('HTTPD', 'STREAM', $response);
458
459 # if we have previously set the dont_flush option
460 # we have to trigger our event back until the end of
461 # the stream like this (that can be a yield, of course):
462 #
463 # $kernel->delay('GOT_STREAM', 1, $stream );
464
465 # otherwise the GOT_STREAM event is triggered continuously until
466 # we call the CLOSE event on the response like that :
467 #
468 if ($heap{'streaming_is_done'}) {
469 # close the socket and end the stream
470 POE::Kernel->post('HTTPD', 'CLOSE', $response );
471 }
472 }
473
474 The dont_flush option is there to be able to control the frequency of
475 flushes to the client.
476
477 SimpleHTTP Notes
478 You can enable debugging mode by doing this:
479
480 sub POE::Component::Server::SimpleHTTP::DEBUG () { 1 }
481 use POE::Component::Server::SimpleHTTP;
482
483 Also, this module will try to keep the Listening socket alive. if it
484 dies, it will open it again for a max of 5 retries.
485
486 You can override this behavior by doing this:
487
488 sub POE::Component::Server::SimpleHTTP::MAX_RETRIES () { 10 }
489 use POE::Component::Server::SimpleHTTP;
490
491 For those who are pondering about basic-authentication, here's a tiny
492 snippet to put in the Event handler
493
494 # Contributed by Rocco Caputo
495 sub Got_Request {
496 # ARG0 = HTTP::Request, ARG1 = HTTP::Response
497 my( $request, $response ) = @_[ ARG0, ARG1 ];
498
499 # Get the login
500 my ( $login, $password ) = $request->authorization_basic();
501
502 # Decide what to do
503 if ( ! defined $login or ! defined $password ) {
504 # Set the authorization
505 $response->header( 'WWW-Authenticate' => 'Basic realm="MyRealm"' );
506 $response->code( 401 );
507 $response->content( 'FORBIDDEN.' );
508
509 # Send it off!
510 $_[KERNEL]->post( 'SimpleHTTP', 'DONE', $response );
511 } else {
512 # Authenticate the user and move on
513 }
514 }
515
516 EXPORT
517 Nothing.
518
520 An easy to use HTTP daemon for POE-enabled programs
521
523 L<POE>
524
525 L<POE::Filter::HTTPD>
526
527 L<HTTP::Request>
528
529 L<HTTP::Response>
530
531 L<POE::Component::Server::SimpleHTTP::Connection>
532
533 L<POE::Component::Server::SimpleHTTP::Response>
534
535 L<POE::Component::Server::SimpleHTTP::PreFork>
536
537 L<POE::Component::SSLify>
538
540 Apocalypse <APOCAL@cpan.org>
541
543 This software is copyright (c) 2023 by Apocalypse, Chris Williams,
544 Eriam Schaffter, Marlon Bailey and Philip Gwyn.
545
546 This is free software; you can redistribute it and/or modify it under
547 the same terms as the Perl 5 programming language system itself.
548
549
550
551perl v5.38.0 2023-07-P2O1E::Component::Server::SimpleHTTP(3)