File Coverage

blib/lib/POE/Component/XUL.pm
Criterion Covered Total %
statement 187 474 39.4
branch 25 134 18.6
condition 13 104 12.5
subroutine 39 72 54.1
pod 4 37 10.8
total 268 821 32.6


line stmt bran cond sub pod time code
1             package # Hide from CPAN indexer
2             POE::Component::XUL;
3             # $Id: XUL.pm 1566 2010-11-03 03:13:32Z fil $
4             # Copyright Philip Gwyn 2007-2010. All rights reserved.
5              
6 14     14   234317 use strict;
  14         16  
  14         328  
7 14     14   48 use warnings;
  14         13  
  14         334  
8              
9 14     14   56 use File::Path;
  14         12  
  14         692  
10 14     14   45 use File::Spec;
  14         16  
  14         208  
11 14     14   41 use File::Basename;
  14         15  
  14         844  
12 14     14   6209 use HTTP::Date;
  14         39110  
  14         712  
13 14     14   5610 use HTTP::Status;
  14         40676  
  14         2863  
14 14     14   6187 use HTML::Entities qw( encode_entities_numeric );
  14         57520  
  14         844  
15 14     14   7269 use I18N::AcceptLanguage;
  14         11423  
  14         366  
16 14     14   4920 use IO::File;
  14         73280  
  14         1380  
17 14     14   5942 use MIME::Types;
  14         70109  
  14         554  
18 14     14   5271 use POE;
  14         276120  
  14         78  
19 14     14   456094 use POE::Component::Server::HTTP;
  14         604170  
  14         750  
20 14     14   100 use POE::Session;
  14         20  
  14         70  
21 14     14   6925 use POE::XUL::Controler;
  14         74  
  14         403  
22 14     14   5974 use POE::XUL::Node;
  14         23  
  14         46  
23 14     14   5000 use POE::XUL::Request;
  14         27  
  14         363  
24 14     14   68 use POE::XUL::Logging;
  14         10  
  14         771  
25 14     14   59 use POSIX qw(:errno_h);
  14         19  
  14         98  
26 14     14   5243 use Scalar::Util qw( reftype blessed );
  14         13  
  14         574  
27 14     14   49 use Socket qw( unpack_sockaddr_in );
  14         16  
  14         470  
28              
29 14     14   48 use Carp;
  14         12  
  14         686  
30              
31             our $VERSION = '0.0601';
32              
33 14     14   48 use constant DEBUG => 0;
  14         13  
  14         637  
34              
35 14     14   49 use vars qw( $HAVE_DEVEL_SIZE $HAVE_DATA_DUMPER $SINGLETON );
  14         13  
  14         977  
