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