File: PreFork.pm

package info (click to toggle)
libhttp-server-simple-cgi-prefork-perl 6-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, sid, trixie
  • size: 140 kB
  • sloc: perl: 282; makefile: 2
file content (513 lines) | stat: -rw-r--r-- 19,718 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
package HTTP::Server::Simple::CGI::PreFork;

use strict;
use warnings;
use Socket ':all';
use IO::Handle;

#use Socket6 qw[unpack_sockaddr_in6];

our $VERSION = 6.0;
use Carp;

use base qw[HTTP::Server::Simple::CGI];

sub run {
    my ($self, %config) = @_;
    
    if(!defined($config{prefork})) {
        $config{prefork} = 0;
    }

    if(!defined($config{usessl})) {
        $config{usessl} = 0;
    }
    
    if($config{prefork}) {
        # Create new subroutine to tell HTTP::Server::Simple that we want
        # to be a preforking server
        no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
        *{__PACKAGE__ . "::net_server"} = sub {
            my $server = 'Net::Server::PreFork';
            return $server;
        };

    } else {
        no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
        *{__PACKAGE__ . "::net_server"} = sub {
            my $server = 'Net::Server::Single';
            return $server;
        };
    }
    
    # SET UP FOR SSL
    if($config{usessl}) {
        # SET UP FOR SSL
        # we need to ovverride the _process_request sub for IPv6. For SSL, we
        # also need to disable the calls to binmode
    
        no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
        *{__PACKAGE__ . "::_process_request"} =
            sub {
        
            my $self = shift;

            # Create a callback closure that is invoked for each incoming request;
            # the $self above is bound into the closure.
            sub {
                $self->stdio_handle(*STDIN) unless $self->stdio_handle;
        
                # Default to unencoded, raw data out.
                # if you're sending utf8 and latin1 data mixed, you may need to override this
                #binmode STDIN,  ':raw';
                #binmode STDOUT, ':raw';
                
                my $remote_sockaddr = getpeername( $self->stdio_handle );
                if(!$remote_sockaddr && defined($main::_realpeername)) {
                    $remote_sockaddr = $main::_realpeername;
                }
                
                my ( $iport, $iaddr, $peeraddr );
                if($remote_sockaddr) {
                    eval {
                        # Be fully backwards compatible
                        ( $iport, $iaddr ) = sockaddr_in($remote_sockaddr);
                        $peeraddr = $iaddr ? ( inet_ntoa($iaddr) || "127.0.0.1" ) : '127.0.0.1';
                        1;
                    } or do {
                        # Handle cases where the $remote_sockaddr is an IPv6 structure
                        eval {
                            ( $iport, $iaddr ) = unpack_sockaddr_in6($remote_sockaddr);
                            $peeraddr = inet_ntop(AF_INET6, $iaddr);
                            1;
                        } or do {
                            # What is the best way to handle an unparseable $remote_sockaddr?
                            # Will IPv6 be the "old protocol" one day in our lifetime to be superceded
                            # by something even more complex?
                            #
                            # For now, just return "127.0.0.1", which itself is problematic: What
                            # about the time IPv4 gets switched off and some backend will croak because
                            # the IP is too short?
                            $peeraddr = "127.0.0.1";
                        }
                    }
                }
                
                if(!defined($peeraddr)) {
                    $peeraddr = "";
                } elsif($peeraddr =~ /^\:\:ffff\:(\d+)\./) {
                    # Looks like a IPv4 adress in IPv6 format (e.g. ::ffff:192.168.0.1
                    # turn it into an IPv4 address for backward compatibility
                    $peeraddr =~ s/^\:\:ffff\://;
                }
                
                my ( $method, $request_uri, $proto ) = $self->parse_request;
                
                unless ($self->valid_http_method($method) ) {
                    $self->bad_request;
                    return;
                }
        
                $proto ||= "HTTP/0.9";
        
                my ( $file, $query_string )
                    = ( $request_uri =~ /([^?]*)(?:\?(.*))?/s );    # split at ?
        
                $self->setup(
                    method       => $method,
                    protocol     => $proto,
                    query_string => ( defined($query_string) ? $query_string : '' ),
                    request_uri  => $request_uri,
                    path         => $file,
                    localname    => $self->host,
                    localport    => $self->port,
                    peername     => $peeraddr,
                    peeraddr     => $peeraddr,
                    peerport     => $iport,
                );
        
                # HTTP/0.9 didn't have any headers (I think)
                my %xheaders;
                if ( $proto =~ m{HTTP/(\d(\.\d)?)$} and $1 >= 1 ) {
        
                    my $headers = $self->parse_headers
                        or do { $self->bad_request; return };
        
                    %xheaders = (@$headers);
                    $self->headers($headers);
        
                }
                
                my $do_continue = 1;
                if(defined($xheaders{Expect} && $xheaders{Expect} =~ /100\-continue/i)) {
                    $do_continue = $self->handle_continue_header(%xheaders);
                    flush STDOUT;
                }
                
                if($do_continue) {
                    $self->post_setup_hook if $self->can("post_setup_hook");
            
                    $self->handler;
                }   
            }
        }


    } else {
        # SET UP FOR NON-SSL
        
        # we need to ovverride the _process_request sub for IPv6.
        
        no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
        *{__PACKAGE__ . "::_process_request"} =
            sub {
        
            my $self = shift;

            # Create a callback closure that is invoked for each incoming request;
            # the $self above is bound into the closure.
            sub {
        
                $self->stdio_handle(*STDIN) unless $self->stdio_handle;
        
                # Default to unencoded, raw data out.
                # if you're sending utf8 and latin1 data mixed, you may need to override this
                binmode STDIN,  ':raw';
                binmode STDOUT, ':raw';
                
                my $remote_sockaddr = getpeername( $self->stdio_handle );
                if(!$remote_sockaddr && defined($main::_realpeername)) {
                    $remote_sockaddr = $main::_realpeername;
                }
                
                my ( $iport, $iaddr, $peeraddr );

                if($remote_sockaddr) {
                    eval {
                        # Be fully backwards compatible
                        ( $iport, $iaddr ) = sockaddr_in($remote_sockaddr);
                        $peeraddr = $iaddr ? ( inet_ntoa($iaddr) || "127.0.0.1" ) : '127.0.0.1';
                        1;
                    } or do {
                        # Handle cases where the $remote_sockaddr is an IPv6 structure
                        #print STDERR $@ . "\n";
                        eval {
                            ( $iport, $iaddr ) = unpack_sockaddr_in6($remote_sockaddr);
                            $peeraddr = inet_ntop(AF_INET6, $iaddr);
                            1;
                        } or do {
                            #print STDERR $@ . "\n";
                            # What is the best way to handle an unparseable $remote_sockaddr?
                            # Will IPv6 be the "old protocol" one day in our lifetime to be superceded
                            # by something even more complex?
                            #
                            # For now, just return "127.0.0.1", which itself is problematic: What
                            # about the time IPv4 gets switched off and some backend will croak because
                            # the IP is too short?
                            $peeraddr = "127.0.0.1";
                        }
                    }
                }
                if(!defined($peeraddr)) {
                    $peeraddr = "";
                } elsif($peeraddr =~ /^\:\:ffff\:(\d+)\./) {
                    # Looks like a IPv4 adress in IPv6 format (e.g. ::ffff:192.168.0.1
                    # turn it into an IPv4 address for backward compatibility
                    $peeraddr =~ s/^\:\:ffff\://;
                }
                
                my ( $method, $request_uri, $proto ) = $self->parse_request;
                
                unless ($self->valid_http_method($method) ) {
                    $self->bad_request;
                    return;
                }
        
                $proto ||= "HTTP/0.9";
        
                # Google-Chrome, Chromium and others sometimes make "futility connections", e.g.
                # they open a connection, do nothing and just close the connection after a few seconds
                if(!defined($request_uri) || $request_uri eq '') {
                    $self->bad_request;
                    return;
                }
                my ( $file, $query_string )
                    = ( $request_uri =~ /([^?]*)(?:\?(.*))?/s );    # split at ?
        
                $self->setup(
                    method       => $method,
                    protocol     => $proto,
                    query_string => ( defined($query_string) ? $query_string : '' ),
                    request_uri  => $request_uri,
                    path         => $file,
                    localname    => $self->host,
                    localport    => $self->port,
                    peername     => $peeraddr,
                    peeraddr     => $peeraddr,
                    peerport     => $iport,
                );
        
                # HTTP/0.9 didn't have any headers (I think)
                my %xheaders;
                if ( $proto =~ m{HTTP/(\d(\.\d)?)$} and $1 >= 1 ) {
        
                    my $headers = $self->parse_headers
                        or do { $self->bad_request; return };
        
                    %xheaders = (@$headers);
                    $self->headers($headers);
        
                }
                
                my $do_continue = 1;
                if(defined($xheaders{Expect} && $xheaders{Expect} =~ /100\-continue/i)) {
                    $do_continue = $self->handle_continue_header(%xheaders);
                    flush STDOUT;
                }
                
                if($do_continue) {
                    $self->post_setup_hook if $self->can("post_setup_hook");
            
                    $self->handler;
                }
            }
        }

    }

    # Ok now fix broken Net::Server*SSL* handling by putting the the SSL options into ARGV
        my @ssl_args = qw(
        SSL_server
        SSL_use_cert
        SSL_verify_mode
        SSL_key_file
        SSL_cert_file
        SSL_ca_path
        SSL_ca_file
        SSL_cipher_list
        SSL_passwd_cb
        SSL_error_callback
        SSL_max_getline_length
    );
    foreach my $ssl_arg (@ssl_args) {
        if(defined($config{$ssl_arg})) {
            push @ARGV, '--' . $ssl_arg . "=" . $config{$ssl_arg};
        }
    }
    
    # Don't call super, just do out stuff here, as we need some changes anyway
    #return $self->SUPER::run(%config); # Call parent run()
    
    #*{__PACKAGE__ . "::_process_request"} = sub {
    {
        my $server = $self->net_server;
    
        local $SIG{CHLD} = 'IGNORE';    # reap child processes
    
        # $pkg is generated anew for each invocation to "run"
        # Just so we can use different net_server() implementations
        # in different runs.
        my $pkg = join '::', ref($self), "NetServer";
        my $thispkg = ref($self);
    
        no strict 'refs';
        *{"$pkg\::process_request"} = $self->_process_request;
    
        if ($server) {
            require join( '/', split /::/, $server ) . '.pm';
            *{"$pkg\::ISA"} = [$server];
    
            # clear the environment before every request
            require HTTP::Server::Simple::CGI;
            *{"$pkg\::post_accept"} = sub {
                HTTP::Server::Simple::CGI::Environment->setup_environment;
                $config{usessl} and $ENV{'HTTPS'} = 'on'; # Required by CGI spec. Also needed for CGI.pm to return 'on' (and not undef) in https() and to return https:// and not http:// links in url().
                # $self->SUPER::post_accept uses the wrong super package
                $server->can('post_accept')->(@_);
            };
            
            *{"$pkg\::post_accept_hook"} = sub {
                my ($xself) = @_;
                $main::_realpeername = $xself->{server}->{peername};
            };
                
        }
        else {
            $self->setup_listener;
        $self->after_setup_listener();
            *{"$pkg\::run"} = $self->_default_run;
        }
    
        #local $SIG{HUP} = sub { $SERVER_SHOULD_RUN = 0; };
    
        $pkg->run( port => $self->port, @_ );
    };
    
    
}