36             BEGIN {
37 14     14   22 $HAVE_DEVEL_SIZE = 0;
38 14     14   671 eval "use " . # Hide from CPANTS kwalitee
  14         5953  
  14         5634  
  14         432  
39             "Devel::Size;";
40 14 50       56 $HAVE_DEVEL_SIZE = 1 unless $@;
41              
42 14         18 $HAVE_DATA_DUMPER = 0;
43 14     14   590 eval "use Data::Dumper;";
  14         83  
  14         15  
  14         451  
44 14 50       51839 $HAVE_DATA_DUMPER = 1 unless $@;
45             }
46              
47             ###############################################################
48             sub spawn
49             {
50 2     2 1 1374 my ($package, $args) = @_;
51              
52 2         2 my $self = $package;
53 2 50       14 unless( blessed $self ) {
54 2         10 $self = $package->new( $args );
55             }
56              
57             POE::Session->create(
58 2 50       4 options => { %{ $self->{opts}||{} } },
  2         20  
59             object_states => [
60             $self => [ qw( _start shutdown
61             static xul httpd_error xul_file
62             poe_size poe_kernel poe_test
63             session_count session_timeout session_exists
64             sig_HUP sig_DIE
65             ) ],
66             ],
67             );
68             }
69              
70             ###############################################################
71             sub new
72             {
73 6     6 0 1613 my( $package, $args ) = @_;
74              
75 6         12 $args->{port} = $args->{port};
76 6 50       23 $args->{port} = 8077 unless defined $args->{port}; # PORT
77 6   50     16 $args->{root} = $args->{root} || '/usr/local/poe-xul/xul'; # ROOT
78 6   100     27 $args->{alias} ||= 'component-poe-xul';
79 6 50       20 $args->{apps} = {} if (!defined $args->{apps});
80 6 50       18 $args->{opts} = {} if (!defined $args->{opts});
81 6   50     27 $args->{timeout} ||= 60*30; # 30 minutes
82              
83 6 50       20 unless (ref($args->{apps}) eq 'HASH') {
84 0         0 croak "apps parameter must be a HASH ref";
85             }
86 6 50       18 unless (ref($args->{opts}) eq 'HASH') {
87 0         0 croak "opts parameter must be a HASH ref";
88             }
89              
90 6         31 my $self = bless { %$args }, $package;
91 6         46 $self->build_controler( $self->{timeout}, $self->{apps} );
92              
93 6         19 $self->__parse_apps();
94 6         10 $self->{sessions} = {};
95              
96 6   33     107 $self->{static_root} ||= File::Spec->catfile( $self->{root}, 'xul' );
97 6   33     50 $self->{log_root} ||= File::Spec->catfile( $self->{root}, 'log' );
98              
99 6         29 $self->build_logging( $args->{logging} );
100              
101 6         14 $self->{languages} = [ qw( en fr ) ]; # XXX
102 6         10 $self->{default_language} = 'fr'; # XXX
103              
104 6         14 return $SINGLETON = $self;
105             }
106              
107             sub __parse_apps
108             {
109 6     6   9 my( $self ) = @_;
110              
111 6         10 my $controler = $self->{controler};
112 6   50     24 $self->{app_names} ||= {};
113              
114 6         9 foreach my $app ( keys %{ $self->{apps} } ) {
  6         20  
115 0         0 my $A = $self->{apps}{$app};
116 0         0 my $r = ref $A;
117             # Make sure we have a package or a coderef
118 0         0 my $ok = 0;
119 0 0 0     0 if( $r and 'HASH' eq $r ) {
120             $self->{app_names}{$app} = {
121             en => $A->{en},
122             fr => $A->{fr},
123 0         0 };
124 0 0       0 if( $A->{package} ) {
125 0         0 $A = $A->{package};
126 0         0 undef $r;
127             }
128             else {
129 0         0 $A = $A->{code};
130 0         0 $r = 'CODE';
131             }
132             }
133 0 0 0     0 if( not $r and $controler->package_ctor( $A ) ) {
    0          
134 0         0 $ok = 1;
135             }
136             elsif( $r eq 'CODE') {
137 0         0 $ok = 1;
138             }
139 0 0       0 unless( $ok ) {
140 0         0 croak "apps parameter $app must be a code reference or name of a package that defines ->spawn, not $r ($A)";
141             }
142 0         0 $self->{apps}{$app} = $A;
143             }
144             }
145              
146              
147             ###############################################################
148             sub build_controler
149             {
150 6     6 0 6 my( $self, $timeout, $apps ) = @_;
151              
152 6         41 $self->{controler} = POE::XUL::Controler->new( $timeout, $apps );
153             }
154              
155             ###############################################################
156             sub build_http_server
157             {
158 0     0 0 0 my( $self, $addr, $port ) = @_;
159 0         0 $self->{mimetypes} = MIME::Types->new();
160              
161 0         0 my $alias = $self->{alias};
162              
163             $self->{aliases} = POE::Component::Server::HTTP->new(
164             Port => $self->{port},
165 0         0 MapOrder => 'bottom-first',
166             # PreHandler => { '/' => _mk_handler( $self, 'pre_connection' ) },
167             PostHandler => {
168             '/' => _mk_handler( $self, 'post_connection' )
169             },
170             ContentHandler => {
171             '/xul' => _mk_call( $alias, 'xul' ),
172             '/xul/file/' => _mk_call( $alias, 'xul_file' ),
173             '/__poe_size' => _mk_call( $alias, 'poe_size' ),
174             '/__poe_kernel' => _mk_call( $alias, 'poe_kernel' ),
175             '/__poe_text ' => _mk_call( $alias, 'poe_text' ),
176             '/' => _mk_call( $alias, 'static' ),
177             },
178             ErrorHandler => {
179             '/' => _mk_call( $alias, 'httpd_error' ),
180             },
181              
182             Headers => { 'X-POE-XUL' => $VERSION },
183             );
184             }
185              
186             ## We build these closures outside of build_http_server, because otherwise
187             ## they would capture a reference to $self
188             sub _mk_handler
189             {
190 0     0   0 my( $self, $call ) = @_;
191 0 0   0   0 return [ sub { RC_OK } ] unless $self;
  0         0  
192 0     0   0 return [ sub { $self->$call(@_) } ]
  0         0  
193             }
194              
195             sub _mk_call
196             {
197 0     0   0 my( $alias, $handler ) = @_;
198 0     0   0 return sub { return $poe_kernel->call( $alias, $handler, @_ ) };
  0         0  
199             }
200              
201              
202             ###############################################################
203             # Introspection used for load balancer
204             sub port
205             {
206 0     0 1 0 my( $self ) = @_;
207            
208 0         0 my $sid = $self->{aliases}{tcp};
209 0         0 my $tcp = $poe_kernel->alias_resolve( $sid );
210 0 0       0 die "$$: Server::TCP has disapeared! tcp=$sid" unless $tcp;
211 0         0 my $wheel = $tcp->get_heap->{listener};
212 0 0       0 die "Server::TCP no longer has the listener wheel in 'listener'"
213             unless $wheel;
214 0         0 my $sockname = $wheel->getsockname;
215 0         0 my($peer_port, $peer_addr) = unpack_sockaddr_in( $sockname );
216 0         0 return $peer_port;
217             # use Data::Denter;
218             # die Denter $sockname;
219             }
220              
221             sub alias
222             {
223 0     0 1 0 my( $self ) = @_;
224 0         0 return $self->{alias};
225             }
226              
227             ############################################################################
228             # POE methods
229              
230             ###############################################################
231             sub _start
232             {
233 2     2   796 my ($self, $kernel, $session) = @_[OBJECT, KERNEL, SESSION];
234              
235 2         8 $kernel->alias_set( $self->{alias} );
236 2         51 $kernel->sig( shutdown => 'shutdown' );
237 2         39 $kernel->sig( HUP => 'sig_HUP' );
238 2         53 $kernel->sig( DIE => 'sig_DIE' );
239              
240             # TODO: listen host
241 2         29 $self->build_http_server( '0.0.0.0', $self->{port} );
242 2         24808 $self->log_setup;
243             }
244              
245             # NB : no longer used
246             sub _stop
247             {
248 0     0   0 xwarn "XUL stop";
249             }
250              
251             ###############################################################
252             # Sane shutdown
253             sub shutdown
254             {
255 2     2 1 5267 my( $self ) = @_;
256             # xwarn "$$ XUL shutdown";
257 2         8 $self->{shutdown} = 1;
258 2         8 $poe_kernel->post( $self->{aliases}{httpd}, 'shutdown' );
259 2         50 $poe_kernel->alias_remove( delete $self->{alias} );
260 2         46 $poe_kernel->sig( 'HUP' );
261             }
262              
263             ###############################################################
264             # POE Exception handling
265             sub sig_DIE
266             {
267 0     0 0 0 my( $self, $kernel, $sig, $ex ) = @_[ OBJECT, KERNEL, ARG0, ARG1 ];
268 0         0 xwarn "============================\nERROR: $sig $ex\n";
269 0         0 xwarn "Exception in $ex->{event}: $ex->{error_str}\n";
270             }
271              
272              
273              
274              
275             ###############################################################
276             sub session_timeout
277             {
278 0     0 0 0 my ($self, $kernel, $SID) = @_[OBJECT, KERNEL, ARG0];
279 0         0 my $controler = $self->{controler};
280 0         0 DEBUG and
281             xwarn "Session timeout for $SID";
282 0 0 0     0 if( defined $SID and $controler->exists( $SID ) ) {
283 0         0 DEBUG and
284             xdebug "Timeout SID=$SID";
285 0         0 $kernel->call( $SID, 'timeout', $SID ); # TODO unit test
286             # unregister will send the 'shutdown' event
287 0         0 $controler->unregister( $SID );
288             }
289             }
290              
291             ###############################################################
292             # Get the number of active sessions.
293             # Used by IGDAIP::App to see when a backend should exit
294             sub session_count
295             {
296 0     0 0 0 my ($self, $kernel) = @_[ OBJECT, KERNEL ];
297 0         0 return $self->{controler}->count;
298             }
299              
300             ###############################################################
301             # Verify if a session exists
302             sub session_exists
303             {
304 0     0 0 0 my ($self, $kernel, $SID ) = @_[ OBJECT, KERNEL, ARG0 ];
305 0         0 return $self->{controler}->exists( $SID );
306             }
307              
308              
309              
310              
311              
312             ############################################################################
313             # XUL request handling
314              
315             ###############################################################
316             # Get the arguments out of a request
317             sub parse_args
318             {
319 0     0 0 0 my( $self, $req ) = @_;
320              
321 0         0 return POE::XUL::Request->new( $req );
322             }
323              
324             ###############################################################
325             # Report an error in the request parsing
326             sub parse_error
327             {
328 0     0 0 0 my( $self, $rc ) = @_;
329              
330 0         0 $self->error_standard( $rc, "argument parsing" );
331             }
332              
333             ###############################################################
334             # A request under /xul for an application
335             sub xul
336             {
337 0     0 0 0 my( $self, $kernel, $req, $resp ) = @_[ OBJECT, KERNEL, ARG0..$#_ ];
338              
339 0         0 DEBUG and
340             warn "$$: xul";
341 0 0       0 if( $self->{shutdown} ) {
342 0         0 xwarn "XUL request, but we are shutdown\n";
343 0         0 return;
344             }
345              
346 0         0 local $self->{request} = $req;
347 0         0 local $self->{response} = $resp;
348              
349 0         0 DEBUG and xwarn "XUL request";
350              
351 0         0 my $controler = $self->{controler};
352              
353 0         0 my $uri = $req->uri->path;
354 0 0       0 if( $uri ne '/xul' ) {
355 0         0 return $self->error_standard( RC_BAD_REQUEST, "parsing uri",
356             "$uri isn't a valid path\n" );
357             }
358              
359 0         0 my $ret = $self->parse_args( $req );
360 0 0       0 unless( ref $ret ) {
361 0         0 return $self->parse_error( $ret );
362             }
363              
364 0         0 $req->{start} = time;
365              
366 0   0     0 my $SID = $req->param( 'SID' ) || '';
367 0   0     0 my $event = $req->param( 'event' ) || 'boot';
368 0   0     0 my $app = $req->param( 'app' ) || '';
369 0         0 DEBUG and xdebug "Request for app=$app SID=$SID event=$event";
370              
371 0 0 0     0 unless( $app and $event ) {
372 0         0 $req->pre_log;
373 0         0 xlog "app=$app SID=$SID event=$event is an empty request";
374 0         0 return $self->error( RC_BAD_REQUEST, 'Empty request' );
375             }
376              
377 0         0 my $rc;
378 0         0 eval {
379 0         0 local $self->{logging}->{app} = $app;
380 0         0 $req->pre_log;
381 0 0       0 if( $event eq 'boot' ) {
    0          
382 0         0 my $fail = $controler->boot( $req, $resp );
383 0 0       0 if( $fail ) {
384             # boot failed
385 0         0 $rc = $self->error_boot_fail( $fail );
386             }
387             }
388             ## TODO: move the rest of this into Controler->something
389             elsif( ! $controler->exists( $SID ) ) {
390 0         0 $rc = $self->error_unknown_session( $SID );
391             }
392             else {
393 0         0 $controler->keepalive( $SID );
394 0 0       0 if( $event eq 'connect' ) {
    0          
    0          
395 0         0 $controler->connect( $SID, $req, $resp );
396             }
397             elsif( $event eq 'disconnect' ) {
398 0         0 $controler->disconnect( $SID, $req, $resp );
399             }
400             elsif( $event eq 'close' ) {
401 0         0 $controler->close( $SID, $req, $resp );
402             }
403             else {
404             # everything else is a DOM event
405 0         0 $controler->request( $SID, $event, $req, $resp );
406             }
407             }
408 0   0     0 $rc ||= RC_WAIT;
409             };
410              
411 0 0       0 unless( defined $rc ) {
412 0         0 warn "Error: $@";
413 0         0 $rc = $self->error_standard( RC_INTERNAL_SERVER_ERROR, $event, $@ );
414             }
415              
416 0         0 return $rc;
417             }
418              
419             ###############################################################
420             ## Request for a file that starts with /xul/
421             sub xul_file
422             {
423 0     0 0 0 my( $self, $kernel, $req, $resp ) = @_[ OBJECT, KERNEL, ARG0..$#_ ];
424              
425             # DEBUG and
426 0         0 warn "$$: xul_file";
427 0         0 my $uri = $req->uri->path;
428 0 0       0 unless( $uri =~ m(^/xul/file(/(.*))?) ) {
429 0         0 return $self->error_standard( RC_BAD_REQUEST, "parsing uri",
430             "$uri isn't a valid path\n" );
431             }
432 0   0     0 my $filename = $2||'';
433 0         0 $req->uri->path( '/xul' );
434 0         0 my $ret = $self->parse_args( $req );
435 0 0       0 unless( ref $ret ) {
436 0         0 return $self->parse_error( $ret );
437             }
438              
439 0         0 $req->param( filename => $filename );
440 0         0 return shift->xul( @_ );
441             }
442              
443              
444              
445             ############################################################################
446             # Static file handling
447              
448             ###############################################################
449             sub static
450             {
451 0     0 0 0 my( $self, $kernel, $req, $resp ) = @_[ OBJECT, KERNEL, ARG0..$#_ ];
452              
453 0         0 DEBUG and
454             xwarn "POE::Component::XUL->static";
455 0 0       0 if( $self->{shutdown} ) {
456 0         0 xwarn "Static request, but we are shutdown\n";
457 0         0 return;
458             }
459              
460 0         0 local $self->{request} = $req;
461 0         0 local $self->{response} = $resp;
462              
463 0         0 my $ret;
464 0         0 eval {
465 0         0 my $method = $req->method;
466             # Verify HTTP method
467 0 0 0     0 unless( $method eq 'GET' or $method eq 'HEAD' ) {
468 0         0 $ret = $self->error_standard( RC_METHOD_NOT_ALLOWED, $method );
469 0         0 return;
470             }
471              
472             # Send the file
473 0         0 my $uri = $req->uri->path;
474 0         0 DEBUG and
475             xdebug "Static request: $uri";
476              
477 0         0 my $file = $self->uri_to_file( $uri );
478 0 0       0 if( -d $file ) {
    0          
479 0         0 $ret = $self->static_file( $uri, 'index.html' );
480             }
481             elsif( -f "$file.build" ) {
482 0         0 $ret = $self->build_file( $uri, $file );
483             }
484             else {
485 0         0 $ret = $self->static_file( $uri );
486             }
487 0         0 DEBUG and xwarn "$$: ret=$ret";
488             };
489              
490 0 0       0 if( $ret ) {
491 0         0 $resp->code( $ret );
492             # $response->continue;
493 0         0 return $ret;
494             }
495 0         0 $self->error_standard( RC_INTERNAL_SERVER_ERROR, "serving static file", $@ );
496             }
497              
498             ####################################################################
499             sub uri_to_file
500             {
501 0     0 0 0 my( $self, @path ) = @_;
502              
503 0         0 my $path = File::Spec->catfile( grep {defined} @path );
  0         0  
504 0         0 $path =~ s(/\./)(/)g;
505 0         0 $path =~ s(/\.\./)(/)g;
506              
507 0 0       0 unless( $path =~ s(^/)($self->{static_root}/) ) {
508 0         0 $path = File::Spec->catfile( $self->{static_root}, $path );
509             }
510 0         0 $path =~ s(//)(/)g;
511 0         0 return $path;
512             }
513              
514             ####################################################################
515             sub static_file
516             {
517 1     1 0 3 my( $self, $uri, $file ) = @_;
518              
519 1         3 my $req = $self->{request};
520 1         2 my $resp = $self->{response};
521              
522 1         1 my $fullfile = $file;
523 1 50       3 if( $uri ) {
524 0         0 $fullfile = $self->uri_to_file( $uri, $file );
525             }
526 1         2 DEBUG and xdebug "Static file: $fullfile";
527              
528              
529             # warn "REQUEST=", $req->as_string;
530             # Does the file exist?
531 1 50       11 return $self->error_not_found( $fullfile ) unless -f $fullfile;
532              
533 1         3 my $lastmod = (stat _)[9];
534 1         2 my $size = (stat _)[7];
535              
536             # open the file
537 1         4 my $in = IO::File->new( $fullfile );
538 1 50       39 unless( $in ) {
539 0         0 return $self->error( RC_FORBIDDEN, "$uri: $!" );
540             }
541              
542             # Make sure it's not too huge
543 1 50       3 if( $size > 1024 * 1024 ) {
544 0         0 return $self->error_standard( RC_REQUEST_ENTITY_TOO_LARGE,
545             "looking at the file",
546             "$size is much to large" );
547             }
548              
549             # set up content-type
550 1         7 my $ct = $self->guess_ct( $fullfile );
551 1         1 DEBUG and xdebug "content_type=$ct\n";
552 1         22 $self->{response}->content_type( $ct );
553              
554             # add useful headers
555 1 50 33     64 if( $lastmod and not $ct =~ m(^application/vnd\.mozilla\.xul\+xml$) ) {
556 1         2 DEBUG and xdebug "Last-modified=", time2str( $lastmod );
557 1         5 $self->{response}->header( 'Last-Modified' =>
558             time2str( $lastmod )
559             );
560             }
561              
562             # bail if HEAD request
563 1 50       84 if ( $req->method eq 'HEAD' ) {
564 0         0 DEBUG and
565             xdebug "HEAD size=$size";
566 0         0 $resp->content_length( $size );
567 0         0 return RC_OK;
568             }
569            
570             # RFC1945 says HEAD should ingore if-modified-since
571              
572             # 304 check
573 1         15 my $since = $req->header( 'If-Modified-Since' );
574 1 50       25 if( $since ) {
575 0         0 DEBUG and xdebug "If-mod-since=$since";
576 0         0 $since = str2time( $since );
577            
578 0 0 0     0 if ( $lastmod && $since && $since >= $lastmod ) {
      0        
579 0         0 DEBUG and xdebug "NOT MODIFIED SINCE (size=$size)";
580 0         0 $resp->header( 'Last-Modified' => '' );
581 0         0 return RC_NOT_MODIFIED;
582             }
583             }
584             # warn "RESPONSE=", $self->{response}->as_string;
585              
586             # Read and set the content
587 1         10 my $c = join '', <$in>;
588 1         8 undef( $in );
589              
590 1 50 33     7 if( ($uri eq '/' or $uri =~ m(^/index.html?)) and
      33        
591             $c =~ /\[APP-LIST\]/ ) {
592 0         0 my $alist = $self->app_list;
593 0         0 $c =~ s/\[APP-LIST\]/$alist/g;
594             }
595              
596 1         5 $self->{response}->content( $c );
597 1         23 $self->{response}->content_length( length $c );
598 1         36 return RC_OK;
599             }
600              
601             ####################################################################
602             sub app_list
603             {
604 0     0 0 0 my( $self ) = @_;
605 0         0 my @html = <
606            
617            
618             HTML
619 0         0 my $lang = $self->language_guess;
620              
621 0 0       0 my $text = $lang eq 'fr' ? "Avec menus" : "Keep menus";
622 0         0 my $count = keys %{ $self->{apps} };
  0         0  
623 0         0 foreach my $app ( sort keys %{ $self->{apps} } ) {
  0         0  
624 0 0 0     0 next if $app eq 'IGDAIP' and 1 != $count;
625 0   0     0 my $name = $self->{app_names}{$app}{$lang} || $app;
626 0         0 push @html, <
627            
  • $name
  • 628             ($text)
    629             HTML
    630             }
    631              
    632 0         0 push @html, "";
    633 0         0 return join "\n", @html;
    634             }
    635              
    636             sub language_guess
    637             {
    638 0     0 0 0 my( $self ) = @_;
    639 0 0       0 return $self->{default_language} unless $self->{request};
    640 0         0 my $accept = $self->{request}->header( 'Accept-Language' );
    641             $self->{acceptor} ||= I18N::AcceptLanguage->new(
    642             defaultLanguage => $self->{default_language},
    643 0   0     0 strict => 0
    644             );
    645 0         0 return $self->{acceptor}->accepts( $accept, $self->{languages} );
    646             }
    647              
    648             ####################################################################
    649             # Build a file out of smaller files
    650             # This removes the need for complex Makefiles to build up a single
    651             # javascript / CSS / XBL file.
    652             #
    653             # The Build files is the filename + .build extention
    654             # A Cache file is the filename + .cache extention
    655             sub build_file
    656             {
    657 1     1 0 29460 my( $self, $uri, $fullfile ) = @_;
    658              
    659 1         3 my $bfile = "$fullfile.build";
    660 1         27 my $bage = (stat $bfile)[9];
    661 1         3 my $cfile = "$fullfile.cache";
    662 1         19 my $cage = (stat $cfile)[9];
    663            
    664 1 50 33     6 unless( $cage and $cage > $bage ) { # cache file isn't newer then build file
    665             # so we have to create the cache file
    666 1         3 local $self->{loop_check} = {};
    667 1         4 $self->create_cache_file( $cfile, $bfile );
    668             }
    669            
    670 1         53 return $self->static_file( '', $cfile );
    671             }
    672              
    673             ############################################################
    674             # Recursively create the file in $cfile from $bfile
    675             sub create_cache_file
    676             {
    677 8     8 0 579 my( $self, $cfile, $bfile ) = @_;
    678 8         7 my $out = $cfile;
    679 8 100       35 $out = IO::File->new( "> $cfile" ) unless ref $cfile;
    680              
    681 8         459 my $dir = dirname $bfile;
    682              
    683 8 50       23 if( $self->{loop_check}{ $bfile } ) {
    684 0         0 die "Recursion detected: $bfile included more then once";
    685             }
    686 8         17 local $self->{loop_check}{ $bfile } = 1;
    687              
    688 8 50       31 my $in = IO::File->new( $bfile ) or die "Unable to read $bfile: $!\n";
    689 8         511 while( my $line = <$in> ) {
    690 16 100       140 if( $line =~ /^\s*\@include "(.+)"\s*$/) {
    691 6         155 my $file = File::Spec->rel2abs( $1, $dir );
    692 6         14 $self->create_cache_file( $out, $file );
    693             }
    694             else {
    695 10         25 $out->print( $line );
    696             }
    697             }
    698             }
    699              
    700             ############################################################
    701             sub guess_ct
    702             {
    703 3     3 0 1824 my($self, $file)=@_;
    704 3         9 $file =~ s/\.cache$//;
    705 3         11 my $ct = $self->{mimetypes}->mimeTypeOf( $file );
    706 3   100     151 $ct ||= 'application/octet-stream';
    707 3 50       13 $ct .= '; charset=iso-8859-1' if $ct eq 'text/html';
    708              
    709 3         32 return $ct;
    710             }
    711              
    712             ############################################################
    713             # URI that would restart an application
    714             sub uri_restart
    715             {
    716 0     0 0 0 my( $self ) = @_;
    717 0         0 my $req = $self->{request};
    718 0         0 my $uri = $req->uri;
    719              
    720             # We need to know what the browser thinks we are called
    721 0         0 my $host = $req->header( 'X-Forwarded-Host' );
    722 0 0       0 if( $host ) {
    723 0         0 xwarn "Restart on $host";
    724 0         0 $host =~ s/,.+$//;
    725 0         0 $uri->host( $host );
    726 0 0 0     0 $uri->port( undef ) if defined $uri->port and 0==$uri->port;
    727             }
    728 0         0 my $referer = $req->header( 'Referer' );
    729 0 0 0     0 if( $referer and $referer =~ /https/ ) {
    730 0         0 $uri->scheme( 'https' );
    731             }
    732 0         0 $uri->path( '/start.xul' );
    733 0         0 my $app = $req->param( 'app' );
    734 0         0 $uri->query_keywords( $app );
    735 0         0 return $uri;
    736             }
    737              
    738             ############################################################################
    739             # Error handling
    740              
    741             ############################################################
    742             sub error
    743             {
    744 0     0 0 0 my($self, $code, $text, $ct)=@_;
    745              
    746 0   0     0 $ct ||= 'text/plain';
    747              
    748             # This could get annoying fast. It also shows 404s
    749 0 0       0 warn "$code $text\n"unless $ENV{AUTOMATED_TESTING};
    750 0 0 0     0 xlog "$code $text\n"
    751             if $ct eq 'text/plain' and (DEBUG or $code != RC_NOT_FOUND);
    752              
    753 0 0       0 if( $self->{response} ) {
    754 0         0 $self->{response}->code( $code );
    755 0         0 $self->{response}->content_type( $ct );
    756 0 0       0 if( $ct eq 'text/html' ) {
    757 0         0 $text = encode_entities_numeric( $text, "\x80-\xff" );
    758             }
    759              
    760 0         0 $self->{response}->content( $text );
    761 0         0 $self->{response}->content_length( length $text );
    762             }
    763             else {
    764 0         0 xcarp "Response was already sent!";
    765             }
    766 0         0 return $code;
    767             }
    768              
    769             ############################################################
    770             sub error_standard
    771             {
    772 0     0 0 0 my( $self, $code, $when, $what ) = @_;
    773              
    774             # Thank you HTTP::Status
    775 0         0 my $message = status_message( $code );
    776 0   0     0 $message ||= 'unknown';
    777              
    778 0   0     0 $what ||= '';
    779              
    780 0         0 return $self->error( $code, "Error while $when: $message ($code)\n$what" );
    781             }
    782              
    783             ############################################################
    784             sub error_not_found
    785             {
    786 0     0 0 0 my( $self, $file ) = @_;
    787 0         0 my $msg = "Unknown file '$file'";
    788 0         0 xwarn "$msg\n";
    789              
    790 0         0 return $self->error( RC_NOT_FOUND, <<" HTML", 'text/html');
    791            
    792             404 N'existe pas
    793            
    794            

    Le fichier que vous cherchez ne semble pas exister.

    795            
    $msg
    796            
    797            
    798             HTML
    799             }
    800              
    801             ###############################################################
    802             ## TODO : as XUL
    803             sub error_unknown_session
    804             {
    805 0     0 0 0 my( $self, $SID ) = @_;
    806              
    807 0         0 xwarn "Unknown session $SID";
    808              
    809 0         0 my $url = $self->uri_restart;
    810              
    811 0         0 return $self->error( RC_GONE, <<" HTML", 'text/html');
    812            
    813             410 absent
    814            
    815            

    Program inexistante

    816            

    Votre session ($SID) n'existe pas. Elle est surement expirée.

    817            

    Ouvrir une nouvelle session.

    818            
    819            
    820             HTML
    821             }
    822              
    823             ###############################################################
    824             ## TODO : as XUL
    825             sub error_boot_fail
    826             {
    827 0     0 0 0 my( $self, $fail ) = @_;
    828              
    829 0         0 return $self->error( RC_NOT_FOUND, <<" HTML", 'text/html');
    830            
    831             404 absent
    832            
    833            

    Écheque au démarrage

    834            

    $fail

    835            
    836            
    837             HTML
    838             }
    839              
    840              
    841              
    842              
    843             ############################################################
    844             sub httpd_error
    845             {
    846 0     0 0 0 my( $self, $request, $response) = @_[ OBJECT, ARG0..$#_ ];
    847              
    848 0         0 my $op=$request->header('Operation');
    849 0         0 my $errnum=$request->header('Errnum');
    850 0         0 my $errstr=$request->header('Error');
    851              
    852 0         0 DEBUG and
    853             xdebug "HTTPD ERROR op=$op errstr=$errstr errnum=$errnum\n";
    854              
    855 0 0 0     0 if($op eq 'read' and ($errnum==0 or $errnum = ECONNRESET)) {
          0        
    856             # remote closed
    857 0 0 0     0 if( $self->{controler} and $request ) {
    858 0         0 DEBUG and
    859             xdebug "$$ REMOTE CLOSED req=$request";
    860 0         0 $self->{controler}->cancel( $request );
    861             }
    862             # PostHandler will deal with resuming the listening socket
    863             }
    864             else {
    865 0         0 xwarn "Error during $op: [$errnum] $errstr";
    866             }
    867              
    868 0         0 return RC_OK;
    869            
    870             }
    871              
    872             ############################################################################
    873             # Peeking
    874              
    875             ###############################################################
    876             sub poe_size
    877             {
    878 0     0 0 0 my( $self, $kernel, $req, $resp ) = @_[ OBJECT, KERNEL, ARG0, ARG1 ];
    879              
    880 0         0 my $content = -1;
    881 0         0 if( DEBUG and $HAVE_DEVEL_SIZE ) {
    882             $content = Devel::Size::total_size( $kernel );
    883             }
    884 0         0 $resp->code( RC_OK );
    885 0         0 $resp->content_type( 'text/plain' );
    886 0         0 $resp->content_length( length $content );
    887 0         0 $resp->content( $content );
    888 0         0 return RC_OK;
    889             }
    890              
    891             sub poe_kernel
    892             {
    893 0     0 0 0 my( $self, $kernel, $req, $resp ) = @_[ OBJECT, KERNEL, ARG0, ARG1 ];
    894              
    895 0         0 my $content = '';
    896 0         0 if( DEBUG and $HAVE_DATA_DUMPER ) {
    897             local $Data::Dumper::Indent = 1;
    898             $content = Data::Dumper::Dumper( $kernel );
    899             }
    900 0         0 $resp->code( RC_OK );
    901 0         0 $resp->content_type( 'text/plain' );
    902 0         0 $resp->content_length( length $content );
    903 0         0 $resp->content( $content );
    904 0         0 return RC_OK;
    905             }
    906              
    907             sub poe_test
    908             {
    909 0     0 0 0 my( $self, $kernel, $req, $resp ) = @_[ OBJECT, KERNEL, ARG0, ARG1 ];
    910              
    911 0         0 local $self->{request} = $req;
    912 0         0 local $self->{response} = $resp;
    913              
    914 0         0 $self->parse_args( $req );
    915              
    916 0         0 my $uri_restart = $self->uri_restart;
    917 0         0 my $content = <
    918             uri_restart: $uri_restart
    919             TEXT
    920 0         0 xwarn "content=$content";
    921 0         0 $resp->code( RC_OK );
    922 0         0 $resp->content_type( 'text/plain' );
    923 0         0 $resp->content_length( length $content );
    924 0         0 $resp->content( $content );
    925 0         0 return RC_OK;
    926             }
    927              
    928              
    929              
    930              
    931              
    932              
    933             ############################################################################
    934             # Log handling
    935              
    936             ############################################################
    937             sub build_logging
    938             {
    939 6     6 0 9 my( $self, $args_log ) = @_;
    940              
    941 6         38 $self->{logging} = POE::XUL::Logging->new( $args_log, $self->{log_root} );
    942             }
    943              
    944             ############################################################
    945             sub log_setup
    946             {
    947 5     5 0 1491 my( $self ) = @_;
    948 5         22 $self->{logging}->setup;
    949             }
    950              
    951             ############################################################
    952             sub sig_HUP
    953             {
    954 0     0 0   my( $self ) = @_;
    955 0           xwarn "SIGHUP";
    956 0           $poe_kernel->sig_handled();
    957              
    958 0           $self->log_setup;
    959             }
    960              
    961              
    962             ############################################################
    963             sub post_connection
    964             {
    965 0     0 0   my( $self, $req, $resp ) = @_;
    966 0   0       my $app = eval { $req->param( 'app' ) } || $self->{logging}->{app};
    967 0           local $self->{logging}->{app} = $app;
    968              
    969 0           my $conn = $req->connection;
    970 0           my @log;
    971 0 0         push @log, ($conn ? $conn->remote_ip : '0.0.0.0');
    972 0 0 0       if( $log[-1] eq '127.0.0.1' and $req->header( 'X-Forwarded-For' ) ) {
    973 0           $log[-1] = $req->header( 'X-Forwarded-For' );
    974             }
    975             # push @log, ($self->{preforked} ? $$ : '-');
    976 0           push @log, $$, '-';
    977              
    978            
    979              
    980 0           my $path = $req->uri->path;
    981 0           my $query = $req->uri->query;
    982 0 0 0       $path .= "?$query" if $query and $req->method eq 'GET';
    983              
    984 0           push @log, "[". POSIX::strftime("%d/%m/%Y:%H:%M:%S %z", localtime)."]",
    985             join ' ', $req->method, $path;
    986 0           $log[-1] = qq("$log[-1]");
    987 0   0       push @log, ($resp->code||'000'), ($resp->content_length||0);
          0        
    988              
    989 0           xlog( { message => join( ' ', @log )."\n",
    990             type => 'REQ'
    991             } );
    992             # use Devel::Cycle;
    993             # find_cycle( $poe_kernel );
    994 0           return RC_OK;
    995             }
    996              
    997              
    998              
    999              
    1000             1;
    1001              
    1002             __END__