CGI-Emulate-PSGI-Streaming
view release on metacpan or search on metacpan
lib/CGI/Parse/PSGI/Streaming.pm view on Meta::CPAN
package CGI::Parse::PSGI::Streaming;
use strict;
use warnings;
our $VERSION = '1.0.1'; # VERSION
use HTTP::Response;
use CGI::Parse::PSGI::Streaming::Handle;
use SelectSaver;
# ABSTRACT: creates a filehandle that parses CGI output and writes to a PSGI responder
sub parse_cgi_output_streaming_fh {
my ($responder) = @_;
# ugly-ish way to get a ref to a new filehandle
my $output = \do {local *HANDLE};
# state for the callback closure
my $headers; # string, accumulated headers
my $response; # HTTP::Response object with parsed headers
my $writer; # the writer object returned by the responder
## no critic(ProhibitTies)
tie *{$output},'CGI::Parse::PSGI::Streaming::Handle', sub {
# this callback is invoked with whatever bytes were printed to
# the filehandle; it will be called with no argument (or an
# undef) when the filehandle is closed
my ($data) = @_;
# reset the default filehandle to the real STDOUT, just in
# case: it's nice to make sure all the callbacks are invoked
# with the state they expect
my $saver = SelectSaver->new("::STDOUT");
# if we're still parsing the headers
if (!$response) {
if (defined $data) {
$headers .= $data;
}
else { # closed file before the end of headers
$headers = "HTTP/1.1 500 Internal Server Error\x0d\x0a";
}
# still more headers to come, return to the CGI
return unless $headers =~ /\x0d?\x0a\x0d?\x0a/;
# since we may have received the last bytes of the headers
# together with the first bytes of the body, we want to
# make sure that $headers contains only the headers, and
# $data contains only the body (or '')
($headers,$data) =
($headers =~ m{\A(.+?)\x0d?\x0a\x0d?\x0a(.*)\z}sm);
# HTTP::Response wants things formatted like... an HTTP
# response. CGI output is slightly different. Let's cheat.
unless ( $headers =~ /^HTTP/ ) {
$headers = "HTTP/1.1 200 OK\x0d\x0a" . $headers;
}
$response = HTTP::Response->parse($headers);
# RFC 3875 6.2.3
if ($response->header('Location') && !$response->header('Status')) {
$response->header('Status', 302);
}
}
# this is not a "elsif"! we may have the start of the body
# with the same 'print' as the end of the headers, and we want
# to stream out that body already
if ($response) { # we have parsed the headers
if ( $response->code == 500 && !defined($data) ) {
# filehandle closed after a raw 500, synthesise a body
$responder->([
500,
[ 'Content-Type' => 'text/html' ],
[ $response->error_as_HTML ]
]);
return;
}
# we haven't sent the headers to the PSGI backend yet
if (!$writer) {
my $status = $response->header('Status') || 200;
$status =~ s/\s+.*$//; # remove ' OK' in '200 OK'
# PSGI doesn't allow having Status header in the response
$response->remove_header('Status');
# we send the status and headers, we get a writer
# object back
$writer = $responder->([
$status,
( run in 0.754 second using v1.01-cache-2.11-cpan-39bf76dae61 )