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