package IO::Async::PSGI;
use Moo;
use Types::Standard qw(CodeRef InstanceOf);
use IO::Async::Loop;
use Future;
use HTTP::Status ();
use Safe::Isa;
use Plack::Middleware::HTTPExceptions;
{
my $logging_requestid_cb;
sub _invoke_logging_cb {
return unless $logging_requestid_cb;
$logging_requestid_cb->(@_);
}
sub logging_requestid_cb {
my ($class,$cb) = @_;
$logging_requestid_cb = $cb
if @_ > 1;
return $logging_requestid_cb;
}
}
our $_current_request_id;
around 'Future::wrap_cb' => sub {
my $orig = shift;
my $cb = $orig->(@_);
my $reqid = $_current_request_id;
return sub {
local $_current_request_id = $reqid;
_invoke_logging_cb($reqid);
$cb->(@_);
};
};
has app => (
is => 'ro',
isa => CodeRef,
required => 1,
);
has _loop => (
is => 'rw',
isa => InstanceOf['IO::Async::Loop'],
default => sub { IO::Async::Loop->new },
);
sub _log {
my ($env,$level,@msg) = @_;
if (my $logger = $env->{'psgix.logger'}) {
$logger->$level(@msg);
}
}
sub psgi_app {
my ($self) = @_;
return sub {
my ($env) = @_;
my $reqid = $env->{'psgix.request_id'};
local $_current_request_id = $reqid;
_invoke_logging_cb($reqid);
my $given_loop = $env->{'io.async.loop'};
if ($given_loop && $given_loop != $self->_loop) {
_log($env,warn => "Event loop changed, re-initialising everything");
$self->_loop($given_loop);
}
$env->{'io.async.loop'} = $self->_loop;
my $f = Future->call($self->app,$env);
return sub {
my ($responder) = @_;
$f->on_done(
sub{
$responder->(@_);
},
);
$f->on_fail(
sub{
my ($exc,@details) = @_;
my $response = $self->on_app_failure($env,$exc,@details);
$responder->($response);
}
);
$f->on_ready(
sub{
if (not $given_loop) {
$self->_loop->stop;
}
undef $f;
}
);
if (not $given_loop and $f and not $f->is_done) {
$self->_loop->run;
}
}
}
}
sub on_app_failure {
my ($self,$env,$exc,@details) = @_;
if ($exc->$_can('as_psgi') or $exc->$_can('code')) {
return Plack::Middleware::HTTPExceptions->new(rethrow=>0)
->transform_error($exc,$env);
} else {
my $message = join ' ', grep {defined} $exc,@details;
_log($env,warn => "Application exception: $message");
return [
500,
['Content-type' => 'text/plain'],
[$message],
];
}
}
1;