1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
|
# You may distribute under the terms of either the GNU General Public License
# or the Artistic License (the same terms as Perl itself)
#
# (C) Paul Evans, 2005-2024 -- leonerd@leonerd.org.uk
package Net::Async::FastCGI::Request 0.26;
use v5.14;
use warnings;
use Carp;
use Net::FastCGI::Constant qw( :type :flag :protocol_status );
use Net::FastCGI::Protocol qw(
parse_params
build_end_request_body
);
# The largest amount of data we can fit in a FastCGI record - MUST NOT
# be greater than 2^16-1
use constant MAXRECORDDATA => 65535;
use Encode qw( find_encoding );
use POSIX qw( EAGAIN );
my $CRLF = "\x0d\x0a";
=head1 NAME
C<Net::Async::FastCGI::Request> - a single active FastCGI request
=head1 SYNOPSIS
use Net::Async::FastCGI;
use IO::Async::Loop;
my $fcgi = Net::Async::FastCGI->new(
on_request => sub {
my ( $fcgi, $req ) = @_;
my $path = $req->param( "PATH_INFO" );
$req->print_stdout( "Status: 200 OK\r\n" .
"Content-type: text/plain\r\n" .
"\r\n" .
"You requested $path" );
$req->finish();
}
);
my $loop = IO::Async::Loop->new();
$loop->add( $fcgi );
$loop->run;
=head1 DESCRIPTION
Instances of this object class represent individual requests received from the
webserver that are currently in-progress, and have not yet been completed.
When given to the controlling program, each request will already have its
parameters (and, on servers without stdin streaming enabled, its STDIN data).
The program can then write response data to the STDOUT stream, messages to the
STDERR stream, and eventually finish it.
This module would not be used directly by a program using
C<Net::Async::FastCGI>, but rather, objects in this class are passed into the
C<on_request> event of the containing C<Net::Async::FastCGI> object.
=cut
sub new
{
my $class = shift;
my %args = @_;
my $rec = $args{rec};
my $self = bless {
conn => $args{conn},
fcgi => $args{fcgi},
reqid => $rec->{reqid},
keepconn => $rec->{flags} & FCGI_KEEP_CONN,
stdin => "",
stdindone => 0,
stream_stdin => $args{stream_stdin},
params => {},
paramsdone => 0,
stdout => "",
stderr => "",
used_stderr => 0,
}, $class;
$self->set_encoding( $args{fcgi}->_default_encoding );
return $self;
}
sub write_record
{
my $self = shift;
my ( $rec ) = @_;
return if $self->is_aborted;
my $content = $rec->{content};
my $contentlen = length( $content );
if( $contentlen > MAXRECORDDATA ) {
warn __PACKAGE__."->write_record() called with content longer than ".MAXRECORDDATA." bytes - truncating";
$content = substr( $content, 0, MAXRECORDDATA );
}
$rec->{reqid} = $self->{reqid} unless defined $rec->{reqid};
my $conn = $self->{conn};
$conn->write_record( $rec, $content );
}
sub incomingrecord
{
my $self = shift;
my ( $rec ) = @_;
my $type = $rec->{type};
if( $type == FCGI_PARAMS ) {
$self->incomingrecord_params( $rec );
}
elsif( $type == FCGI_STDIN ) {
$self->incomingrecord_stdin( $rec );
}
else {
warn "$self just received unknown record type";
}
}
sub _ready_check
{
my $self = shift;
if( $self->{paramsdone} and ( $self->{stdindone} || $self->{stream_stdin} ) ) {
$self->{fcgi}->_request_ready( $self );
}
}
sub incomingrecord_params
{
my $self = shift;
my ( $rec ) = @_;
my $content = $rec->{content};
my $len = $rec->{len};
if( $len ) {
no warnings 'uninitialized';
$self->{paramscontent} .= $content;
return;
}
else {
$self->{params} = parse_params( delete $self->{paramscontent} );
$self->{paramsdone} = 1;
}
$self->_ready_check;
}
sub incomingrecord_stdin
{
my $self = shift;
my ( $rec ) = @_;
my $content = $rec->{content};
my $len = $rec->{len};
if( $len ) {
$self->{stdin} .= $content;
}
else {
$self->{stdindone} = 1;
}
if( $self->{stream_stdin} ) {
$self->_flush_stdin;
}
else {
$self->_ready_check;
}
}
sub _start
{
my $self = shift;
$self->{started} = 1;
$self->_flush_stdin if $self->{stream_stdin} and length $self->{stdin};
}
sub _flush_stdin
{
my $self = shift;
my $on_stdin = $self->{on_stdin_read};
if( !$on_stdin ) {
warn "NaFastCGI::Request incoming STDIN data with on on_stdin_read\n" if $self->{started};
return;
}
{
my $ret = $self->$on_stdin( \$self->{stdin}, $self->{stdindone} );
redo if $ret and length $self->{stdin};
}
}
=head1 METHODS
=cut
=head2 params
$hashref = $req->params;
This method returns a reference to a hash containing a copy of the request
parameters that had been sent by the webserver as part of the request.
=cut
sub params
{
my $self = shift;
my %p = %{$self->{params}};
return \%p;
}
=head2 param
$p = $req->param( $key );
This method returns the value of a single request parameter, or C<undef> if no
such key exists.
=cut
sub param
{
my $self = shift;
my ( $key ) = @_;
return $self->{params}{$key};
}
=head2 method
$method = $req->method;
Returns the value of the C<REQUEST_METHOD> parameter, or C<GET> if there is no
value set for it.
=cut
sub method
{
my $self = shift;
return $self->param( "REQUEST_METHOD" ) || "GET";
}
=head2 script_name
$script_name = $req->script_name;
Returns the value of the C<SCRIPT_NAME> parameter.
=cut
sub script_name
{
my $self = shift;
return $self->param( "SCRIPT_NAME" );
}
=head2 path_info
$path_info = $req->path_info;
Returns the value of the C<PATH_INFO> parameter.
=cut
sub path_info
{
my $self = shift;
return $self->param( "PATH_INFO" );
}
=head2 path
$path = $req->path;
Returns the full request path by reconstructing it from C<script_name> and
C<path_info>.
=cut
sub path
{
my $self = shift;
my $path = join "", grep defined && length,
$self->script_name,
$self->path_info;
$path = "/" if !length $path;
return $path;
}
=head2 query_string
$query_string = $req->query_string;
Returns the value of the C<QUERY_STRING> parameter.
=cut
sub query_string
{
my $self = shift;
return $self->param( "QUERY_STRING" ) || "";
}
=head2 protocol
$protocol = $req->protocol;
Returns the value of the C<SERVER_PROTOCOL> parameter.
=cut
sub protocol
{
my $self = shift;
return $self->param( "SERVER_PROTOCOL" );
}
=head2 set_encoding
$req->set_encoding( $encoding );
Sets the character encoding used by the request's STDIN, STDOUT and STDERR
streams. This method may be called at any time to change the encoding in
effect, which will be used the next time C<read_stdin_line>, C<read_stdin>,
C<print_stdout> or C<print_stderr> are called. This encoding will remain in
effect until changed again. The encoding of a new request is determined by the
C<default_encoding> parameter of the containing C<Net::Async::FastCGI> object.
If the value C<undef> is passed, the encoding will be removed, and the above
methods will work directly on bytes instead of encoded strings.
=cut
sub set_encoding
{
my $self = shift;
my ( $encoding ) = @_;
if( defined $encoding ) {
my $codec = find_encoding( $encoding );
defined $codec or croak "Unrecognised encoding '$encoding'";
$self->{codec} = $codec;
}
else {
undef $self->{codec};
}
}
=head2 read_stdin_line
$line = $req->read_stdin_line;
This method works similarly to the C<< <HANDLE> >> operator. If at least one
line of data is available then it is returned, including the linefeed, and
removed from the buffer. If not, then any remaining partial line is returned
and removed from the buffer. If no data is available any more, then C<undef>
is returned instead.
=cut
sub read_stdin_line
{
my $self = shift;
croak "Cannot call ->read_stdin_line on streaming-stdin requests" if $self->{stream_stdin};
my $codec = $self->{codec};
if( $self->{stdin} =~ s/^(.*[\r\n])// ) {
return $codec ? $codec->decode( $1 ) : $1;
}
elsif( $self->{stdin} =~ s/^(.+)// ) {
return $codec ? $codec->decode( $1 ) : $1;
}
else {
return undef;
}
}
=head2 read_stdin
$data = $req->read_stdin( $size );
This method works similarly to the C<read(HANDLE)> function. It returns the
next block of up to $size bytes from the STDIN buffer. If no data is available
any more, then C<undef> is returned instead. If $size is not defined, then it
will return all the available data.
=cut
sub read_stdin
{
my $self = shift;
croak "Cannot call ->read_stdin on streaming-stdin requests" if $self->{stream_stdin};
my ( $size ) = @_;
return undef unless length $self->{stdin};
$size = length $self->{stdin} unless defined $size;
my $codec = $self->{codec};
# If $size is too big, substr() will cope
my $bytes = substr( $self->{stdin}, 0, $size, "" );
return $codec ? $codec->decode( $bytes ) : $bytes;
}
=head2 set_on_stdin_read
$req->set_on_stdin_read( $on_stdin_read );
$again = $on_stdin_read->( $req, $buffref, $eof );
I<Since version 0.26.>
Only valid on requests on servers with stdin streaming enabled.
This method should be called as part of the C<on_request> event on the server,
to set the callback function to invoke when new data is provided to the stdin
stream for this request.
The callback function is invoked in a similar style to the C<on_read> event
handler of an L<IO::Async::Stream>. It is passed the request itself, along
with a SCALAR reference to the buffer containing the stdin data, and a boolean
indicating if the end of stdin data has been reached.
It should inspect this buffer and remove some prefix of it that it wishes to
consume. Any remaining content will be present on the next call. If it returns
a true value, the callback will be invoked again immediately, to consume more
data. This continues until there is no more data left, or it returns false.
=cut
sub set_on_stdin_read
{
my $self = shift;
croak "Cannot call ->set_on_stdin_read except on streaming-stdin requests" unless $self->{stream_stdin};
( $self->{on_stdin_read} ) = @_;
}
sub _print_stream
{
my $self = shift;
my ( $data, $stream ) = @_;
while( length $data ) {
# Send chunks of up to MAXRECORDDATA bytes at once
my $chunk = substr( $data, 0, MAXRECORDDATA, "" );
$self->write_record( { type => $stream, content => $chunk } );
}
}
sub _flush_streams
{
my $self = shift;
if( length $self->{stdout} ) {
$self->_print_stream( $self->{stdout}, FCGI_STDOUT );
$self->{stdout} = "";
}
elsif( my $cb = $self->{stdout_cb} ) {
$cb->();
}
if( length $self->{stderr} ) {
$self->_print_stream( $self->{stderr}, FCGI_STDERR );
$self->{stderr} = "";
}
}
sub _needs_flush
{
my $self = shift;
return defined $self->{stdout_cb};
}
=head2 print_stdout
$req->print_stdout( $data );
This method appends the given data to the STDOUT stream of the FastCGI
request, sending it to the webserver to be sent to the client.
=cut
sub print_stdout
{
my $self = shift;
my ( $data ) = @_;
my $codec = $self->{codec};
$self->{stdout} .= $codec ? $codec->encode( $data ) : $data;
$self->{conn}->_req_needs_flush( $self );
}
=head2 print_stderr
$req->print_stderr( $data );
This method appends the given data to the STDERR stream of the FastCGI
request, sending it to the webserver.
=cut
sub print_stderr
{
my $self = shift;
my ( $data ) = @_;
my $codec = $self->{codec};
$self->{used_stderr} = 1;
$self->{stderr} .= $codec ? $codec->encode( $data ) : $data;
$self->{conn}->_req_needs_flush( $self );
}
=head2 stream_stdout_then_finish
$req->stream_stdout_then_finish( $readfn, $exitcode );
This method installs a callback for streaming data to the STDOUT stream.
Whenever the output stream is otherwise-idle, the function will be called to
generate some more data to output. When this function returns C<undef> it
indicates the end of the stream, and the request will be finished with the
given exit code.
If this method is used, then care should be taken to ensure that the number of
bytes written to the server matches the number that was claimed in the
C<Content-Length>, if such was provided. This logic should be performed by the
containing application; C<Net::Async::FastCGI> will not track it.
=cut
sub stream_stdout_then_finish
{
my $self = shift;
my ( $readfn, $exitcode ) = @_;
$self->{stdout_cb} = sub {
my $data = $readfn->();
if( defined $data ) {
$self->print_stdout( $data );
}
else {
delete $self->{stdout_cb};
$self->finish( $exitcode );
}
};
$self->{conn}->_req_needs_flush( $self );
}
=head2 stdin
$stdin = $req->stdin;
Returns an IO handle representing the request's STDIN buffer. This may be read
from using the C<read> or C<readline> functions or the C<< <$stdin> >>
operator.
Note that this will be a tied IO handle, it will not be useable directly as an
OS-level filehandle.
=cut
sub stdin
{
my $self = shift;
return Net::Async::FastCGI::Request::TiedHandle->new(
READ => sub {
$_[1] = $self->read_stdin( $_[2] );
return defined $_[1] ? length $_[1] : 0;
},
READLINE => sub {
return $self->read_stdin_line;
},
);
}
=head2 stdout
=head2 stderr
$stdout = $req->stdout;
$stderr = $req->stderr;
Returns an IO handle representing the request's STDOUT or STDERR streams
respectively. These may written to using C<print>, C<printf>, C<say>, etc..
Note that these will be tied IO handles, they will not be useable directly as
an OS-level filehandle.
=cut
sub _stdouterr
{
my $self = shift;
my ( $method ) = @_;
return Net::Async::FastCGI::Request::TiedHandle->new(
WRITE => sub { $self->$method( $_[1] ) },
);
}
sub stdout
{
return shift->_stdouterr( "print_stdout" );
}
sub stderr
{
return shift->_stdouterr( "print_stderr" );
}
=head2 finish
$req->finish( $exitcode );
When the request has been dealt with, this method should be called to indicate
to the webserver that it is finished. After calling this method, no more data
may be appended to the STDOUT stream. At some point after calling this method,
the request object will be removed from the containing C<Net::Async::FastCGI>
object, once all the buffered outbound data has been sent.
If present, C<$exitcode> should indicate the numeric status code to send to
the webserver. If absent, a value of C<0> is presumed.
=cut
sub finish
{
my $self = shift;
my ( $exitcode ) = @_;
return if $self->is_aborted;
$self->_flush_streams;
# Signal the end of STDOUT
$self->write_record( { type => FCGI_STDOUT, content => "" } );
# Signal the end of STDERR if we used it
$self->write_record( { type => FCGI_STDERR, content => "" } ) if $self->{used_stderr};
$self->write_record( { type => FCGI_END_REQUEST,
content => build_end_request_body( $exitcode || 0, FCGI_REQUEST_COMPLETE )
} );
my $conn = $self->{conn};
if( $self->{keepconn} ) {
$conn->_removereq( $self->{reqid} );
}
else {
$conn->close;
}
}
=head2 stdout_with_close
$stdout = $req->stdout_with_close;
Similar to the C<stdout> method, except that when the C<close> method is
called on the returned filehandle, the request will be finished by calling
C<finish>.
=cut
sub stdout_with_close
{
my $self = shift;
return Net::Async::FastCGI::Request::TiedHandle->new(
WRITE => sub { $self->print_stdout( $_[1] ) },
CLOSE => sub { $self->finish( 0 ) },
);
}
sub _abort
{
my $self = shift;
$self->{aborted} = 1;
my $conn = $self->{conn};
$conn->_removereq( $self->{reqid} );
delete $self->{stdout_cb};
}
=head2 is_aborted
$req->is_aborted;
Returns true if the webserver has already closed the control connection. No
further work on this request is necessary, as it will be discarded.
It is not required to call this method; if the request is aborted then any
output will be discarded. It may however be useful to call just before
expensive operations, in case effort can be avoided if it would otherwise be
wasted.
=cut
sub is_aborted
{
my $self = shift;
return $self->{aborted};
}
=head1 HTTP::Request/Response Interface
The following pair of methods form an interface that allows the request to be
used as a source of L<HTTP::Request> objects, responding to them by sending
L<HTTP::Response> objects. This may be useful to fit it in to existing code
that already uses these.
=cut
=head2 as_http_request
$http_req = $req->as_http_request;
Returns a new C<HTTP::Request> object that gives a reasonable approximation to
the request. Because the webserver has translated the original HTTP request
into FastCGI parameters, this may not be a perfect recreation of the request
as received by the webserver.
=cut
sub as_http_request
{
my $self = shift;
require HTTP::Request;
my $params = $self->params;
my $authority =
( $params->{HTTP_HOST} || $params->{SERVER_NAME} || "" ) . ":" .
( $params->{SERVER_PORT} || "80" );
my $path = $self->path;
my $query_string = $self->query_string;
$path .= "?$query_string" if length $query_string;
my $uri = URI->new( "http://$authority$path" )->canonical;
my @headers;
# Content-Type and Content-Length come specially
push @headers, "Content-Type" => $params->{CONTENT_TYPE}
if exists $params->{CONTENT_TYPE};
push @headers, "Content-Length" => $params->{CONTENT_LENGTH}
if exists $params->{CONTENT_LENGTH};
# Pull all the HTTP_FOO parameters as headers. These will be in all-caps
# and use _ for word separators, but HTTP::Headers can cope
foreach ( keys %$params ) {
m/^HTTP_(.*)$/ and push @headers, $1 => $params->{$_};
}
my $content = $self->{stdin};
my $req = HTTP::Request->new( $self->method, $uri, \@headers, $content );
$req->protocol( $self->protocol );
return $req;
}
=head2 send_http_response
$req->send_http_response( $resp );
Sends the given C<HTTP::Response> object as the response to this request. The
status, headers and content are all written out to the request's STDOUT stream
and then the request is finished with 0 as the exit code.
=cut
sub send_http_response
{
my $self = shift;
my ( $resp ) = @_;
# (Fast)CGI suggests this is the way to report the status
$resp->header( Status => $resp->code );
my $topline = $resp->protocol . " " . $resp->status_line;
$self->print_stdout( $topline . $CRLF );
$self->print_stdout( $resp->headers_as_string( $CRLF ) );
$self->print_stdout( $CRLF );
$self->print_stdout( $resp->content );
$self->finish( 0 );
}
package # hide from CPAN
Net::Async::FastCGI::Request::TiedHandle;
use base qw( Tie::Handle );
use Symbol qw( gensym );
sub new
{
my $class = shift;
my $handle = gensym;
tie *$handle, $class, @_;
return $handle;
}
sub TIEHANDLE
{
my $class = shift;
return bless { @_ }, $class;
}
sub CLOSE { shift->{CLOSE}->( @_ ) }
sub READ { shift->{READ}->( @_ ) }
sub READLINE { shift->{READLINE}->( @_ ) }
sub WRITE { shift->{WRITE}->( @_ ) }
=head1 EXAMPLES
=head2 Streaming A File
To serve contents of files on disk, it may be more efficient to use
C<stream_stdout_then_finish>:
use Net::Async::FastCGI;
use IO::Async::Loop;
my $fcgi = Net::Async::FastCGI->new(
on_request => sub {
my ( $fcgi, $req ) = @_;
open( my $file, "<", "/path/to/file" );
$req->print_stdout( "Status: 200 OK\r\n" .
"Content-type: application/octet-stream\r\n" .
"\r\n" );
$req->stream_stdout_then_finish(
sub { read( $file, my $buffer, 8192 ) or return undef; return $buffer },
0
);
}
my $loop = IO::Async::Loop->new();
$loop->add( $fcgi );
$loop->run;
It may be more efficient again to instead use the C<X-Sendfile> feature of
certain webservers, which allows the webserver itself to serve the file
efficiently. See your webserver's documentation for more detail.
=head1 AUTHOR
Paul Evans <leonerd@leonerd.org.uk>
=cut
0x55AA;
|