improved examples and tests
[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;
9 use IO::Handle;
10 use IO::File;
11
12 __PACKAGE__->mk_accessors(qw[ enviroment request stdin stdout stderr ]);
13
14 our $VERSION = 0.1;
15
16 sub new {
17     my $class   = shift;
18     my $request = shift;
19
20     my $self = {
21         request  => $request,
22         restored => 0,
23         setuped  => 0,
24         stdin    => IO::File->new_tmpfile,
25         stdout   => IO::File->new_tmpfile
26     };
27
28     my $host = $request->header('Host');
29     my $uri  = $request->uri->clone;
30     $uri->scheme('http')    unless $uri->scheme;
31     $uri->host('localhost') unless $uri->host;
32     $uri->port(80)          unless $uri->port;
33     $uri->host_port($host)  unless !$host || ( $host eq $uri->host_port );
34
35     $self->{enviroment} = {
36         GATEWAY_INTERFACE => 'CGI/1.1',
37         HTTP_HOST         => $uri->host_port,
38         HTTPS             => ( $uri->scheme eq 'https' ) ? 'ON' : 'OFF',  # not in RFC 3875
39         PATH_INFO         => $uri->path,
40         QUERY_STRING      => $uri->query || '',
41         SCRIPT_NAME       => '/',
42         SERVER_NAME       => $uri->host,
43         SERVER_PORT       => $uri->port,
44         SERVER_PROTOCOL   => $request->protocol || 'HTTP/1.1',
45         SERVER_SOFTWARE   => "HTTP-Request-AsCGI/$VERSION",
46         REMOTE_ADDR       => '127.0.0.1',
47         REMOTE_HOST       => 'localhost',
48         REMOTE_PORT       => int( rand(64000) + 1000 ),                   # not in RFC 3875
49         REQUEST_URI       => $uri->path_query || '/',                     # not in RFC 3875
50         REQUEST_METHOD    => $request->method,
51         @_
52     };
53
54     foreach my $field ( $request->headers->header_field_names ) {
55
56         my $key = uc($field);
57         $key =~ tr/-/_/;
58         $key = 'HTTP_' . $key unless $field =~ /^Content-(Length|Type)$/;
59
60         unless ( exists $self->{enviroment}->{$key} ) {
61             $self->{enviroment}->{$key} = $request->headers->header($field);
62         }
63     }
64
65     return $class->SUPER::new($self);
66 }
67
68 sub setup {
69     my $self = shift;
70
71     $self->{restore}->{enviroment} = {%ENV};
72
73     open( $self->{restore}->{stdin}, '>&', STDIN->fileno )
74       or croak("Can't dup stdin: $!");
75
76     open( STDIN, '<&=', $self->stdin->fileno )
77       or croak("Can't open stdin: $!");
78
79     binmode( $self->stdin );
80     binmode( STDIN );
81
82     if ( $self->request->content_length ) {
83
84         syswrite( $self->stdin, $self->request->content )
85           or croak("Can't write request content to stdin handle: $!");
86
87         sysseek( $self->stdin, 0, SEEK_SET )
88           or croak("Can't seek stdin handle: $!");
89     }
90
91     if ( $self->stdout ) {
92
93         open( $self->{restore}->{stdout}, '>&', STDOUT->fileno )
94           or croak("Can't dup stdout: $!");
95
96         open( STDOUT, '>&=', $self->stdout->fileno )
97           or croak("Can't open stdout: $!");
98
99         binmode( $self->stdout );
100         binmode( STDOUT);
101     }
102
103     if ( $self->stderr ) {
104
105         open( $self->{restore}->{stderr}, '>&', STDERR->fileno )
106           or croak("Can't dup stderr: $!");
107
108         open( STDERR, '>&=', $self->stderr->fileno )
109           or croak("Can't open stderr: $!");
110
111         binmode( $self->stderr );
112         binmode( STDERR );
113     }
114
115     {
116         no warnings 'uninitialized';
117         %ENV = %{ $self->enviroment };
118     }
119     
120     if ( $INC{'CGI.pm'} ) {
121         CGI::initialize_globals();
122     }    
123
124     $self->{setuped}++;
125
126     return $self;
127 }
128
129 sub response {
130     my ( $self, $callback ) = @_;
131
132     return undef unless $self->{setuped};
133     return undef unless $self->{restored};
134     return undef unless $self->{restore}->{stdout};
135
136     require HTTP::Response;
137
138     my $message = undef;
139     my $stdout  = $self->stdout;
140
141     seek( $self->stdout, 0, SEEK_SET )
142       or croak("Can't seek stdout handle: $!");
143
144     while ( my $line = <$stdout> ) {
145         $message .= $line;
146         last if $line =~ /^\x0d?\x0a$/;
147     }
148
149     unless ( $message =~ /^HTTP/ ) {
150         $message = "HTTP/1.1 200\x0d\x0a" . $message;
151     }
152
153     my $response = HTTP::Response->parse($message);
154
155     if ( my $code = $response->header('Status') ) {
156         $response->code($code);
157     }
158
159     $response->protocol( $self->request->protocol );
160     $response->headers->date( time() );
161
162     if ($callback) {
163         $response->content( sub {
164             if ( $self->stdout->read( my $buffer, 4096 ) ) {
165                 return $buffer;
166             }
167             return undef;
168         });
169     }
170     else {
171         my $length = 0;
172         while ( $self->stdout->read( my $buffer, 4096 ) ) {
173             $length += length($buffer);
174             $response->add_content($buffer);
175         }
176         $response->content_length($length) unless $response->content_length;
177     }
178
179     return $response;
180 }
181
182 sub restore {
183     my $self = shift;
184
185     %ENV = %{ $self->{restore}->{enviroment} };
186
187     open( STDIN, '>&', $self->{restore}->{stdin} )
188       or croak("Can't restore stdin: $!");
189
190     sysseek( $self->stdin, 0, SEEK_SET )
191       or croak("Can't seek stdin: $!");
192
193     if ( $self->{restore}->{stdout} ) {
194
195         STDOUT->flush
196           or croak("Can't flush stdout: $!");
197
198         open( STDOUT, '>&', $self->{restore}->{stdout} )
199           or croak("Can't restore stdout: $!");
200
201         sysseek( $self->stdout, 0, SEEK_SET )
202           or croak("Can't seek stdout: $!");
203     }
204
205     if ( $self->{restore}->{stderr} ) {
206
207         STDERR->flush
208           or croak("Can't flush stderr: $!");
209
210         open( STDERR, '>&', $self->{restore}->{stderr} )
211           or croak("Can't restore stderr: $!");
212
213         sysseek( $self->stderr, 0, SEEK_SET )
214           or croak("Can't seek stderr: $!");
215     }
216
217     $self->{restored}++;
218
219     return $self;
220 }
221
222 sub DESTROY {
223     my $self = shift;
224     $self->restore if $self->{setuped} && !$self->{restored};
225 }
226
227 1;
228
229 __END__
230
231 =head1 NAME
232
233 HTTP::Request::AsCGI - Setup a CGI enviroment from a HTTP::Request
234
235 =head1 SYNOPSIS
236
237     use CGI;
238     use HTTP::Request;
239     use HTTP::Request::AsCGI;
240     
241     my $request = HTTP::Request->new( GET => 'http://www.host.com/' );
242     my $stdout;
243     
244     {
245         my $c = HTTP::Request::AsCGI->new($request)->setup;
246         my $q = CGI->new;
247         
248         print $q->header,
249               $q->start_html('Hello World'),
250               $q->h1('Hello World'),
251               $q->end_html;
252         
253         $stdout = $c->stdout;
254         
255         # enviroment and descriptors will automatically be restored when $c is destructed.
256     }
257     
258     while ( my $line = $stdout->getline ) {
259         print $line;
260     }
261     
262 =head1 DESCRIPTION
263
264 =head1 METHODS
265
266 =over 4 
267
268 =item new
269
270 =item enviroment
271
272 =item setup
273
274 =item restore
275
276 =item request
277
278 =item response
279
280 =item stdin
281
282 =item stdout
283
284 =item stderr
285
286 =back
287
288 =head1 BUGS
289
290 =item THANKS TO
291
292 Thomas L. Shinnick for his valuable win32 testing.
293
294 =head1 AUTHOR
295
296 Christian Hansen, C<ch@ngmedia.com>
297
298 =head1 LICENSE
299
300 This library is free software. You can redistribute it and/or modify 
301 it under the same terms as perl itself.
302
303 =cut