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