First stab at cleaning up H::R::AsCGI
[catagits/HTTP-Request-AsCGI.git] / lib / HTTP / Request / AsCGI.pm
1 package HTTP::Request::AsCGI;
2
3 use strict;
4 use warnings;
5 use bytes;
6 use base 'Class::Accessor::Fast';
7
8 use Carp            qw[croak];
9 use HTTP::Response  qw[];
10 use IO::Handle      qw[];
11 use IO::File        qw[SEEK_SET];
12 use Symbol          qw[];
13
14 __PACKAGE__->mk_accessors(qw[environment request is_restored is_setuped is_prepared should_dup should_restore should_rewind stdin stdout stderr]);
15
16 our $VERSION = 0.6_01;
17
18 sub new {
19     my $class  = ref $_[0] ? ref shift : shift;
20     my $params = {};
21
22     if ( @_ % 2 == 0 ) {
23         $params = { @_ };
24     }
25     else {
26         $params = { request => shift, environment => { @_ } };
27     }
28
29     return bless( {}, $class )->initialize($params);
30 }
31
32 sub initialize {
33     my ( $self, $params ) = @_;
34
35     if ( exists $params->{request} ) {
36         $self->request( $params->{request} );
37     }
38     else {
39         croak("Mandatory parameter 'request' is missing.");
40     }
41
42     if ( exists $params->{environment} ) {
43         $self->environment( $params->{environment} );
44     }
45     else {
46         $self->environment( {} );
47     }
48
49     if ( exists $params->{stdin} ) {
50         $self->stdin( $params->{stdin} );
51     }
52     else {
53         $self->stdin( IO::File->new_tmpfile );
54     }
55
56     if ( exists $params->{stdout} ) {
57         $self->stdout( $params->{stdout} );
58     }
59     else {
60         $self->stdout( IO::File->new_tmpfile );
61     }
62
63     if ( exists $params->{stderr} ) {
64         $self->stderr( $params->{stderr} );
65     }
66     
67     if ( exists $params->{dup} ) {
68         $self->should_dup( $params->{dup} ? 1 : 0 );
69     }
70     else {
71         $self->should_dup(1);
72     }    
73
74     if ( exists $params->{restore} ) {
75         $self->should_restore( $params->{restore} ? 1 : 0 );
76     }
77     else {
78         $self->should_restore(1);
79     }
80
81     if ( exists $params->{rewind} ) {
82         $self->should_rewind( $params->{rewind} ? 1 : 0 );
83     }
84     else {
85         $self->should_rewind(1);
86     }
87
88     $self->prepare;
89
90     return $self;
91 }
92
93 *enviroment = \&environment;
94
95 sub has_stdin  { return defined $_[0]->stdin  }
96 sub has_stdout { return defined $_[0]->stdout }
97 sub has_stderr { return defined $_[0]->stderr }
98
99 sub prepare {
100     my $self = shift;
101
102     my $environment = $self->environment;
103     my $request     = $self->request;
104
105     my $host = $request->header('Host');
106     my $uri  = $request->uri->clone;
107
108     $uri->scheme('http')    unless $uri->scheme;
109     $uri->host('localhost') unless $uri->host;
110     $uri->port(80)          unless $uri->port;
111     $uri->host_port($host)  unless !$host || ( $host eq $uri->host_port );
112
113     $uri = $uri->canonical;
114
115     my %cgi = (
116         GATEWAY_INTERFACE => 'CGI/1.1',
117         HTTP_HOST         => $uri->host_port,
118         HTTPS             => ( $uri->scheme eq 'https' ) ? 'ON' : 'OFF',  # not in RFC 3875
119         PATH_INFO         => $uri->path,
120         QUERY_STRING      => $uri->query || '',
121         SCRIPT_NAME       => '/',
122         SERVER_NAME       => $uri->host,
123         SERVER_PORT       => $uri->port,
124         SERVER_PROTOCOL   => $request->protocol || 'HTTP/1.1',
125         SERVER_SOFTWARE   => "HTTP-Request-AsCGI/$VERSION",
126         REMOTE_ADDR       => '127.0.0.1',
127         REMOTE_HOST       => 'localhost',
128         REMOTE_PORT       => int( rand(64000) + 1000 ),                   # not in RFC 3875
129         REQUEST_URI       => $uri->path_query,                            # not in RFC 3875
130         REQUEST_METHOD    => $request->method
131     );
132
133     foreach my $key ( keys %cgi ) {
134
135         unless ( exists $environment->{ $key } ) {
136             $environment->{ $key } = $cgi{ $key };
137         }
138     }
139
140     foreach my $field ( $self->request->headers->header_field_names ) {
141
142         my $key = uc("HTTP_$field");
143         $key =~ tr/-/_/;
144         $key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
145
146         unless ( exists $environment->{ $key } ) {
147             $environment->{ $key } = $self->request->headers->header($field);
148         }
149     }
150
151     unless ( $environment->{SCRIPT_NAME} eq '/' && $environment->{PATH_INFO} ) {
152         $environment->{PATH_INFO} =~ s/^\Q$environment->{SCRIPT_NAME}\E/\//;
153         $environment->{PATH_INFO} =~ s/^\/+/\//;
154     }
155
156     $self->is_prepared(1);
157 }
158
159 sub setup {
160     my $self = shift;
161
162     $self->setup_stdin;
163     $self->setup_stdout;
164     $self->setup_stderr;
165     $self->setup_environment;
166
167     if ( $INC{'CGI.pm'} ) {
168         CGI::initialize_globals();
169     }
170
171     $self->is_setuped(1);
172
173     return $self;
174 }
175
176 sub setup_environment {
177     my $self = shift;
178
179     no warnings 'uninitialized';
180
181     if ( $self->should_restore ) {
182         $self->{restore}->{environment} = { %ENV };
183     }
184
185     %ENV = %{ $self->environment };
186 }
187
188 sub setup_stdin {
189     my $self = shift;
190
191     if ( $self->has_stdin ) {
192
193         binmode( $self->stdin );
194
195         if ( $self->request->content_length ) {
196
197             syswrite( $self->stdin, $self->request->content )
198               or croak("Couldn't write request content to stdin handle: '$!'");
199
200             sysseek( $self->stdin, 0, SEEK_SET )
201               or croak("Couldn't seek stdin handle: '$!'");
202         }
203
204         if ( $self->should_dup ) {
205
206             if ( $self->should_restore ) {
207
208                 open( my $stdin, '<&STDIN' )
209                   or croak("Couldn't dup STDIN: '$!'");
210
211                 $self->{restore}->{stdin} = $stdin;
212             }
213
214             STDIN->fdopen( $self->stdin, '<' )
215               or croak("Couldn't redirect STDIN: '$!'");
216         }
217         else {
218
219             my $stdin = Symbol::qualify_to_ref('STDIN');
220
221             if ( $self->should_restore ) {
222
223                 $self->{restore}->{stdin}     = *$stdin;
224                 $self->{restore}->{stdin_ref} = \*$stdin;
225             }
226
227             *{ $stdin } = $self->stdin;
228         }
229
230         binmode( STDIN );
231     }
232 }
233
234 sub setup_stdout {
235     my $self = shift;
236
237     if ( $self->has_stdout ) {
238
239         if ( $self->should_dup ) {
240
241             if ( $self->should_restore ) {
242
243                 open( my $stdout, '>&STDOUT' )
244                   or croak("Couldn't dup STDOUT: '$!'");
245
246                 $self->{restore}->{stdout} = $stdout;
247             }
248
249             STDOUT->fdopen( $self->stdout, '>' )
250               or croak("Couldn't redirect STDOUT: '$!'");
251         }
252         else {
253
254             my $stdout = Symbol::qualify_to_ref('STDOUT');
255
256             if ( $self->should_restore ) {
257
258                 $self->{restore}->{stdout}     = *$stdout;
259                 $self->{restore}->{stdout_ref} = \*$stdout;
260             }
261
262             *{ $stdout } = $self->stdout;
263         }
264
265         binmode( $self->stdout );
266         binmode( STDOUT);
267     }
268 }
269
270 sub setup_stderr {
271     my $self = shift;
272
273     if ( $self->has_stderr ) {
274
275         if ( $self->should_dup ) {
276
277             if ( $self->should_restore ) {
278
279                 open( my $stderr, '>&STDERR' )
280                   or croak("Couldn't dup STDERR: '$!'");
281
282                 $self->{restore}->{stderr} = $stderr;
283             }
284
285             STDERR->fdopen( $self->stderr, '>' )
286               or croak("Couldn't redirect STDERR: '$!'");
287         }
288         else {
289
290             my $stderr = Symbol::qualify_to_ref('STDERR');
291
292             if ( $self->should_restore ) {
293
294                 $self->{restore}->{stderr}     = *$stderr;
295                 $self->{restore}->{stderr_ref} = \*$stderr;
296             }
297
298             *{ $stderr } = $self->stderr;
299         }
300
301         binmode( $self->stderr );
302         binmode( STDERR );
303     }
304 }
305
306 sub response {
307     my ( $self, $callback ) = @_;
308
309     return undef unless $self->stdout;
310
311     seek( $self->stdout, 0, SEEK_SET )
312       or croak("Couldn't seek stdout handle: '$!'");
313
314     my $headers;
315     while ( my $line = $self->stdout->getline ) {
316         $headers .= $line;
317         last if $headers =~ /\x0d?\x0a\x0d?\x0a$/;
318     }
319
320     unless ( defined $headers ) {
321         $headers = "HTTP/1.1 500 Internal Server Error\x0d\x0a";
322     }
323
324     unless ( $headers =~ /^HTTP/ ) {
325         $headers = "HTTP/1.1 200 OK\x0d\x0a" . $headers;
326     }
327
328     my $response = HTTP::Response->parse($headers);
329     $response->date( time() ) unless $response->date;
330
331     my $message = $response->message;
332     my $status  = $response->header('Status');
333
334     if ( $message && $message =~ /^(.+)\x0d$/ ) {
335         $response->message($1);
336     }
337
338     if ( $status && $status =~ /^(\d\d\d)\s?(.+)?$/ ) {
339
340         my $code    = $1;
341         my $message = $2 || HTTP::Status::status_message($code);
342
343         $response->code($code);
344         $response->message($message);
345     }
346
347     my $length = ( stat( $self->stdout ) )[7] - tell( $self->stdout );
348
349     if ( $response->code == 500 && !$length ) {
350
351         $response->content( $response->error_as_HTML );
352         $response->content_type('text/html');
353
354         return $response;
355     }
356
357     if ($callback) {
358
359         my $handle = $self->stdout;
360
361         $response->content( sub {
362
363             if ( $handle->read( my $buffer, 4096 ) ) {
364                 return $buffer;
365             }
366
367             return undef;
368         });
369     }
370     else {
371
372         my $length = 0;
373
374         while ( $self->stdout->read( my $buffer, 4096 ) ) {
375             $length += length($buffer);
376             $response->add_content($buffer);
377         }
378
379         if ( $length && !$response->content_length ) {
380             $response->content_length($length);
381         }
382     }
383
384     return $response;
385 }
386
387 sub restore {
388     my $self = shift;
389
390     if ( $self->should_restore ) {
391
392         $self->restore_environment;
393         $self->restore_stdin;
394         $self->restore_stdout;
395         $self->restore_stderr;
396
397         $self->{restore} = {};
398
399         $self->is_restored(1);
400     }
401
402     return $self;
403 }
404
405 sub restore_environment {
406     my $self = shift;
407
408     no warnings 'uninitialized';
409
410     %ENV = %{ $self->{restore}->{environment} };
411 }
412
413 sub restore_stdin {
414     my $self = shift;
415
416     if ( $self->has_stdin ) {
417
418         my $stdin = $self->{restore}->{stdin};
419
420         if ( $self->should_dup ) {
421
422             STDIN->fdopen( $stdin, '<' )
423               or croak("Couldn't restore STDIN: '$!'");
424         }
425         else {
426
427             my $stdin_ref = $self->{restore}->{stdin_ref};
428
429             *{ $stdin_ref } = $stdin;
430         }
431
432         if ( $self->should_rewind ) {
433
434             seek( $self->stdin, 0, SEEK_SET )
435               or croak("Couldn't seek stdin handle: '$!'");
436         }
437     }
438 }
439
440 sub restore_stdout {
441     my $self = shift;
442
443     if ( $self->has_stdout ) {
444
445         my $stdout = $self->{restore}->{stdout};
446
447         if ( $self->should_dup ) {
448
449             STDOUT->flush
450               or croak("Couldn't flush STDOUT: '$!'");
451
452             STDOUT->fdopen( $stdout, '>' )
453               or croak("Couldn't restore STDOUT: '$!'");
454         }
455         else {
456
457             my $stdout_ref = $self->{restore}->{stdout_ref};
458
459             *{ $stdout_ref } = $stdout;
460         }
461
462         if ( $self->should_rewind ) {
463
464             seek( $self->stdout, 0, SEEK_SET )
465               or croak("Couldn't seek stdout handle: '$!'");
466         }
467     }
468 }
469
470 sub restore_stderr {
471     my $self = shift;
472
473     if ( $self->has_stderr ) {
474
475         my $stderr = $self->{restore}->{stderr};
476
477         if ( $self->should_dup ) {
478
479             STDERR->flush
480               or croak("Couldn't flush STDERR: '$!'");
481
482             STDERR->fdopen( $stderr, '>' )
483               or croak("Couldn't restore STDERR: '$!'");
484         }
485         else {
486
487             my $stderr_ref = $self->{restore}->{stderr_ref};
488
489             *{ $stderr_ref } = $stderr;
490         }
491
492         if ( $self->should_rewind ) {
493
494             seek( $self->stderr, 0, SEEK_SET )
495               or croak("Couldn't seek stderr handle: '$!'");
496         }
497     }
498 }
499
500 sub DESTROY {
501     my $self = shift;
502
503     if ( $self->should_restore ) {
504
505         if ( $self->is_setuped && !$self->is_restored ) {
506             $self->restore;
507         }
508     }
509 }
510
511 1;
512
513 __END__
514
515 =head1 NAME
516
517 HTTP::Request::AsCGI - Setup a CGI environment from a HTTP::Request
518
519 =head1 SYNOPSIS
520
521     use CGI;
522     use HTTP::Request;
523     use HTTP::Request::AsCGI;
524
525     my $request = HTTP::Request->new( GET => 'http://www.host.com/' );
526     my $stdout;
527
528     {
529         my $c = HTTP::Request::AsCGI->new($request)->setup;
530         my $q = CGI->new;
531
532         print $q->header,
533               $q->start_html('Hello World'),
534               $q->h1('Hello World'),
535               $q->end_html;
536
537         $stdout = $c->stdout;
538
539         # environment and descriptors will automatically be restored
540         # when $c is destructed.
541     }
542
543     while ( my $line = $stdout->getline ) {
544         print $line;
545     }
546
547 =head1 DESCRIPTION
548
549 Provides a convinient way of setting up an CGI environment from a HTTP::Request.
550
551 =head1 METHODS
552
553 =over 4
554
555 =item new ( $request [, key => value ] )
556
557 Contructor, first argument must be a instance of HTTP::Request
558 followed by optional pairs of environment key and value.
559
560 =item environment
561
562 Returns a hashref containing the environment that will be used in setup.
563 Changing the hashref after setup has been called will have no effect.
564
565 =item setup
566
567 Setups the environment and descriptors.
568
569 =item restore
570
571 Restores the environment and descriptors. Can only be called after setup.
572
573 =item request
574
575 Returns the request given to constructor.
576
577 =item response
578
579 Returns a HTTP::Response. Can only be called after restore.
580
581 =item stdin
582
583 Accessor for handle that will be used for STDIN, must be a real seekable
584 handle with an file descriptor. Defaults to a tempoary IO::File instance.
585
586 =item stdout
587
588 Accessor for handle that will be used for STDOUT, must be a real seekable
589 handle with an file descriptor. Defaults to a tempoary IO::File instance.
590
591 =item stderr
592
593 Accessor for handle that will be used for STDERR, must be a real seekable
594 handle with an file descriptor.
595
596 =back
597
598 =head1 SEE ALSO
599
600 =over 4
601
602 =item examples directory in this distribution.
603
604 =item L<WWW::Mechanize::CGI>
605
606 =item L<Test::WWW::Mechanize::CGI>
607
608 =back
609
610 =head1 THANKS TO
611
612 Thomas L. Shinnick for his valuable win32 testing.
613
614 =head1 AUTHOR
615
616 Christian Hansen, C<ch@ngmedia.com>
617
618 =head1 LICENSE
619
620 This library is free software. You can redistribute it and/or modify
621 it under the same terms as perl itself.
622
623 =cut