sub handle_continue_header {
    my ($self, %headers) = @_;
    my $continue = 1;
    
    print "HTTP/1.1 100 Continue\r\n";
    
    return $continue;
    
}

1;
__END__

=head1 NAME

HTTP::Server::Simple::CGI::PreFork - Turn HSS into a preforking webserver and enable SSL

=head1 SYNOPSIS

Are you using HTTP::Server::Simple::CGI (or are you planning to)? But you want to handle multiple
connections at once and even try out this SSL thingy everyone is using these days?

Fear not, the (brilliant) HTTP::Server::Simple::CGI is easy to extend and this (only modestly well-designed)
module does it for you.

HTTP::Server::Simple::CGI::PreFork should be fully IPv6 compliant.

=head1 DESCRIPTION

This module is a plugin module for the "Commands" module and handles
PostgreSQL admin commands scheduled from the WebGUI.

=head1 Configuration

Obviously, you want to read the HTTP::Server::Simple documentation for the bulk
of configuration options. Since we also overload the base tcp connection class
with Net::Server, you might also want to read the documentation for that.

We use two Net::Server classes, depending on if we are preforking or single
threaded:

Net::Server::Single for singlethreaded

Net::Server::PreFork for multithreaded

In addition to the HTTP::Server::Simple configuration,
there are only two additional options (in the hash to) the
run() method: usessl and prefork.

