Added a forking test
[catagits/HTTP-Request-AsCGI.git] / lib / HTTP / Request / AsCGI.pm
CommitLineData
b2e1304d 1package HTTP::Request::AsCGI;
2
3use strict;
4use warnings;
090cc060 5use bytes;
b2e1304d 6use base 'Class::Accessor::Fast';
7
8use Carp;
30efa07d 9use IO::Handle;
bd7813ac 10use IO::File;
b2e1304d 11
090cc060 12__PACKAGE__->mk_accessors(qw[ enviroment request stdin stdout stderr ]);
b2e1304d 13
ca38286c 14our $VERSION = 0.2;
b2e1304d 15
16sub new {
17 my $class = shift;
18 my $request = shift;
2d51e42f 19
20 unless ( @_ % 2 == 0 && eval { $request->isa('HTTP::Request') } ) {
21 croak(qq/usage: $class->new( \$request [, key => value] )/);
22 }
ca38286c 23
24 my $self = $class->SUPER::new( { restored => 0, setuped => 0 } );
25 $self->request($request);
26 $self->stdin( IO::File->new_tmpfile );
27 $self->stdout( IO::File->new_tmpfile );
b2e1304d 28
30efa07d 29 my $host = $request->header('Host');
30 my $uri = $request->uri->clone;
31 $uri->scheme('http') unless $uri->scheme;
32 $uri->host('localhost') unless $uri->host;
33 $uri->port(80) unless $uri->port;
34 $uri->host_port($host) unless !$host || ( $host eq $uri->host_port );
ca38286c 35
36 $uri = $uri->canonical;
30efa07d 37
ca38286c 38 my $enviroment = {
b2e1304d 39 GATEWAY_INTERFACE => 'CGI/1.1',
30efa07d 40 HTTP_HOST => $uri->host_port,
41 HTTPS => ( $uri->scheme eq 'https' ) ? 'ON' : 'OFF', # not in RFC 3875
42 PATH_INFO => $uri->path,
43 QUERY_STRING => $uri->query || '',
c1e07bf1 44 SCRIPT_NAME => '/',
30efa07d 45 SERVER_NAME => $uri->host,
46 SERVER_PORT => $uri->port,
b2e1304d 47 SERVER_PROTOCOL => $request->protocol || 'HTTP/1.1',
30efa07d 48 SERVER_SOFTWARE => "HTTP-Request-AsCGI/$VERSION",
b2e1304d 49 REMOTE_ADDR => '127.0.0.1',
50 REMOTE_HOST => 'localhost',
30efa07d 51 REMOTE_PORT => int( rand(64000) + 1000 ), # not in RFC 3875
ca38286c 52 REQUEST_URI => $uri->path_query, # not in RFC 3875
b2e1304d 53 REQUEST_METHOD => $request->method,
54 @_
55 };
56
57 foreach my $field ( $request->headers->header_field_names ) {
58
ca38286c 59 my $key = uc("HTTP_$field");
2aaf55bc 60 $key =~ tr/-/_/;
ca38286c 61 $key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
b2e1304d 62
ca38286c 63 unless ( exists $enviroment->{$key} ) {
64 $enviroment->{$key} = $request->headers->header($field);
b2e1304d 65 }
66 }
67
ca38286c 68 unless ( $enviroment->{SCRIPT_NAME} eq '/' && $enviroment->{PATH_INFO} ) {
69 $enviroment->{PATH_INFO} =~ s/^\Q$enviroment->{SCRIPT_NAME}\E/\//;
70 $enviroment->{PATH_INFO} =~ s/^\/+/\//;
71 }
72
73 $self->enviroment($enviroment);
74
75 return $self;
b2e1304d 76}
77
78sub setup {
79 my $self = shift;
80
090cc060 81 $self->{restore}->{enviroment} = {%ENV};
b2e1304d 82
17b370b0 83 binmode( $self->stdin );
b2e1304d 84
85 if ( $self->request->content_length ) {
86
30efa07d 87 syswrite( $self->stdin, $self->request->content )
780060e5 88 or croak("Can't write request content to stdin handle: $!");
b2e1304d 89
30efa07d 90 sysseek( $self->stdin, 0, SEEK_SET )
780060e5 91 or croak("Can't seek stdin handle: $!");
b2e1304d 92 }
93
ca38286c 94 open( $self->{restore}->{stdin}, '>&', STDIN->fileno )
95 or croak("Can't dup stdin: $!");
96
97 open( STDIN, '<&=', $self->stdin->fileno )
98 or croak("Can't open stdin: $!");
99
100 binmode( STDIN );
101
090cc060 102 if ( $self->stdout ) {
30efa07d 103
090cc060 104 open( $self->{restore}->{stdout}, '>&', STDOUT->fileno )
105 or croak("Can't dup stdout: $!");
106
107 open( STDOUT, '>&=', $self->stdout->fileno )
108 or croak("Can't open stdout: $!");
441eeb04 109
17b370b0 110 binmode( $self->stdout );
111 binmode( STDOUT);
090cc060 112 }
113
114 if ( $self->stderr ) {
30efa07d 115
090cc060 116 open( $self->{restore}->{stderr}, '>&', STDERR->fileno )
117 or croak("Can't dup stderr: $!");
118
119 open( STDERR, '>&=', $self->stderr->fileno )
120 or croak("Can't open stderr: $!");
441eeb04 121
17b370b0 122 binmode( $self->stderr );
123 binmode( STDERR );
090cc060 124 }
125
3cdea3c7 126 {
127 no warnings 'uninitialized';
128 %ENV = %{ $self->enviroment };
129 }
30efa07d 130
131 if ( $INC{'CGI.pm'} ) {
132 CGI::initialize_globals();
133 }
b2e1304d 134
6f5fb9a7 135 $self->{setuped}++;
b2e1304d 136
137 return $self;
138}
139
780060e5 140sub response {
141 my ( $self, $callback ) = @_;
142
74fbb9dd 143 return undef unless $self->stdout;
780060e5 144
145 require HTTP::Response;
146
30efa07d 147 seek( $self->stdout, 0, SEEK_SET )
148 or croak("Can't seek stdout handle: $!");
780060e5 149
decf17dc 150 my $message;
151 while ( my $line = $self->stdout->getline ) {
780060e5 152 $message .= $line;
decf17dc 153 last if $message =~ /\x0d?\x0a\x0d?\x0a$/;
780060e5 154 }
155
156 unless ( $message =~ /^HTTP/ ) {
decf17dc 157 $message = "HTTP/1.1 200 OK\x0d\x0a" . $message;
158 }
159
160 my $response = HTTP::Response->new;
161 my @headers = split( /\x0d?\x0a/, $message );
162 my $status = shift(@headers);
163
164 unless ( $status =~ s/^(HTTP\/\d\.\d) (\d{3}) (.*)$// ) {
165 croak( "Invalid Status-Line: '$status'" );
780060e5 166 }
167
decf17dc 168 $response->protocol($1);
169 $response->code($2);
170 $response->message($3);
171
172 my $token = qr/[^][\x00-\x1f\x7f()<>@,;:\\"\/?={} \t]+/;
173
174 foreach my $header (@headers) {
175
176 unless( $header =~ s/^($token):[\t ]*// ) {
177 croak( "Invalid header field name : '$header'" );
178 }
179
180 $response->push_header( $1 => $header );
181 }
780060e5 182
183 if ( my $code = $response->header('Status') ) {
184 $response->code($code);
decf17dc 185 $response->message( HTTP::Status::status_message($code) );
780060e5 186 }
187
780060e5 188 $response->headers->date( time() );
189
090cc060 190 if ($callback) {
780060e5 191 $response->content( sub {
192 if ( $self->stdout->read( my $buffer, 4096 ) ) {
193 return $buffer;
194 }
195 return undef;
090cc060 196 });
780060e5 197 }
198 else {
199 my $length = 0;
200 while ( $self->stdout->read( my $buffer, 4096 ) ) {
201 $length += length($buffer);
202 $response->add_content($buffer);
203 }
decf17dc 204
205 if ( $length && !$response->content_length ) {
206 $response->content_length($length);
207 }
780060e5 208 }
209
780060e5 210 return $response;
211}
212
b2e1304d 213sub restore {
214 my $self = shift;
215
216 %ENV = %{ $self->{restore}->{enviroment} };
217
218 open( STDIN, '>&', $self->{restore}->{stdin} )
219 or croak("Can't restore stdin: $!");
220
30efa07d 221 sysseek( $self->stdin, 0, SEEK_SET )
780060e5 222 or croak("Can't seek stdin: $!");
12852959 223
090cc060 224 if ( $self->{restore}->{stdout} ) {
30efa07d 225
226 STDOUT->flush
227 or croak("Can't flush stdout: $!");
228
090cc060 229 open( STDOUT, '>&', $self->{restore}->{stdout} )
230 or croak("Can't restore stdout: $!");
231
30efa07d 232 sysseek( $self->stdout, 0, SEEK_SET )
6f5fb9a7 233 or croak("Can't seek stdout: $!");
234 }
12852959 235
090cc060 236 if ( $self->{restore}->{stderr} ) {
30efa07d 237
238 STDERR->flush
239 or croak("Can't flush stderr: $!");
240
090cc060 241 open( STDERR, '>&', $self->{restore}->{stderr} )
242 or croak("Can't restore stderr: $!");
243
30efa07d 244 sysseek( $self->stderr, 0, SEEK_SET )
6f5fb9a7 245 or croak("Can't seek stderr: $!");
246 }
12852959 247
b2e1304d 248 $self->{restored}++;
090cc060 249
250 return $self;
b2e1304d 251}
252
253sub DESTROY {
254 my $self = shift;
6f5fb9a7 255 $self->restore if $self->{setuped} && !$self->{restored};
b2e1304d 256}
257
2581;
259
260__END__
261
262=head1 NAME
263
bd7813ac 264HTTP::Request::AsCGI - Setup a CGI enviroment from a HTTP::Request
b2e1304d 265
266=head1 SYNOPSIS
267
bd7813ac 268 use CGI;
269 use HTTP::Request;
270 use HTTP::Request::AsCGI;
271
272 my $request = HTTP::Request->new( GET => 'http://www.host.com/' );
273 my $stdout;
274
275 {
276 my $c = HTTP::Request::AsCGI->new($request)->setup;
277 my $q = CGI->new;
278
279 print $q->header,
280 $q->start_html('Hello World'),
281 $q->h1('Hello World'),
282 $q->end_html;
283
284 $stdout = $c->stdout;
285
2d51e42f 286 # enviroment and descriptors will automatically be restored
287 # when $c is destructed.
bd7813ac 288 }
289
bd7813ac 290 while ( my $line = $stdout->getline ) {
291 print $line;
292 }
293
b2e1304d 294=head1 DESCRIPTION
295
2d51e42f 296Provides a convinient way of setting up an CGI enviroment from a HTTP::Request.
297
b2e1304d 298=head1 METHODS
299
300=over 4
301
2d51e42f 302=item new ( $request [, key => value ] )
303
304Contructor, first argument must be a instance of HTTP::Request
ca38286c 305followed by optional pairs of environment key and value.
b2e1304d 306
bd7813ac 307=item enviroment
308
2d51e42f 309Returns a hashref containing the environment that will be used in setup.
310Changing the hashref after setup has been called will have no effect.
311
b2e1304d 312=item setup
313
2d51e42f 314Setups the environment and descriptors.
315
b2e1304d 316=item restore
317
2d51e42f 318Restores the enviroment and descriptors. Can only be called after setup.
319
b2e1304d 320=item request
321
2d51e42f 322Returns the request given to constructor.
323
780060e5 324=item response
325
2d51e42f 326Returns a HTTP::Response. Can only be called after restore.
327
b2e1304d 328=item stdin
329
2d51e42f 330Accessor for handle that will be used for STDIN, must be a real seekable
331handle with an file descriptor. Defaults to a tempoary IO::File instance.
332
b2e1304d 333=item stdout
334
2d51e42f 335Accessor for handle that will be used for STDOUT, must be a real seekable
336handle with an file descriptor. Defaults to a tempoary IO::File instance.
337
b2e1304d 338=item stderr
339
2d51e42f 340Accessor for handle that will be used for STDERR, must be a real seekable
341handle with an file descriptor.
b2e1304d 342
2d51e42f 343=back
b2e1304d 344
74fbb9dd 345=head1 SEE ALSO
346
347=over 4
348
349=item examples directory in this distribution.
350
351=item L<WWW::Mechanize::CGI>
352
353=item L<Test::WWW::Mechanize::CGI>
354
355=back
356
2d51e42f 357=head1 THANKS TO
17b370b0 358
359Thomas L. Shinnick for his valuable win32 testing.
360
b2e1304d 361=head1 AUTHOR
362
363Christian Hansen, C<ch@ngmedia.com>
364
365=head1 LICENSE
366
367This library is free software. You can redistribute it and/or modify
368it under the same terms as perl itself.
369
370=cut