view release on metacpan or search on metacpan
examples/fatpacked.plackup view on Meta::CPAN
our @EXPORT = qw( req_to_psgi res_from_psgi );
use Carp ();
use HTTP::Status qw(status_message);
use URI::Escape ();
use Plack::Util;
use Try::Tiny;
my $TRUE = (1 == 1);
my $FALSE = !$TRUE;
sub req_to_psgi {
my $req = shift;
unless (try { $req->isa('HTTP::Request') }) {
Carp::croak("Request is not HTTP::Request: $req");
}
# from HTTP::Request::AsCGI
my $host = $req->header('Host');
my $uri = $req->uri->clone;
$uri->scheme('http') unless $uri->scheme;
$uri->host('localhost') unless $uri->host;
$uri->port(80) unless $uri->port;
$uri->host_port($host) unless !$host || ( $host eq $uri->host_port );
my $input;
my $content = $req->content;
if (ref $content eq 'CODE') {
if (defined $req->content_length) {
$input = HTTP::Message::PSGI::ChunkedInput->new($content);
} else {
$req->header("Transfer-Encoding" => "chunked");
$input = HTTP::Message::PSGI::ChunkedInput->new($content, 1);
}
} else {
open $input, "<", \$content;
$req->content_length(length $content)
unless defined $req->content_length;
}
my $env = {
PATH_INFO => URI::Escape::uri_unescape($uri->path || '/'),
QUERY_STRING => $uri->query || '',
SCRIPT_NAME => '',
SERVER_NAME => $uri->host,
SERVER_PORT => $uri->port,
SERVER_PROTOCOL => $req->protocol || 'HTTP/1.1',
REMOTE_ADDR => '127.0.0.1',
REMOTE_HOST => 'localhost',
REMOTE_PORT => int( rand(64000) + 1000 ), # not in RFC 3875
REQUEST_URI => $uri->path_query || '/', # not in RFC 3875
REQUEST_METHOD => $req->method,
'psgi.version' => [ 1, 1 ],
'psgi.url_scheme' => $uri->scheme eq 'https' ? 'https' : 'http',
'psgi.input' => $input,
'psgi.errors' => *STDERR,
'psgi.multithread' => $FALSE,
'psgi.multiprocess' => $FALSE,
'psgi.run_once' => $TRUE,
'psgi.streaming' => $TRUE,
'psgi.nonblocking' => $FALSE,
@_,
};
for my $field ( $req->headers->header_field_names ) {
my $key = uc("HTTP_$field");
$key =~ tr/-/_/;
$key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
unless ( exists $env->{$key} ) {
$env->{$key} = $req->headers->header($field);
}
}
if ($env->{SCRIPT_NAME}) {
$env->{PATH_INFO} =~ s/^\Q$env->{SCRIPT_NAME}\E/\//;
$env->{PATH_INFO} =~ s/^\/+/\//;
}
if (!defined($env->{HTTP_HOST}) && $req->uri->can('host')) {
$env->{HTTP_HOST} = $req->uri->host;
$env->{HTTP_HOST} .= ':' . $req->uri->port
if $req->uri->port ne $req->uri->default_port;
}
return $env;
}
sub res_from_psgi {
my ($psgi_res) = @_;
require HTTP::Response;
my $res;
if (ref $psgi_res eq 'ARRAY') {
_res_from_psgi($psgi_res, \$res);
} elsif (ref $psgi_res eq 'CODE') {
$psgi_res->(sub {
_res_from_psgi($_[0], \$res);
});
} else {
Carp::croak("Bad response: ", defined $psgi_res ? $psgi_res : 'undef');
}
return $res;
}
sub _res_from_psgi {
my ($status, $headers, $body) = @{+shift};
my $res_ref = shift;
my $convert_resp = sub {
my $res = HTTP::Response->new($status);
$res->message(status_message($status));
$res->headers->header(@$headers) if @$headers;
if (ref $body eq 'ARRAY') {
$res->content(join '', grep defined, @$body);
} else {
local $/ = \4096;
examples/fatpacked.plackup view on Meta::CPAN
eval { require IO::Socket::SSL; 1 }
or Carp::croak("SSL suport requires IO::Socket::SSL");
$args->{SSL_key_file} = $self->{ssl_key_file};
$args->{SSL_cert_file} = $self->{ssl_cert_file};
return "IO::Socket::SSL";
} elsif ($self->{ipv6}) {
eval { require IO::Socket::IP; 1 }
or Carp::croak("IPv6 support requires IO::Socket::IP");
$self->{host} ||= '::';
$args->{LocalAddr} ||= '::';
return "IO::Socket::IP";
}
return "IO::Socket::INET";
}
sub setup_listener {
my $self = shift;
$self->{listen_sock} ||= do {
my %args = (
Listen => SOMAXCONN,
LocalPort => $self->{port},
LocalAddr => $self->{host},
Proto => 'tcp',
ReuseAddr => 1,
);
my $class = $self->prepare_socket_class(\%args);
$class->new(%args)
or die "failed to listen to port $self->{port}: $!";
};
$self->{server_ready}->({ %$self, proto => $self->{ssl} ? 'https' : 'http' });
}
sub accept_loop {
my($self, $app) = @_;
$app = Plack::Middleware::ContentLength->wrap($app);
while (1) {
local $SIG{PIPE} = 'IGNORE';
if (my $conn = $self->{listen_sock}->accept) {
if (defined TCP_NODELAY) {
$conn->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
or die "setsockopt(TCP_NODELAY) failed:$!";
}
my $env = {
SERVER_PORT => $self->{port},
SERVER_NAME => $self->{host},
SCRIPT_NAME => '',
REMOTE_ADDR => $conn->peerhost,
REMOTE_PORT => $conn->peerport || 0,
'psgi.version' => [ 1, 1 ],
'psgi.errors' => *STDERR,
'psgi.url_scheme' => $self->{ssl} ? 'https' : 'http',
'psgi.run_once' => Plack::Util::FALSE,
'psgi.multithread' => Plack::Util::FALSE,
'psgi.multiprocess' => Plack::Util::FALSE,
'psgi.streaming' => Plack::Util::TRUE,
'psgi.nonblocking' => Plack::Util::FALSE,
'psgix.harakiri' => Plack::Util::TRUE,
'psgix.input.buffered' => Plack::Util::TRUE,
'psgix.io' => $conn,
};
$self->handle_connection($env, $conn, $app);
$conn->close;
last if $env->{'psgix.harakiri.commit'};
}
}
}
sub handle_connection {
my($self, $env, $conn, $app) = @_;
my $buf = '';
my $res = [ 400, [ 'Content-Type' => 'text/plain' ], [ 'Bad Request' ] ];
while (1) {
my $rlen = $self->read_timeout(
$conn, \$buf, MAX_REQUEST_SIZE - length($buf), length($buf),
$self->{timeout},
) or return;
my $reqlen = parse_http_request($buf, $env);
if ($reqlen >= 0) {
$buf = substr $buf, $reqlen;
if (my $cl = $env->{CONTENT_LENGTH}) {
my $buffer = Stream::Buffered->new($cl);
while ($cl > 0) {
my $chunk;
if (length $buf) {
$chunk = $buf;
$buf = '';
} else {
$self->read_timeout($conn, \$chunk, $cl, 0, $self->{timeout})
or return;
}
$buffer->print($chunk);
$cl -= length $chunk;
}
$env->{'psgi.input'} = $buffer->rewind;
} else {
open my $input, "<", \$buf;
$env->{'psgi.input'} = $input;
}
$res = Plack::Util::run_app $app, $env;
last;
}
if ($reqlen == -2) {
# request is incomplete, do nothing
} elsif ($reqlen == -1) {
# error, close conn
last;
}
}
if (ref $res eq 'ARRAY') {
$self->_handle_response($res, $conn);
examples/fatpacked.plackup view on Meta::CPAN
$fatpacked{"Plack/Handler/Apache1.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PLACK_HANDLER_APACHE1';
package Plack::Handler::Apache1;
use strict;
use Apache::Request;
use Apache::Constants qw(:common :response);
use Plack::Util;
use Scalar::Util;
my %apps; # psgi file to $app mapping
sub new { bless {}, shift }
sub preload {
my $class = shift;
for my $app (@_) {
$class->load_app($app);
}
}
sub load_app {
my($class, $app) = @_;
return $apps{$app} ||= do {
# Trick Catalyst, CGI.pm, CGI::Cookie and others that check
# for $ENV{MOD_PERL}.
#
# Note that we delete it instead of just localizing
# $ENV{MOD_PERL} because some users may check if the key
# exists, and we do it this way because "delete local" is new
# in 5.12:
# http://perldoc.perl.org/5.12.0/perldelta.html#delete-local
local $ENV{MOD_PERL};
delete $ENV{MOD_PERL};
Plack::Util::load_psgi $app;
};
}
sub handler {
my $class = __PACKAGE__;
my $r = shift;
my $psgi = $r->dir_config('psgi_app');
$class->call_app($r, $class->load_app($psgi));
}
sub call_app {
my ($class, $r, $app) = @_;
$r->subprocess_env; # let Apache create %ENV for us :)
my $env = {
%ENV,
'psgi.version' => [ 1, 1 ],
'psgi.url_scheme' => ($ENV{HTTPS}||'off') =~ /^(?:on|1)$/i ? 'https' : 'http',
'psgi.input' => $r,
'psgi.errors' => *STDERR,
'psgi.multithread' => Plack::Util::FALSE,
'psgi.multiprocess' => Plack::Util::TRUE,
'psgi.run_once' => Plack::Util::FALSE,
'psgi.streaming' => Plack::Util::TRUE,
'psgi.nonblocking' => Plack::Util::FALSE,
'psgix.harakiri' => Plack::Util::TRUE,
};
if (defined(my $HTTP_AUTHORIZATION = $r->headers_in->{Authorization})) {
$env->{HTTP_AUTHORIZATION} = $HTTP_AUTHORIZATION;
}
my $vpath = $env->{SCRIPT_NAME} . ($env->{PATH_INFO} || '');
my $location = $r->location || "/";
$location =~ s{/$}{};
(my $path_info = $vpath) =~ s/^\Q$location\E//;
$env->{SCRIPT_NAME} = $location;
$env->{PATH_INFO} = $path_info;
my $res = $app->($env);
if (ref $res eq 'ARRAY') {
_handle_response($r, $res);
}
elsif (ref $res eq 'CODE') {
$res->(sub {
_handle_response($r, $_[0]);
});
}
else {
die "Bad response $res";
}
if ($env->{'psgix.harakiri.commit'}) {
$r->child_terminate;
}
return OK;
}
sub _handle_response {
my ($r, $res) = @_;
my ($status, $headers, $body) = @{ $res };
my $hdrs = ($status >= 200 && $status < 300)
? $r->headers_out : $r->err_headers_out;
Plack::Util::header_iter($headers, sub {
my($h, $v) = @_;
if (lc $h eq 'content-type') {
$r->content_type($v);
} else {
$hdrs->add($h => $v);
}
});
$r->status($status);
$r->send_http_header;
if (defined $body) {
if (Plack::Util::is_real_fh($body)) {
$r->send_fd($body);
examples/fatpacked.plackup view on Meta::CPAN
$fatpacked{"Plack/Handler/Apache2.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PLACK_HANDLER_APACHE2';
package Plack::Handler::Apache2;
use strict;
use warnings;
use Apache2::RequestRec;
use Apache2::RequestIO;
use Apache2::RequestUtil;
use Apache2::Response;
use Apache2::Const -compile => qw(OK);
use Apache2::Log;
use APR::Table;
use IO::Handle;
use Plack::Util;
use Scalar::Util;
use URI;
use URI::Escape;
my %apps; # psgi file to $app mapping
sub new { bless {}, shift }
sub preload {
my $class = shift;
for my $app (@_) {
$class->load_app($app);
}
}
sub load_app {
my($class, $app) = @_;
return $apps{$app} ||= do {
# Trick Catalyst, CGI.pm, CGI::Cookie and others that check
# for $ENV{MOD_PERL}.
#
# Note that we delete it instead of just localizing
# $ENV{MOD_PERL} because some users may check if the key
# exists, and we do it this way because "delete local" is new
# in 5.12:
# http://perldoc.perl.org/5.12.0/perldelta.html#delete-local
local $ENV{MOD_PERL};
delete $ENV{MOD_PERL};
Plack::Util::load_psgi $app;
};
}
sub call_app {
my ($class, $r, $app) = @_;
$r->subprocess_env; # let Apache create %ENV for us :)
my $env = {
%ENV,
'psgi.version' => [ 1, 1 ],
'psgi.url_scheme' => ($ENV{HTTPS}||'off') =~ /^(?:on|1)$/i ? 'https' : 'http',
'psgi.input' => $r,
'psgi.errors' => *STDERR,
'psgi.multithread' => Plack::Util::FALSE,
'psgi.multiprocess' => Plack::Util::TRUE,
'psgi.run_once' => Plack::Util::FALSE,
'psgi.streaming' => Plack::Util::TRUE,
'psgi.nonblocking' => Plack::Util::FALSE,
'psgix.harakiri' => Plack::Util::TRUE,
'psgix.cleanup' => Plack::Util::TRUE,
'psgix.cleanup.handlers' => [],
};
if (defined(my $HTTP_AUTHORIZATION = $r->headers_in->{Authorization})) {
$env->{HTTP_AUTHORIZATION} = $HTTP_AUTHORIZATION;
}
# If you supply more than one Content-Length header Apache will
# happily concat the values with ", ", e.g. "72, 72". This
# violates the PSGI spec so fix this up and just take the first
# one.
if (exists $env->{CONTENT_LENGTH} && $env->{CONTENT_LENGTH} =~ /,/) {
no warnings qw(numeric);
$env->{CONTENT_LENGTH} = int $env->{CONTENT_LENGTH};
}
# Actually, we can not trust PATH_INFO from mod_perl because mod_perl squeezes multiple slashes into one slash.
my $uri = URI->new("http://".$r->hostname.$r->unparsed_uri);
$env->{PATH_INFO} = uri_unescape($uri->path);
$class->fixup_path($r, $env);
my $res = $app->($env);
if (ref $res eq 'ARRAY') {
_handle_response($r, $res);
}
elsif (ref $res eq 'CODE') {
$res->(sub {
_handle_response($r, $_[0]);
});
}
else {
die "Bad response $res";
}
if (@{ $env->{'psgix.cleanup.handlers'} }) {
$r->push_handlers(
PerlCleanupHandler => sub {
for my $cleanup_handler (@{ $env->{'psgix.cleanup.handlers'} }) {
$cleanup_handler->($env);
}
if ($env->{'psgix.harakiri.commit'}) {
$r->child_terminate;
}
},
);
} else {
if ($env->{'psgix.harakiri.commit'}) {
$r->child_terminate;
}
}
return Apache2::Const::OK;
}
examples/fatpacked.plackup view on Meta::CPAN
414 => 'Request-URI Too Large',
415 => 'Unsupported Media Type',
416 => 'Request Range Not Satisfiable',
417 => 'Expectation Failed',
422 => 'Unprocessable Entity', # RFC 2518 (WebDAV)
423 => 'Locked', # RFC 2518 (WebDAV)
424 => 'Failed Dependency', # RFC 2518 (WebDAV)
425 => 'No code', # WebDAV Advanced Collections
426 => 'Upgrade Required', # RFC 2817
449 => 'Retry with', # unofficial Microsoft
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates', # RFC 2295
507 => 'Insufficient Storage', # RFC 2518 (WebDAV)
509 => 'Bandwidth Limit Exceeded', # unofficial
510 => 'Not Extended', # RFC 2774
);
sub new { bless {}, shift }
sub run {
my ($self, $app) = @_;
my $env = $self->setup_env();
my $res = $app->($env);
if (ref $res eq 'ARRAY') {
$self->_handle_response($res);
}
elsif (ref $res eq 'CODE') {
$res->(sub {
$self->_handle_response($_[0]);
});
}
else {
die "Bad response $res";
}
}
sub setup_env {
my ( $self, $override_env ) = @_;
$override_env ||= {};
binmode STDIN;
binmode STDERR;
my $env = {
%ENV,
'psgi.version' => [ 1, 1 ],
'psgi.url_scheme' => ($ENV{HTTPS}||'off') =~ /^(?:on|1)$/i ? 'https' : 'http',
'psgi.input' => *STDIN,
'psgi.errors' => *STDERR,
'psgi.multithread' => 0,
'psgi.multiprocess' => 1,
'psgi.run_once' => 1,
'psgi.streaming' => 1,
'psgi.nonblocking' => 1,
%{ $override_env },
};
delete $env->{HTTP_CONTENT_TYPE};
delete $env->{HTTP_CONTENT_LENGTH};
$env->{'HTTP_COOKIE'} ||= $ENV{COOKIE}; # O'Reilly server bug
if (!exists $env->{PATH_INFO}) {
$env->{PATH_INFO} = '';
}
if ($env->{SCRIPT_NAME} eq '/') {
$env->{SCRIPT_NAME} = '';
$env->{PATH_INFO} = '/' . $env->{PATH_INFO};
}
return $env;
}
sub _handle_response {
my ($self, $res) = @_;
*STDOUT->autoflush(1);
binmode STDOUT;
my $hdrs;
my $message = $StatusCode{$res->[0]};
$hdrs = "Status: $res->[0] $message\015\012";
my $headers = $res->[1];
while (my ($k, $v) = splice(@$headers, 0, 2)) {
$hdrs .= "$k: $v\015\012";
}
$hdrs .= "\015\012";
print STDOUT $hdrs;
my $body = $res->[2];
my $cb = sub { print STDOUT $_[0] };
# inline Plack::Util::foreach here
if (ref $body eq 'ARRAY') {
for my $line (@$body) {
$cb->($line) if length $line;
}
}
elsif (defined $body) {
local $/ = \65536 unless ref $/;
while (defined(my $line = $body->getline)) {
$cb->($line) if length $line;
}
$body->close;
}
else {
return Plack::Handler::CGI::Writer->new;
}
}
examples/fatpacked.plackup view on Meta::CPAN
die "STDIN is not a socket: specify a listen location";
}
@{$self}{qw(stdin stdout stderr)}
= (IO::Handle->new, IO::Handle->new, IO::Handle->new);
my %env;
my $request = FCGI::Request(
$self->{stdin}, $self->{stdout}, $self->{stderr},
\%env, $sock,
($self->{nointr} ? 0 : &FCGI::FAIL_ACCEPT_ON_INTR),
);
my $proc_manager;
if ($self->{listen} or $running_on_server_starter) {
$self->daemon_fork if $self->{daemonize};
if ($self->{manager}) {
if (blessed $self->{manager}) {
for (qw(nproc pid proc_title)) {
die "Don't use '$_' when passing in a 'manager' object"
if $self->{$_};
}
$proc_manager = $self->{manager};
} else {
Plack::Util::load_class($self->{manager});
$proc_manager = $self->{manager}->new({
n_processes => $self->{nproc},
pid_fname => $self->{pid},
(exists $self->{proc_title}
? (pm_title => $self->{proc_title}) : ()),
});
}
# detach *before* the ProcManager inits
$self->daemon_detach if $self->{daemonize};
}
elsif ($self->{daemonize}) {
$self->daemon_detach;
}
} elsif (blessed $self->{manager}) {
$proc_manager = $self->{manager};
}
$proc_manager && $proc_manager->pm_manage;
while ($request->Accept >= 0) {
$proc_manager && $proc_manager->pm_pre_dispatch;
my $env = {
%env,
'psgi.version' => [1,1],
'psgi.url_scheme' => ($env{HTTPS}||'off') =~ /^(?:on|1)$/i ? 'https' : 'http',
'psgi.input' => $self->{stdin},
'psgi.errors' =>
($self->{keep_stderr} ? \*STDERR : $self->{stderr}),
'psgi.multithread' => Plack::Util::FALSE,
'psgi.multiprocess' => defined $proc_manager,
'psgi.run_once' => Plack::Util::FALSE,
'psgi.streaming' => Plack::Util::TRUE,
'psgi.nonblocking' => Plack::Util::FALSE,
'psgix.harakiri' => defined $proc_manager,
'psgix.cleanup' => 1,
'psgix.cleanup.handlers' => [],
};
delete $env->{HTTP_CONTENT_TYPE};
delete $env->{HTTP_CONTENT_LENGTH};
# lighttpd munges multiple slashes in PATH_INFO into one. Try recovering it
my $uri = URI->new("http://localhost" . $env->{REQUEST_URI});
$env->{PATH_INFO} = uri_unescape($uri->path);
$env->{PATH_INFO} =~ s/^\Q$env->{SCRIPT_NAME}\E//;
# root access for mod_fastcgi
if (!exists $env->{PATH_INFO}) {
$env->{PATH_INFO} = '';
}
# typical fastcgi_param from nginx might get empty values
for my $key (qw(CONTENT_TYPE CONTENT_LENGTH)) {
no warnings;
delete $env->{$key} if exists $env->{$key} && $env->{$key} eq '';
}
if (defined(my $HTTP_AUTHORIZATION = $env->{Authorization})) {
$env->{HTTP_AUTHORIZATION} = $HTTP_AUTHORIZATION;
}
my $res = Plack::Util::run_app $app, $env;
if (ref $res eq 'ARRAY') {
$self->_handle_response($res);
}
elsif (ref $res eq 'CODE') {
$res->(sub {
$self->_handle_response($_[0]);
});
}
else {
die "Bad response $res";
}
# give pm_post_dispatch the chance to do things after the client thinks
# the request is done
$request->Finish;
$proc_manager && $proc_manager->pm_post_dispatch();
# When the fcgi-manager exits it sends a TERM signal to the workers.
# However, if we're busy processing the cleanup handlers, testing
# shows that the worker doesn't actually exit in that case.
# Trapping the TERM signal and finshing up fixes that.
my $exit_due_to_signal = 0;
if ( @{ $env->{'psgix.cleanup.handlers'} || [] } ) {
local $SIG{TERM} = sub { $exit_due_to_signal = 1 };
for my $handler ( @{ $env->{'psgix.cleanup.handlers'} } ) {
$handler->($env);
}
}
examples/fatpacked.plackup view on Meta::CPAN
Plack::Loader::Restarter is a loader backend that implements C<-r> and
C<-R> option for the L<plackup> script. It forks the server as a child
process and the parent watches the directories for file updates, and
whenever it receives the notification, kills the child server and
restart.
=head1 SEE ALSO
L<Plack::Runner>, L<Catalyst::Restarter>
=cut
PLACK_LOADER_RESTARTER
$fatpacked{"Plack/Loader/Shotgun.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PLACK_LOADER_SHOTGUN';
package Plack::Loader::Shotgun;
use strict;
use parent qw(Plack::Loader);
use Storable;
use Try::Tiny;
use Plack::Middleware::BufferedStreaming;
die <<DIE if $^O eq 'MSWin32' && !$ENV{PLACK_SHOTGUN_MEMORY_LEAK};
Shotgun loader uses fork(2) system call to create a fresh Perl interpreter, that is known to not work
properly in a fork-emulation layer on Windows and cause huge memory leaks.
If you're aware of this and still want to run the loader, run it with the environment variable
PLACK_SHOTGUN_MEMORY_LEAK on.
DIE
sub preload_app {
my($self, $builder) = @_;
$self->{builder} = sub { Plack::Middleware::BufferedStreaming->wrap($builder->()) };
}
sub run {
my($self, $server) = @_;
my $app = sub {
my $env = shift;
pipe my $read, my $write;
my $pid = fork;
if ($pid) {
# parent
close $write;
my $res = Storable::thaw(join '', <$read>);
close $read;
waitpid($pid, 0);
return $res;
} else {
# child
close $read;
my $res;
try {
$env->{'psgi.streaming'} = 0;
$res = $self->{builder}->()->($env);
my @body;
Plack::Util::foreach($res->[2], sub { push @body, $_[0] });
$res->[2] = \@body;
} catch {
$env->{'psgi.errors'}->print($_);
$res = [ 500, [ "Content-Type", "text/plain" ], [ "Internal Server Error" ] ];
};
print {$write} Storable::freeze($res);
close $write;
exit;
}
};
$server->run($app);
}
1;
__END__
=head1 NAME
Plack::Loader::Shotgun - forking implementation of plackup
=head1 SYNOPSIS
plackup -L Shotgun
=head1 DESCRIPTION
Shotgun loader delays the compilation and execution of your
application until the runtime. When a new request comes in, this forks
a new child, compiles your code and runs the application.
This should be an ultimate alternative solution when reloading with
L<Plack::Middleware::Refresh> doesn't work, or plackup's default C<-r>
filesystem watcher causes problems. I can imagine this is useful for
applications which expects their application is only evaluated once
(like in-file templates) or on operating systems with broken fork
implementation, etc.
This is much like good old CGI's fork and run but you don't need a web
server, and there's a benefit of preloading modules that are not
likely to change. For instance if you develop a web application using
Moose and DBIx::Class,
plackup -MMoose -MDBIx::Class -L Shotgun yourapp.psgi
would preload those modules and only re-evaluates your code in every
request.
=head1 AUTHOR
Tatsuhiko Miyagawa with an inspiration from L<http://github.com/rtomayko/shotgun>
=head1 SEE ALSO
L<plackup>
examples/fatpacked.plackup view on Meta::CPAN
# $self->app is the original app
my $res = $self->app->($env);
# Do something with $res
return $res;
}
# then in app.psgi
use Plack::Builder;
my $app = sub { ... } # as usual
builder {
enable "Plack::Middleware::Foo";
enable "Plack::Middleware::Bar", %options;
$app;
};
=head1 DESCRIPTION
Plack::Middleware is a utility base class to write PSGI
middleware. All you have to do is to inherit from Plack::Middleware
and then implement the callback C<call> method (or the C<to_app> method
that would return the PSGI code reference) to do the actual work. You
can use C<< $self->app >> to call the original (wrapped) application.
Your middleware object is created at the PSGI application compile time
and is persistent during the web server life cycle (unless it is a
non-persistent environment such as CGI), so you should never set or
cache per-request data like C<$env> in your middleware object. See
also L<Plack::Component/"OBJECT LIFECYCLE">.
See L<Plack::Builder> how to actually enable middleware in your
I<.psgi> application file using the DSL. If you do not like our
builder DSL, you can also use the C<wrap> method to wrap your application
with a middleware:
use Plack::Middleware::Foo;
my $app = sub { ... };
$app = Plack::Middleware::Foo->wrap($app, %options);
$app = Plack::Middleware::Bar->wrap($app, %options);
=head1 RESPONSE CALLBACK
The typical middleware is written like this:
package Plack::Middleware::Something;
use parent qw(Plack::Middleware);
sub call {
my($self, $env) = @_;
# pre-processing $env
my $res = $self->app->($env);
# post-processing $res
return $res;
}
The tricky thing about post-processing the response is that it could
either be an immediate 3 element array ref, or a code reference that
implements the delayed (streaming) interface.
Dealing with these two types of response in each piece of middleware
is pointless, so you're recommended to use the C<response_cb> wrapper
function in L<Plack::Util> when implementing a post processing
middleware.
sub call {
my($self, $env) = @_;
# pre-processing $env
my $res = $self->app->($env);
return Plack::Util::response_cb($res, sub {
my $res = shift;
# do something with $res;
});
}
The callback function gets a response as an array reference, and you can
update the reference to implement the post-processing. In the normal
case, this arrayref will have three elements (as described by the PSGI
spec), but will have only two elements when using a C<$writer> as
described below.
package Plack::Middleware::Always500;
use parent qw(Plack::Middleware);
use Plack::Util;
sub call {
my($self, $env) = @_;
my $res = $self->app->($env);
return Plack::Util::response_cb($res, sub {
my $res = shift;
$res->[0] = 500;
return;
});
}
In this example, the callback gets the C<$res> and updates its first
element (status code) to 500. Using C<response_cb> makes sure that
this works with the delayed response too.
You're not required (and not recommended either) to return a new array
reference - they will be simply ignored. You're suggested to
explicitly return, unless you fiddle with the content filter callback
(see below).
Similarly, note that you have to keep the C<$res> reference when you
swap the entire response.
Plack::Util::response_cb($res, sub {
my $res = shift;
$res = [ $new_status, $new_headers, $new_body ]; # THIS DOES NOT WORK
return;
});
This does not work, since assigning a new anonymous array to C<$res>
doesn't update the original PSGI response value. You should instead
do:
Plack::Util::response_cb($res, sub {
my $res = shift;
@$res = ($new_status, $new_headers, $new_body); # THIS WORKS
return;
});
The third element of the response array ref is a body, and it could
be either an arrayref or L<IO::Handle>-ish object. The application could
also make use of the C<$writer> object if C<psgi.streaming> is in
effect, and in this case, the third element will not exist
(C<@$res == 2>). Dealing with these variants is again really painful,
and C<response_cb> can take care of that too, by allowing you to return
a content filter as a code reference.
# replace all "Foo" in content body with "Bar"
Plack::Util::response_cb($res, sub {
my $res = shift;
return sub {
my $chunk = shift;
return unless defined $chunk;
$chunk =~ s/Foo/Bar/g;
return $chunk;
}
});
The callback takes one argument C<$chunk> and your callback is
expected to return the updated chunk. If the given C<$chunk> is undef,
it means the stream has reached the end, so your callback should also
return undef, or return the final chunk and return undef when called
next time.
=head1 SEE ALSO
L<Plack> L<Plack::Builder> L<Plack::Component>
=cut
PLACK_MIDDLEWARE
$fatpacked{"Plack/Middleware/AccessLog.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PLACK_MIDDLEWARE_ACCESSLOG';
package Plack::Middleware::AccessLog;
use strict;
use warnings;
use parent qw( Plack::Middleware );
use Plack::Util::Accessor qw( logger format compiled_format char_handlers block_handlers );
use Apache::LogFormat::Compiler;
my %formats = (
common => '%h %l %u %t "%r" %>s %b',
combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"',
);
sub prepare_app {
my $self = shift;
my $fmt = $self->format || "combined";
$fmt = $formats{$fmt} if exists $formats{$fmt};
$self->compiled_format(Apache::LogFormat::Compiler->new($fmt,
char_handlers => $self->char_handlers || {},
block_handlers => $self->block_handlers || {},
));
}
sub call {
my $self = shift;
my($env) = @_;
my $res = $self->app->($env);
if ( ref($res) && ref($res) eq 'ARRAY' ) {
my $content_length = Plack::Util::content_length($res->[2]);
examples/fatpacked.plackup view on Meta::CPAN
supplied and returns whether the authentication succeeds. Required.
Authenticator can also be an object that responds to C<authenticate>
method that takes username and password and returns boolean, so
backends for L<Authen::Simple> is perfect to use:
use Authen::Simple::LDAP;
enable "Auth::Basic", authenticator => Authen::Simple::LDAP->new(...);
=item realm
Realm name to display in the basic authentication dialog. Defaults to I<restricted area>.
=back
=head1 LIMITATIONS
This middleware expects that the application has a full access to the
headers sent by clients in PSGI environment. That is normally the case
with standalone Perl PSGI web servers such as L<Starman> or
L<HTTP::Server::Simple::PSGI>.
However, in a web server configuration where you can't achieve this
(i.e. using your application via Apache's mod_cgi), this middleware
does not work since your application can't know the value of
C<Authorization:> header.
If you use Apache as a web server and CGI to run your PSGI
application, you can either a) compile Apache with
C<-DSECURITY_HOLE_PASS_AUTHORIZATION> option, or b) use mod_rewrite to
pass the Authorization header to the application with the rewrite rule
like following.
RewriteEngine on
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
=head1 AUTHOR
Tatsuhiko Miyagawa
=head1 SEE ALSO
L<Plack>
=cut
PLACK_MIDDLEWARE_AUTH_BASIC
$fatpacked{"Plack/Middleware/BufferedStreaming.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PLACK_MIDDLEWARE_BUFFEREDSTREAMING';
package Plack::Middleware::BufferedStreaming;
use strict;
no warnings;
use Carp;
use Plack::Util;
use Plack::Util::Accessor qw(force);
use Scalar::Util qw(weaken);
use parent qw(Plack::Middleware);
sub call {
my ( $self, $env ) = @_;
my $caller_supports_streaming = $env->{'psgi.streaming'};
$env->{'psgi.streaming'} = Plack::Util::TRUE;
my $res = $self->app->($env);
return $res if $caller_supports_streaming && !$self->force;
if ( ref($res) eq 'CODE' ) {
my $ret;
$res->(sub {
my $write = shift;
if ( @$write == 2 ) {
my @body;
$ret = [ @$write, \@body ];
return Plack::Util::inline_object(
write => sub { push @body, $_[0] },
close => sub { },
);
} else {
$ret = $write;
return;
}
});
return $ret;
} else {
return $res;
}
}
1;
__END__
=head1 NAME
Plack::Middleware::BufferedStreaming - Enable buffering for non-streaming aware servers
=head1 SYNOPSIS
enable "BufferedStreaming";
=head1 DESCRIPTION
Plack::Middleware::BufferedStreaming is a PSGI middleware component
that wraps the application that uses C<psgi.streaming> interface to
run on the servers that do not support the interface, by buffering the
writer output to a temporary buffer.
This middleware doesn't do anything and bypass the application if the
server supports C<psgi.streaming> interface, unless you set C<force>
option (see below).
=head1 OPTIONS
=over 4
=item force
Force enable this middleware only if the container supports C<psgi.streaming>.
=back
=head1 AUTHOR
Yuval Kogman
Tatsuhiko Miyagawa
=cut
PLACK_MIDDLEWARE_BUFFEREDSTREAMING
$fatpacked{"Plack/Middleware/Chunked.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PLACK_MIDDLEWARE_CHUNKED';
package Plack::Middleware::Chunked;
use strict;
use parent qw(Plack::Middleware);
use Plack::Util;
sub call {
my($self, $env) = @_;
my $res = $self->app->($env);
$self->response_cb($res, sub {
my $res = shift;
my $h = Plack::Util::headers($res->[1]);
if ($env->{'SERVER_PROTOCOL'} ne 'HTTP/1.0' and
! Plack::Util::status_with_no_entity_body($res->[0]) and
! $h->exists('Content-Length') and
! $h->exists('Transfer-Encoding')
) {
$h->set('Transfer-Encoding' => 'chunked');
my $done;
return sub {
my $chunk = shift;
return if $done;
unless (defined $chunk) {
$done = 1;
return "0\015\012\015\012";
}
return '' unless length $chunk;
return sprintf('%x', length $chunk) . "\015\012$chunk\015\012";
};
}
});
}
1;
__END__
=head1 NAME
Plack::Middleware::Chunked - Applies chunked encoding to the response body
=head1 SYNOPSIS
# Mostly from server implementations
$app = Plack::Middleware::Chunked->wrap($app);
=head1 DESCRIPTION
examples/fatpacked.plackup view on Meta::CPAN
$fatpacked{"Plack/Middleware/JSONP.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PLACK_MIDDLEWARE_JSONP';
package Plack::Middleware::JSONP;
use strict;
use parent qw(Plack::Middleware);
use Plack::Util;
use URI::Escape ();
use Plack::Util::Accessor qw/callback_key/;
sub prepare_app {
my $self = shift;
unless (defined $self->callback_key) {
$self->callback_key('callback');
}
}
sub call {
my($self, $env) = @_;
my $res = $self->app->($env);
$self->response_cb($res, sub {
my $res = shift;
if (defined $res->[2]) {
my $h = Plack::Util::headers($res->[1]);
my $callback_key = $self->callback_key;
if ($h->get('Content-Type') =~ m!/(?:json|javascript)! &&
$env->{QUERY_STRING} =~ /(?:^|&)$callback_key=([^&]+)/) {
my $cb = URI::Escape::uri_unescape($1);
if ($cb =~ /^[\w\.\[\]]+$/) {
my $body;
Plack::Util::foreach($res->[2], sub { $body .= $_[0] });
my $jsonp = "/**/$cb($body)";
$res->[2] = [ $jsonp ];
$h->set('Content-Length', length $jsonp);
$h->set('Content-Type', 'text/javascript');
}
}
}
});
}
1;
__END__
=head1 NAME
Plack::Middleware::JSONP - Wraps JSON response in JSONP if callback parameter is specified
=head1 SYNOPSIS
enable "JSONP", callback_key => 'jsonp';
=head1 DESCRIPTION
Plack::Middleware::JSONP wraps JSON response, which has Content-Type
value either C<text/javascript> or C<application/json> as a JSONP
response which is specified with the C<callback> query parameter. The
name of the parameter can be set while enabling the middleware.
This middleware only works with a non-streaming response, and doesn't
touch the response otherwise.
=head1 AUTHOR
Tatsuhiko Miyagawa
=head1 SEE ALSO
L<Plack>
=cut
PLACK_MIDDLEWARE_JSONP
$fatpacked{"Plack/Middleware/LighttpdScriptNameFix.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PLACK_MIDDLEWARE_LIGHTTPDSCRIPTNAMEFIX';
package Plack::Middleware::LighttpdScriptNameFix;
use strict;
use parent qw/Plack::Middleware/;
use Plack::Util::Accessor qw(script_name);
sub prepare_app {
my $self = shift;
my $script_name = $self->script_name;
$script_name = '' unless defined($script_name);
$script_name =~ s!/$!!;
$self->script_name($script_name);
}
sub call {
my($self, $env) = @_;
if ($env->{SERVER_SOFTWARE} && $env->{SERVER_SOFTWARE} =~ /lighttpd/) {
$env->{PATH_INFO} = $env->{SCRIPT_NAME} . $env->{PATH_INFO};
$env->{SCRIPT_NAME} = $self->script_name;
$env->{PATH_INFO} =~ s/^\Q$env->{SCRIPT_NAME}\E//;
}
return $self->app->($env);
}
1;
__END__
=head1 NAME
Plack::Middleware::LighttpdScriptNameFix - fixes wrong SCRIPT_NAME and PATH_INFO that lighttpd sets
=head1 SYNOPSIS
# in your app.psgi
use Plack::Builder;
builder {
enable "LighttpdScriptNameFix";
$app;
};
# Or from the command line
examples/fatpacked.plackup view on Meta::CPAN
sub call {
my $self = shift;
my $env = shift;
$self->validate_env($env);
my $res = $self->app->($env);
return $self->validate_res($res);
}
sub validate_env {
my ($self, $env) = @_;
unless ($env->{REQUEST_METHOD}) {
die('Missing env param: REQUEST_METHOD');
}
unless ($env->{REQUEST_METHOD} =~ /^[A-Z]+$/) {
die("Invalid env param: REQUEST_METHOD($env->{REQUEST_METHOD})");
}
unless (defined($env->{SCRIPT_NAME})) { # allows empty string
die('Missing mandatory env param: SCRIPT_NAME');
}
if ($env->{SCRIPT_NAME} eq '/') {
die('SCRIPT_NAME must not be /');
}
unless (defined($env->{PATH_INFO})) { # allows empty string
die('Missing mandatory env param: PATH_INFO');
}
if ($env->{PATH_INFO} ne '' && $env->{PATH_INFO} !~ m!^/!) {
die('PATH_INFO must begin with / ($env->{PATH_INFO})');
}
unless (defined($env->{SERVER_NAME})) {
die('Missing mandatory env param: SERVER_NAME');
}
if ($env->{SERVER_NAME} eq '') {
die('SERVER_NAME must not be empty string');
}
unless (defined($env->{SERVER_PORT})) {
die('Missing mandatory env param: SERVER_PORT');
}
if ($env->{SERVER_PORT} eq '') {
die('SERVER_PORT must not be empty string');
}
if (defined($env->{SERVER_PROTOCOL}) and $env->{SERVER_PROTOCOL} !~ m{^HTTP/\d}) {
die("Invalid SERVER_PROTOCOL: $env->{SERVER_PROTOCOL}");
}
for my $param (qw/version url_scheme input errors multithread multiprocess/) {
unless (exists $env->{"psgi.$param"}) {
die("Missing psgi.$param");
}
}
unless (ref($env->{'psgi.version'}) eq 'ARRAY') {
die("psgi.version should be ArrayRef: $env->{'psgi.version'}");
}
unless (scalar(@{$env->{'psgi.version'}}) == 2) {
die('psgi.version should contain 2 elements, not ', scalar(@{$env->{'psgi.version'}}));
}
unless ($env->{'psgi.url_scheme'} =~ /^https?$/) {
die("psgi.url_scheme should be 'http' or 'https': ", $env->{'psgi.url_scheme'});
}
if ($env->{"psgi.version"}->[1] == 1) { # 1.1
for my $param (qw(streaming nonblocking run_once)) {
unless (exists $env->{"psgi.$param"}) {
die("Missing psgi.$param");
}
}
}
if ($env->{HTTP_CONTENT_TYPE}) {
die('HTTP_CONTENT_TYPE should not exist');
}
if ($env->{HTTP_CONTENT_LENGTH}) {
die('HTTP_CONTENT_LENGTH should not exist');
}
}
sub is_possibly_fh {
my $fh = shift;
ref $fh eq 'GLOB' &&
*{$fh}{IO} &&
*{$fh}{IO}->can('getline');
}
sub validate_res {
my ($self, $res, $streaming) = @_;
unless (ref($res) eq 'ARRAY' or ref($res) eq 'CODE') {
die("Response should be array ref or code ref: $res");
}
if (ref $res eq 'CODE') {
return $self->response_cb($res, sub { $self->validate_res(@_, 1) });
}
unless (@$res == 3 || ($streaming && @$res == 2)) {
die('Response needs to be 3 element array, or 2 element in streaming');
}
unless ($res->[0] =~ /^\d+$/ && $res->[0] >= 100) {
die("Status code needs to be an integer greater than or equal to 100: $res->[0]");
}
unless (ref $res->[1] eq 'ARRAY') {
die("Headers needs to be an array ref: $res->[1]");
}
my @copy = @{$res->[1]};
unless (@copy % 2 == 0) {
die('The number of response headers needs to be even, not odd(', scalar(@copy), ')');
}
while(my($key, $val) = splice(@copy, 0, 2)) {
if (lc $key eq 'status') {
die('Response headers MUST NOT contain a key named Status');
}
if ($key =~ /[:\r\n]|[-_]$/) {
die("Response headers MUST NOT contain a key with : or newlines, or that end in - or _. Header: $key");
}
unless ($key =~ /^[a-zA-Z][0-9a-zA-Z\-_]*$/) {
die("Response headers MUST consist only of letters, digits, _ or - and MUST start with a letter. Header: $key");
}
if ($val =~ /[\000-\037]/) {
die("Response headers MUST NOT contain characters below octal \037. Header: $key. Value: $val");
}
unless (defined $val) {
die("Response headers MUST be a defined string. Header: $key");
}
}
# @$res == 2 is only right in psgi.streaming, and it's already checked.
unless (@$res == 2 ||
ref $res->[2] eq 'ARRAY' ||
Plack::Util::is_real_fh($res->[2]) ||
is_possibly_fh($res->[2]) ||
(blessed($res->[2]) && $res->[2]->can('getline'))) {
die("Body should be an array ref or filehandle: $res->[2]");
}
if (ref $res->[2] eq 'ARRAY' && grep _has_wide_char($_), @{$res->[2]}) {
die("Body must be bytes and should not contain wide characters (UTF-8 strings)");
}
return $res;
}
# NOTE: Some modules like HTML:: or XML:: could possibly generate
# ASCII/Latin-1 strings with utf8 flags on. They're actually safe to
# print, so there's no need to give warnings about it.
sub _has_wide_char {
my $str = shift;
utf8::is_utf8($str) && $str =~ /[^\x00-\xff]/;
}
1;
__END__
=head1 NAME
Plack::Middleware::Lint - Validate request and response
=head1 SYNOPSIS
use Plack::Middleware::Lint;
my $app = sub { ... }; # your app or middleware
$app = Plack::Middleware::Lint->wrap($app);
# Or from plackup
plackup -e 'enable "Lint"' myapp.psgi
=head1 DESCRIPTION
Plack::Middleware::Lint is a middleware component to validate request
and response environment formats. You are strongly suggested to use
this middleware when you develop a new framework adapter or a new PSGI
web server that implements the PSGI interface.
This middleware is enabled by default when you run plackup or other
launcher tools with the default environment I<development> value.
=head1 DEBUGGING
Because of how this middleware works, it may not be easy to debug Lint
errors when you encounter one, unless you're writing a PSGI web server
or a framework.
For example, when you're an application developer (user of some
framework) and see errors like:
Body should be an array ref or filehandle at lib/Plack/Middleware/Lint.pm line XXXX
examples/fatpacked.plackup view on Meta::CPAN
return $err if ref $err;
# Ugly hack to remove " at ... line ..." automatically appended by perl
# If there's a proper way to do this, please let me know.
$err =~ s/ at \Q$caller->[1]\E line $caller->[2]\.\n$//;
return $err;
}
sub utf8_safe {
my $str = shift;
# NOTE: I know messing with utf8:: in the code is WRONG, but
# because we're running someone else's code that we can't
# guarantee which encoding an exception is encoded, there's no
# better way than doing this. The latest Devel::StackTrace::AsHTML
# (0.08 or later) encodes high-bit chars as HTML entities, so this
# path won't be executed.
if (utf8::is_utf8($str)) {
utf8::encode($str);
}
$str;
}
1;
__END__
=head1 NAME
Plack::Middleware::StackTrace - Displays stack trace when your app dies
=head1 SYNOPSIS
enable "StackTrace";
=head1 DESCRIPTION
This middleware uses C<$SIG{__DIE__}> to intercept I<all> exceptions
(run-time errors) happening in your application, even those that are caught.
For each exception it builds a detailed stack trace.
If the applications aborts by throwing an exception it will be caught and matched
against the saved stack traces. If a match is found it will be displayed as a nice
stack trace screen, if not then the exception will be reported without a stack trace.
The stack trace is also stored in the environment as a plaintext and HTML under the key
C<plack.stacktrace.text> and C<plack.stacktrace.html> respectively, so
that middleware further up the stack can reference it.
This middleware is enabled by default when you run L<plackup> in the
default I<development> mode.
You're recommended to use this middleware during the development and
use L<Plack::Middleware::HTTPExceptions> in the deployment mode as a
replacement, so that all the exceptions thrown from your application
still get caught and rendered as a 500 error response, rather than
crashing the web server.
Catching errors in streaming response is not supported.
=head2 Stack Trace Module
The L<Devel::StackTrace::WithLexicals> module will be used to capture the stack trace
if the installed version is 0.08 or later. Otherwise L<Devel::StackTrace> is used.
=head2 Performance
Gathering the information for a stack trace via L<Devel::StackTrace> is slow,
and L<Devel::StackTrace::WithLexicals> is significantly slower still.
This is not usually a concern in development and when exceptions are rare.
However, your application may include code that's throwing and catching exceptions
that you're not aware of. Such code will run I<significantly> slower with this module.
=head1 CONFIGURATION
=over 4
=item force
enable "StackTrace", force => 1;
Force display the stack trace when an error occurs within your
application and the response code from your application is
500. Defaults to off.
The use case of this option is that when your framework catches all
the exceptions in the main handler and returns all failures in your
code as a normal 500 PSGI error response. In such cases, this
middleware would never have a chance to display errors because it
can't tell if it's an application error or just random C<eval> in your
code. This option enforces the middleware to display stack trace even
if it's not the direct error thrown by the application.
=item no_print_errors
enable "StackTrace", no_print_errors => 1;
Skips printing the text stacktrace to console
(C<psgi.errors>). Defaults to 0, which means the text version of the
stack trace error is printed to the errors handle, which usually is a
standard error.
=back
=head1 AUTHOR
Tokuhiro Matsuno
Tatsuhiko Miyagawa
=head1 SEE ALSO
L<Devel::StackTrace::AsHTML> L<Plack::Middleware> L<Plack::Middleware::HTTPExceptions>
=cut
PLACK_MIDDLEWARE_STACKTRACE
$fatpacked{"Plack/Middleware/Static.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PLACK_MIDDLEWARE_STATIC';
examples/fatpacked.plackup view on Meta::CPAN
my $env = shift;
return [ 200, [ 'Content-Type' => 'text/plain' ], [ $env->{REQUEST_URI} ] ];
},
],
[
'filehandle with path()',
sub {
my $cb = shift;
my $res = $cb->(GET "http://127.0.0.1/foo.jpg");
is $res->code, 200;
is $res->message, 'OK';
is $res->header('content_type'), 'image/jpeg';
is length $res->content, 2898;
},
sub {
my $env = shift;
open my $fh, '<', "$share_dir/face.jpg";
Plack::Util::set_io_path($fh, "$share_dir/face.jpg");
return [
200,
[ 'Content-Type' => 'image/jpeg', 'Content-Length' => -s $fh ],
$fh
];
},
],
[
'a big header value > 128 bytes',
sub {
my $cb = shift;
my $req = GET "http://127.0.0.1/";
my $v = ("abcdefgh" x 16);
$req->header('X-Foo' => $v);
my $res = $cb->($req);
is $res->code, 200;
is $res->message, 'OK';
is $res->content, $v;
},
sub {
my $env = shift;
return [
200,
[ 'Content-Type' => 'text/plain' ],
[ $env->{HTTP_X_FOO} ],
];
},
],
[
'coderef res',
sub {
my $cb = shift;
my $res = $cb->(GET "http://127.0.0.1/?name=miyagawa");
return if $res->code == 501;
is $res->code, 200;
is $res->message, 'OK';
is $res->header('content_type'), 'text/plain';
is $res->content, 'Hello, name=miyagawa';
},
sub {
my $env = shift;
$env->{'psgi.streaming'} or return [ 501, ['Content-Type','text/plain'], [] ];
return sub {
my $respond = shift;
$respond->([
200,
[ 'Content-Type' => 'text/plain', ],
[ 'Hello, ' . $env->{QUERY_STRING} ],
]);
}
},
],
[
'coderef streaming',
sub {
my $cb = shift;
my $res = $cb->(GET "http://127.0.0.1/?name=miyagawa");
return if $res->code == 501;
is $res->code, 200;
is $res->message, 'OK';
is $res->header('content_type'), 'text/plain';
is $res->content, 'Hello, name=miyagawa';
},
sub {
my $env = shift;
$env->{'psgi.streaming'} or return [ 501, ['Content-Type','text/plain'], [] ];
return sub {
my $respond = shift;
my $writer = $respond->([
200,
[ 'Content-Type' => 'text/plain', ],
]);
$writer->write("Hello, ");
$writer->write($env->{QUERY_STRING});
$writer->close();
}
},
],
[
'CRLF output and FCGI parse bug',
sub {
my $cb = shift;
my $res = $cb->(GET "http://127.0.0.1/");
is $res->header("Foo"), undef;
is $res->content, "Foo: Bar\r\n\r\nHello World";
},
sub {
return [ 200, [ "Content-Type", "text/plain" ], [ "Foo: Bar\r\n\r\nHello World" ] ];
},
],
[
'newlines',
sub {
my $cb = shift;
my $res = $cb->(GET "http://127.0.0.1/");
is length($res->content), 7;
},
sub {
return [ 200, [ "Content-Type", "text/plain" ], [ "Bar\nBaz" ] ];
},
],
[
'test 404',
sub {
my $cb = shift;
my $res = $cb->(GET "http://127.0.0.1/");
is $res->code, 404;
is $res->message, 'Not Found';
is $res->content, 'Not Found';
},
sub {
return [ 404, [ "Content-Type", "text/plain" ], [ "Not Found" ] ];
},
],
[
'request->input seekable',
sub {
my $cb = shift;
my $req = HTTP::Request->new(POST => "http://127.0.0.1/");
$req->content("body");
$req->content_type('text/plain');
$req->content_length(4);