=head2 prefork

Basic usage:

$myserver->run(prefork => 1):

Per default, prefork is turned off (e.g. server runs singlethreaded). This
is very useful for debugging and backward compatibility.

Beware when forking: Keep in mind how database and filehandles behave. Normally,
you should set up everything before the run method (cache files, load confiugurations,...),
then close all handles and run(). Then, depending on your site setup, either open a
database connection for every request and close it again, or (and this is the better
performing option) open a database handle at every request you don't have an open handle yet -
since we are forking, every thread get's its own unique handle while not constantly opening and
closing the handles.

Optionally, you can also add all the different options of Net::Server::Prefork like "max_servers" on
the call to run() to optimize your configuration.

=head2 usessl

Caution: SSL support is experimental at best. I got this to work with a lot of warnings,
sometimes it might not work at all. If you use this, please send patches!

Set this option to 1 if you want to use SSL (default is off). For SSL to actually work, need
to add some extra options (required for the underlying Net::Server classes, something like this
usually does the trick:

$webserver->run(usessl => 1,
                proto => 'ssleay',
                "--SSL_key_file"=> 'mysite.key',
                "--SSL_cert_file"=>'mysite.crt',
                );


=head2 run

Internal functions that overrides the HTTP::Server::Simple::CGI run function. Just as explained above.

=head2 handle_continue_header

Overrideable function that allows one to custom-handle the "100 Continue" status codes. This function
is called if the client sends a a "Expect: 100-continue" header. It defaults to sending a "100 Continue"
status line and proceed with the rest of the request.

If you want to override this, for example to check upload size or permissions, subclass this function. You
will receive the headers as a hash as the only input (nothing much else has been parsed from the client as of
this moment in time).

It is your job to send/print the appropriate status line header, either "100 Continue" or the appropriate error code.
Return true if you want HSS::Prefork to continue data transfer and finish setting up the CGI environment for the request
or false to abort.

BEWARE: Since only the headers have been parsed at this point of time, you don't have the full CGI kaboodle at your disposal.
The way HSS:Prefork overrides the base modules, the internal setup phase is not complete and you should only use the headers
provided to make a basic decision if you want to continue and make a full check later (permissions, client IP, whatever) on,
just as you would when the client wouldn't have send the Expect-Header

=head1 IPv6

This module overrides also the pure IPv4 handling of HTTP::Server::Simple::CGI and turns
it into an IPv4/IPv6 multimode server.

Only caveat here is, that you need the Net::Server modules in version 2.0
or higher. If you still use Net::Server 0.99.6.*, you should install
HTTP::Server::Simple::CGI::PreFork 1.2 from BackPan. 

Net::Server version 0.99 and lower only supports IPv4.

=head1 Possible incompatibilities with your computer

Older versions of HSSC::Prefork did not automatically require the IPv6 modules on installation.
This behaviour has changed, starting at version 2.0. This is in accordance with with RFC6540, titled
"IPv6 Support Required for All IP-Capable Nodes". If you don't have an IPv6 address, that's OK (or more
precisely *your* problem). But the software now assumes that your system is technicaly capable of handling 
IPv6 connections, even if you don't have an IPv6 uplink at the moment.

Doing it this way simplifies many future tasks. Anyway, if your system is old enough to be incapable of
handling IPv6... according to RFC6540 you are not connected to what is nowadays defined as "the internet".


=head1 QUICK-HACK-WARNING

This module "patches" HTTP::Server::Simple by overloading one
of the functions. Updating HTTP::Server::Simple *might* break
something. While this is not very likely, make sure to test
updates before updating a production system!

=head1 AUTHOR

Rene Schickbauer, E<lt>cavac@cpan.orgE<gt>

This module borrows heavily from the follfowing modules:

HTTP::Server::Simple by Jesse Vincent

Net::Server by Paul T. Seamons

HTTPS bugfix for version 6 by Luigi Iotti

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.0 or,
at your option, any later version of Perl 5 you may have available.

=head1 THANKS

Special thanks to Jesse Vincent for giving me quick feedback when i needed it.

Also thanks to the countless PerlMonks helping me out when i'm stuck. This module
is dedicated to you!

=cut