7f92588b3a195916b1a3ba382e8667bcbe80610f
[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( STDIN, ':raw' );
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( STDOUT, ':raw' );
90     }
91
92     if ( $self->stderr ) {
93         open( $self->{restore}->{stderr}, '>&', STDERR->fileno )
94           or croak("Can't dup stderr: $!");
95
96         open( STDERR, '>&=', $self->stderr->fileno )
97           or croak("Can't open stderr: $!");
98         
99         binmode( STDERR, ':raw' );
100     }
101
102     {
103         no warnings 'uninitialized';
104         %ENV = %{ $self->enviroment };
105     }
106
107     $self->{setuped}++;
108
109     return $self;
110 }
111
112 sub response {
113     my ( $self, $callback ) = @_;
114
115     return undef unless $self->{setuped};
116     return undef unless $self->{restored};
117     return undef unless $self->{restore}->{stdout};
118
119     require HTTP::Response;
120
121     my $message  = undef;
122     my $position = $self->stdin->tell;
123
124     $self->stdout->sysseek( 0, SEEK_SET )
125       or croak("Can't seek stdin handle: $!");
126
127     while ( my $line = $self->stdout->getline ) {
128         $message .= $line;
129         last if $line =~ /^\x0d?\x0a$/;
130     }
131
132     unless ( $message =~ /^HTTP/ ) {
133         $message = "HTTP/1.1 200\x0d\x0a" . $message;
134     }
135
136     my $response = HTTP::Response->parse($message);
137
138     if ( my $code = $response->header('Status') ) {
139         $response->code($code);
140     }
141
142     $response->protocol( $self->request->protocol );
143     $response->headers->date( time() );
144
145     if ($callback) {
146         $response->content( sub {
147             if ( $self->stdout->read( my $buffer, 4096 ) ) {
148                 return $buffer;
149             }
150             return undef;
151         });
152     }
153     else {
154         my $length = 0;
155         while ( $self->stdout->read( my $buffer, 4096 ) ) {
156             $length += length($buffer);
157             $response->add_content($buffer);
158         }
159         $response->content_length($length) unless $response->content_length;
160     }
161
162     $self->stdout->sysseek( $position, SEEK_SET )
163       or croak("Can't seek stdin handle: $!");
164
165     return $response;
166 }
167
168 sub restore {
169     my $self = shift;
170
171     %ENV = %{ $self->{restore}->{enviroment} };
172
173     open( STDIN, '>&', $self->{restore}->{stdin} )
174       or croak("Can't restore stdin: $!");
175
176     $self->stdin->sysseek( 0, SEEK_SET )
177       or croak("Can't seek stdin: $!");
178
179     if ( $self->{restore}->{stdout} ) {
180         open( STDOUT, '>&', $self->{restore}->{stdout} )
181           or croak("Can't restore stdout: $!");
182
183         $self->stdout->sysseek( 0, SEEK_SET )
184           or croak("Can't seek stdout: $!");
185     }
186
187     if ( $self->{restore}->{stderr} ) {
188         open( STDERR, '>&', $self->{restore}->{stderr} )
189           or croak("Can't restore stderr: $!");
190
191         $self->stderr->sysseek( 0, SEEK_SET )
192           or croak("Can't seek stderr: $!");
193     }
194
195     $self->{restored}++;
196
197     return $self;
198 }
199
200 sub DESTROY {
201     my $self = shift;
202     $self->restore if $self->{setuped} && !$self->{restored};
203 }
204
205 1;
206
207 __END__
208
209 =head1 NAME
210
211 HTTP::Request::AsCGI - Setup a CGI enviroment from a HTTP::Request
212
213 =head1 SYNOPSIS
214
215     use CGI;
216     use HTTP::Request;
217     use HTTP::Request::AsCGI;
218     
219     my $request = HTTP::Request->new( GET => 'http://www.host.com/' );
220     my $stdout;
221     
222     {
223         my $c = HTTP::Request::AsCGI->new($request)->setup;
224         my $q = CGI->new;
225         
226         print $q->header,
227               $q->start_html('Hello World'),
228               $q->h1('Hello World'),
229               $q->end_html;
230         
231         $stdout = $c->stdout;
232         
233         # enviroment and descriptors will automatically be restored when $c is destructed.
234     }
235     
236     while ( my $line = $stdout->getline ) {
237         print $line;
238     }
239     
240 =head1 DESCRIPTION
241
242 =head1 METHODS
243
244 =over 4 
245
246 =item new
247
248 =item enviroment
249
250 =item setup
251
252 =item restore
253
254 =item request
255
256 =item response
257
258 =item stdin
259
260 =item stdout
261
262 =item stderr
263
264 =back
265
266 =head1 BUGS
267
268 =head1 AUTHOR
269
270 Christian Hansen, C<ch@ngmedia.com>
271
272 =head1 LICENSE
273
274 This library is free software. You can redistribute it and/or modify 
275 it under the same terms as perl itself.
276
277 =cut