More performance improvements. Added undocumented "headers_only" and "sync" options...
[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
26e3d92b 8use Carp qw[croak];
9use HTTP::Response qw[];
10use IO::Handle qw[];
11use IO::File qw[SEEK_SET];
12use Symbol qw[];
b2e1304d 13
26e3d92b 14__PACKAGE__->mk_accessors(qw[environment request is_restored is_setuped is_prepared should_dup should_restore should_rewind stdin stdout stderr]);
b2e1304d 15
26e3d92b 16our $VERSION = 0.6_01;
b2e1304d 17
18sub new {
26e3d92b 19 my $class = ref $_[0] ? ref shift : shift;
20 my $params = {};
4e0afe7d 21
26e3d92b 22 if ( @_ % 2 == 0 ) {
23 $params = { @_ };
24 }
25 else {
26 $params = { request => shift, environment => { @_ } };
27 }
28
29 return bless( {}, $class )->initialize($params);
30}
31
32sub initialize {
33 my ( $self, $params ) = @_;
34
35 if ( exists $params->{request} ) {
36 $self->request( $params->{request} );
37 }
38 else {
39 croak("Mandatory parameter 'request' is missing.");
2d51e42f 40 }
4e0afe7d 41
26e3d92b 42 if ( exists $params->{environment} ) {
43 $self->environment( $params->{environment} );
44 }
45 else {
46 $self->environment( {} );
47 }
48
49 if ( exists $params->{stdin} ) {
50 $self->stdin( $params->{stdin} );
51 }
52 else {
53 $self->stdin( IO::File->new_tmpfile );
54 }
55
56 if ( exists $params->{stdout} ) {
57 $self->stdout( $params->{stdout} );
58 }
59 else {
60 $self->stdout( IO::File->new_tmpfile );
61 }
62
63 if ( exists $params->{stderr} ) {
64 $self->stderr( $params->{stderr} );
65 }
4d6e304a 66
26e3d92b 67 if ( exists $params->{dup} ) {
68 $self->should_dup( $params->{dup} ? 1 : 0 );
69 }
70 else {
71 $self->should_dup(1);
4d6e304a 72 }
26e3d92b 73
74 if ( exists $params->{restore} ) {
75 $self->should_restore( $params->{restore} ? 1 : 0 );
76 }
77 else {
78 $self->should_restore(1);
79 }
80
81 if ( exists $params->{rewind} ) {
82 $self->should_rewind( $params->{rewind} ? 1 : 0 );
83 }
84 else {
85 $self->should_rewind(1);
86 }
87
88 $self->prepare;
89
90 return $self;
91}
92
93*enviroment = \&environment;
94
95sub has_stdin { return defined $_[0]->stdin }
96sub has_stdout { return defined $_[0]->stdout }
97sub has_stderr { return defined $_[0]->stderr }
98
99sub prepare {
100 my $self = shift;
101
102 my $environment = $self->environment;
103 my $request = $self->request;
b2e1304d 104
30efa07d 105 my $host = $request->header('Host');
106 my $uri = $request->uri->clone;
26e3d92b 107
30efa07d 108 $uri->scheme('http') unless $uri->scheme;
109 $uri->host('localhost') unless $uri->host;
110 $uri->port(80) unless $uri->port;
a3875fc6 111 $uri->host_port($host) unless !$host || ( $host eq $uri->host_port );
4e0afe7d 112
ca38286c 113 $uri = $uri->canonical;
30efa07d 114
26e3d92b 115 my %cgi = (
b2e1304d 116 GATEWAY_INTERFACE => 'CGI/1.1',
30efa07d 117 HTTP_HOST => $uri->host_port,
a3875fc6 118 HTTPS => ( $uri->scheme eq 'https' ) ? 'ON' : 'OFF', # not in RFC 3875
119 PATH_INFO => $uri->path,
120 QUERY_STRING => $uri->query || '',
121 SCRIPT_NAME => '/',
122 SERVER_NAME => $uri->host,
123 SERVER_PORT => $uri->port,
124 SERVER_PROTOCOL => $request->protocol || 'HTTP/1.1',
125 SERVER_SOFTWARE => "HTTP-Request-AsCGI/$VERSION",
126 REMOTE_ADDR => '127.0.0.1',
127 REMOTE_HOST => 'localhost',
128 REMOTE_PORT => int( rand(64000) + 1000 ), # not in RFC 3875
129 REQUEST_URI => $uri->path_query, # not in RFC 3875
26e3d92b 130 REQUEST_METHOD => $request->method
131 );
b2e1304d 132
26e3d92b 133 foreach my $key ( keys %cgi ) {
134
135 unless ( exists $environment->{ $key } ) {
136 $environment->{ $key } = $cgi{ $key };
137 }
138 }
139
140 foreach my $field ( $self->request->headers->header_field_names ) {
b2e1304d 141
ca38286c 142 my $key = uc("HTTP_$field");
2aaf55bc 143 $key =~ tr/-/_/;
ca38286c 144 $key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
b2e1304d 145
26e3d92b 146 unless ( exists $environment->{ $key } ) {
147 $environment->{ $key } = $self->request->headers->header($field);
b2e1304d 148 }
149 }
150
26e3d92b 151 unless ( $environment->{SCRIPT_NAME} eq '/' && $environment->{PATH_INFO} ) {
152 $environment->{PATH_INFO} =~ s/^\Q$environment->{SCRIPT_NAME}\E/\//;
153 $environment->{PATH_INFO} =~ s/^\/+/\//;
154 }
155
156 $self->is_prepared(1);
157}
158
159sub setup {
160 my $self = shift;
161
162 $self->setup_stdin;
163 $self->setup_stdout;
164 $self->setup_stderr;
165 $self->setup_environment;
166
167 if ( $INC{'CGI.pm'} ) {
168 CGI::initialize_globals();
ca38286c 169 }
170
26e3d92b 171 $self->is_setuped(1);
ca38286c 172
173 return $self;
b2e1304d 174}
175
26e3d92b 176sub setup_environment {
b2e1304d 177 my $self = shift;
178
26e3d92b 179 no warnings 'uninitialized';
180
181 if ( $self->should_restore ) {
182 $self->{restore}->{environment} = { %ENV };
183 }
184
185 %ENV = %{ $self->environment };
186}
187
188sub setup_stdin {
189 my $self = shift;
190
191 if ( $self->has_stdin ) {
192
193 binmode( $self->stdin );
194
195 if ( $self->request->content_length ) {
b2e1304d 196
26e3d92b 197 syswrite( $self->stdin, $self->request->content )
198 or croak("Couldn't write request content to stdin handle: '$!'");
b2e1304d 199
26e3d92b 200 sysseek( $self->stdin, 0, SEEK_SET )
201 or croak("Couldn't seek stdin handle: '$!'");
202 }
203
204 if ( $self->should_dup ) {
205
206 if ( $self->should_restore ) {
207
208 open( my $stdin, '<&STDIN' )
209 or croak("Couldn't dup STDIN: '$!'");
210
211 $self->{restore}->{stdin} = $stdin;
212 }
213
214 STDIN->fdopen( $self->stdin, '<' )
215 or croak("Couldn't redirect STDIN: '$!'");
216 }
217 else {
218
219 my $stdin = Symbol::qualify_to_ref('STDIN');
220
221 if ( $self->should_restore ) {
222
223 $self->{restore}->{stdin} = *$stdin;
224 $self->{restore}->{stdin_ref} = \*$stdin;
225 }
b2e1304d 226
26e3d92b 227 *{ $stdin } = $self->stdin;
228 }
b2e1304d 229
26e3d92b 230 binmode( STDIN );
b2e1304d 231 }
26e3d92b 232}
233
234sub setup_stdout {
235 my $self = shift;
236
237 if ( $self->has_stdout ) {
b2e1304d 238
26e3d92b 239 if ( $self->should_dup ) {
ca38286c 240
26e3d92b 241 if ( $self->should_restore ) {
242
243 open( my $stdout, '>&STDOUT' )
244 or croak("Couldn't dup STDOUT: '$!'");
245
246 $self->{restore}->{stdout} = $stdout;
247 }
248
249 STDOUT->fdopen( $self->stdout, '>' )
250 or croak("Couldn't redirect STDOUT: '$!'");
251 }
252 else {
30efa07d 253
26e3d92b 254 my $stdout = Symbol::qualify_to_ref('STDOUT');
090cc060 255
26e3d92b 256 if ( $self->should_restore ) {
441eeb04 257
26e3d92b 258 $self->{restore}->{stdout} = *$stdout;
259 $self->{restore}->{stdout_ref} = \*$stdout;
260 }
76391122 261
26e3d92b 262 *{ $stdout } = $self->stdout;
263 }
76391122 264
a3875fc6 265 binmode( $self->stdout );
266 binmode( STDOUT);
267 }
26e3d92b 268}
090cc060 269
26e3d92b 270sub setup_stderr {
271 my $self = shift;
30efa07d 272
26e3d92b 273 if ( $self->has_stderr ) {
090cc060 274
26e3d92b 275 if ( $self->should_dup ) {
441eeb04 276
26e3d92b 277 if ( $self->should_restore ) {
090cc060 278
26e3d92b 279 open( my $stderr, '>&STDERR' )
280 or croak("Couldn't dup STDERR: '$!'");
4e0afe7d 281
26e3d92b 282 $self->{restore}->{stderr} = $stderr;
283 }
b2e1304d 284
26e3d92b 285 STDERR->fdopen( $self->stderr, '>' )
286 or croak("Couldn't redirect STDERR: '$!'");
287 }
288 else {
b2e1304d 289
26e3d92b 290 my $stderr = Symbol::qualify_to_ref('STDERR');
291
292 if ( $self->should_restore ) {
293
294 $self->{restore}->{stderr} = *$stderr;
295 $self->{restore}->{stderr_ref} = \*$stderr;
296 }
297
298 *{ $stderr } = $self->stderr;
299 }
300
301 binmode( $self->stderr );
302 binmode( STDERR );
303 }
b2e1304d 304}
305
780060e5 306sub response {
4d6e304a 307 my $self = shift;
308 my %params = ( headers_only => 0, sync => 0, @_ );
780060e5 309
74fbb9dd 310 return undef unless $self->stdout;
780060e5 311
30efa07d 312 seek( $self->stdout, 0, SEEK_SET )
26e3d92b 313 or croak("Couldn't seek stdout handle: '$!'");
780060e5 314
4e0afe7d 315 my $headers;
decf17dc 316 while ( my $line = $self->stdout->getline ) {
4e0afe7d 317 $headers .= $line;
318 last if $headers =~ /\x0d?\x0a\x0d?\x0a$/;
780060e5 319 }
26e3d92b 320
4e0afe7d 321 unless ( defined $headers ) {
322 $headers = "HTTP/1.1 500 Internal Server Error\x0d\x0a";
decf17dc 323 }
324
4e0afe7d 325 unless ( $headers =~ /^HTTP/ ) {
326 $headers = "HTTP/1.1 200 OK\x0d\x0a" . $headers;
780060e5 327 }
328
4e0afe7d 329 my $response = HTTP::Response->parse($headers);
330 $response->date( time() ) unless $response->date;
decf17dc 331
4e0afe7d 332 my $message = $response->message;
333 my $status = $response->header('Status');
decf17dc 334
4e0afe7d 335 if ( $message && $message =~ /^(.+)\x0d$/ ) {
336 $response->message($1);
337 }
decf17dc 338
4e0afe7d 339 if ( $status && $status =~ /^(\d\d\d)\s?(.+)?$/ ) {
decf17dc 340
4e0afe7d 341 my $code = $1;
342 my $message = $2 || HTTP::Status::status_message($code);
780060e5 343
780060e5 344 $response->code($code);
4e0afe7d 345 $response->message($message);
780060e5 346 }
26e3d92b 347
b81054c2 348 my $length = ( stat( $self->stdout ) )[7] - tell( $self->stdout );
780060e5 349
b81054c2 350 if ( $response->code == 500 && !$length ) {
4e0afe7d 351
352 $response->content( $response->error_as_HTML );
353 $response->content_type('text/html');
354
355 return $response;
356 }
780060e5 357
4d6e304a 358 if ( $params{headers_only} ) {
4e0afe7d 359
4d6e304a 360 if ( $params{sync} ) {
4e0afe7d 361
4d6e304a 362 my $position = tell( $self->stdout )
363 or croak("Couldn't get file position from stdout handle: '$!'");
4e0afe7d 364
4d6e304a 365 sysseek( $self->stdout, $position, SEEK_SET )
366 or croak("Couldn't seek stdout handle: '$!'");
367 }
4e0afe7d 368
4d6e304a 369 return $response;
780060e5 370 }
4e0afe7d 371
4d6e304a 372 my $content = undef;
373 my $content_length = 0;
374
375 while () {
376
377 my $r = $self->stdout->read( $content, 4096, $content_length );
4e0afe7d 378
4d6e304a 379 if ( defined $r ) {
380
381 $content_length += $r;
382
383 last unless $r;
384 }
385 else {
386 croak("Couldn't read from stdin handle: '$!'");
780060e5 387 }
4d6e304a 388 }
389
390 if ( $content_length ) {
391
392 $response->content_ref(\$content);
4e0afe7d 393
4d6e304a 394 if ( !$response->content_length ) {
395 $response->content_length($content_length);
decf17dc 396 }
780060e5 397 }
398
780060e5 399 return $response;
400}
401
b2e1304d 402sub restore {
403 my $self = shift;
4e0afe7d 404
26e3d92b 405 if ( $self->should_restore ) {
406
407 $self->restore_environment;
408 $self->restore_stdin;
409 $self->restore_stdout;
410 $self->restore_stderr;
411
412 $self->{restore} = {};
413
414 $self->is_restored(1);
cef1c068 415 }
b2e1304d 416
26e3d92b 417 return $self;
418}
419
420sub restore_environment {
421 my $self = shift;
422
423 no warnings 'uninitialized';
b2e1304d 424
26e3d92b 425 %ENV = %{ $self->{restore}->{environment} };
426}
427
428sub restore_stdin {
429 my $self = shift;
12852959 430
26e3d92b 431 if ( $self->has_stdin ) {
30efa07d 432
26e3d92b 433 my $stdin = $self->{restore}->{stdin};
30efa07d 434
26e3d92b 435 if ( $self->should_dup ) {
436
437 STDIN->fdopen( $stdin, '<' )
438 or croak("Couldn't restore STDIN: '$!'");
439 }
440 else {
441
442 my $stdin_ref = $self->{restore}->{stdin_ref};
443
444 *{ $stdin_ref } = $stdin;
445 }
090cc060 446
26e3d92b 447 if ( $self->should_rewind ) {
448
449 seek( $self->stdin, 0, SEEK_SET )
450 or croak("Couldn't seek stdin handle: '$!'");
451 }
a3875fc6 452 }
26e3d92b 453}
454
455sub restore_stdout {
456 my $self = shift;
457
458 if ( $self->has_stdout ) {
459
460 my $stdout = $self->{restore}->{stdout};
461
462 if ( $self->should_dup ) {
463
464 STDOUT->flush
465 or croak("Couldn't flush STDOUT: '$!'");
466
467 STDOUT->fdopen( $stdout, '>' )
468 or croak("Couldn't restore STDOUT: '$!'");
469 }
470 else {
12852959 471
26e3d92b 472 my $stdout_ref = $self->{restore}->{stdout_ref};
30efa07d 473
26e3d92b 474 *{ $stdout_ref } = $stdout;
475 }
30efa07d 476
26e3d92b 477 if ( $self->should_rewind ) {
090cc060 478
26e3d92b 479 seek( $self->stdout, 0, SEEK_SET )
480 or croak("Couldn't seek stdout handle: '$!'");
481 }
6f5fb9a7 482 }
26e3d92b 483}
12852959 484
26e3d92b 485sub restore_stderr {
486 my $self = shift;
090cc060 487
26e3d92b 488 if ( $self->has_stderr ) {
489
490 my $stderr = $self->{restore}->{stderr};
491
492 if ( $self->should_dup ) {
493
494 STDERR->flush
495 or croak("Couldn't flush STDERR: '$!'");
496
497 STDERR->fdopen( $stderr, '>' )
498 or croak("Couldn't restore STDERR: '$!'");
499 }
500 else {
501
502 my $stderr_ref = $self->{restore}->{stderr_ref};
503
504 *{ $stderr_ref } = $stderr;
505 }
506
507 if ( $self->should_rewind ) {
508
509 seek( $self->stderr, 0, SEEK_SET )
510 or croak("Couldn't seek stderr handle: '$!'");
511 }
512 }
b2e1304d 513}
514
515sub DESTROY {
516 my $self = shift;
26e3d92b 517
518 if ( $self->should_restore ) {
519
520 if ( $self->is_setuped && !$self->is_restored ) {
521 $self->restore;
522 }
523 }
b2e1304d 524}
525
5261;
527
528__END__
529
530=head1 NAME
531
26e3d92b 532HTTP::Request::AsCGI - Setup a CGI environment from a HTTP::Request
b2e1304d 533
534=head1 SYNOPSIS
535
bd7813ac 536 use CGI;
537 use HTTP::Request;
538 use HTTP::Request::AsCGI;
26e3d92b 539
bd7813ac 540 my $request = HTTP::Request->new( GET => 'http://www.host.com/' );
541 my $stdout;
26e3d92b 542
bd7813ac 543 {
544 my $c = HTTP::Request::AsCGI->new($request)->setup;
545 my $q = CGI->new;
26e3d92b 546
bd7813ac 547 print $q->header,
548 $q->start_html('Hello World'),
549 $q->h1('Hello World'),
550 $q->end_html;
26e3d92b 551
bd7813ac 552 $stdout = $c->stdout;
26e3d92b 553
554 # environment and descriptors will automatically be restored
2d51e42f 555 # when $c is destructed.
bd7813ac 556 }
26e3d92b 557
bd7813ac 558 while ( my $line = $stdout->getline ) {
559 print $line;
560 }
26e3d92b 561
b2e1304d 562=head1 DESCRIPTION
563
26e3d92b 564Provides a convinient way of setting up an CGI environment from a HTTP::Request.
2d51e42f 565
b2e1304d 566=head1 METHODS
567
26e3d92b 568=over 4
b2e1304d 569
2d51e42f 570=item new ( $request [, key => value ] )
571
572Contructor, first argument must be a instance of HTTP::Request
ca38286c 573followed by optional pairs of environment key and value.
b2e1304d 574
26e3d92b 575=item environment
bd7813ac 576
26e3d92b 577Returns a hashref containing the environment that will be used in setup.
2d51e42f 578Changing the hashref after setup has been called will have no effect.
579
b2e1304d 580=item setup
581
2d51e42f 582Setups the environment and descriptors.
583
b2e1304d 584=item restore
585
26e3d92b 586Restores the environment and descriptors. Can only be called after setup.
2d51e42f 587
b2e1304d 588=item request
589
2d51e42f 590Returns the request given to constructor.
591
780060e5 592=item response
593
2d51e42f 594Returns a HTTP::Response. Can only be called after restore.
595
b2e1304d 596=item stdin
597
2d51e42f 598Accessor for handle that will be used for STDIN, must be a real seekable
599handle with an file descriptor. Defaults to a tempoary IO::File instance.
600
b2e1304d 601=item stdout
602
2d51e42f 603Accessor for handle that will be used for STDOUT, must be a real seekable
604handle with an file descriptor. Defaults to a tempoary IO::File instance.
605
b2e1304d 606=item stderr
607
2d51e42f 608Accessor for handle that will be used for STDERR, must be a real seekable
609handle with an file descriptor.
b2e1304d 610
2d51e42f 611=back
b2e1304d 612
74fbb9dd 613=head1 SEE ALSO
614
615=over 4
616
617=item examples directory in this distribution.
618
619=item L<WWW::Mechanize::CGI>
620
621=item L<Test::WWW::Mechanize::CGI>
622
623=back
624
2d51e42f 625=head1 THANKS TO
17b370b0 626
627Thomas L. Shinnick for his valuable win32 testing.
628
b2e1304d 629=head1 AUTHOR
630
631Christian Hansen, C<ch@ngmedia.com>
632
633=head1 LICENSE
634
26e3d92b 635This library is free software. You can redistribute it and/or modify
b2e1304d 636it under the same terms as perl itself.
637
638=cut