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