Conduit
view release on metacpan or search on metacpan
lib/Conduit.pm view on Meta::CPAN
part of a program based on L<Future::IO>. It currently supports either a
simple L<HTTP::Message>-based responder or a PSGI application, but the
intention is to allow PAGI and possibly other interface shapes in a later
version.
This is currently B<experimental>, serving also as a testbed for how to design
larger systems using C<Future::IO>, and hopefully soon as a way to test out
the PAGI design.
This module reports basic metrics about received requests and sent responses
via L<Metrics::Any>, as described by L<Conduit::Metrics>.
=cut
use Carp;
use Future::IO 0.17;
use Future::Selector;
use IO::Socket::IP;
use Conduit::Client;
=head1 PARAMETERS
=head2 port
port => $int
TCP port number to listen on for HTTP requests.
Either this or the C<listensock> parameter must be provided; though the latter
is intended for internal and unit-test purposes and will not be otherwise
documented.
=cut
field $listensock :param = undef;
ADJUST :params (
:$port = undef,
) {
defined( $port ) or defined( $listensock ) or
croak "Require either 'port' or 'listensock'";
$listensock //= IO::Socket::IP->new(
LocalPort => $port,
Listen => 5,
ReuseAddr => 1,
);
}
method port () { return $listensock->sockport; }
=head2 responder
responder => $app
$response = await $app->( $request );
A code reference to the responder used for handling requests.
It will be passed an L<HTTP::Request> instance containing the incoming request
and is expected to yield an L<HTTP::Response> instance via a future. As a
small convenience, the server will fill in the C<protocol> and
C<content_length> fields of the response if they are not provided by the
responder.
=head2 psgi_app
psgi_app => $app
A code reference to the L<PSGI> application used for handling requests.
Currently, exactly one of C<responder> or C<psgi_app> must be provided, but
the intention is soon to allow other forms of responders, such as L<PAGI> as
alternatives.
=cut
field $responder :param = undef;
field $psgi_app :param = undef;
ADJUST {
defined $responder or defined $psgi_app or
croak "Require either 'responder' or 'psgi_app'";
defined( $responder ) + defined( $psgi_app ) == 1 or
croak "Require exactly one of 'responder' or 'psgi_app'";
}
=head1 METHODS
=cut
field $selector = Future::Selector->new;
async method listen ()
{
while( my $clientsock = await Future::IO->accept( $listensock ) ) {
my $client;
if( $responder ) {
$client = Conduit::Client::_ForHTTP->new(
responder => $responder,
server => $self,
socket => $clientsock,
);
}
elsif( $psgi_app ) {
$client = Conduit::Client::_ForPSGI->new(
psgi_app => $psgi_app,
server => $self,
socket => $clientsock,
);
}
else {
die "TODO: other app shapes?";
}
$selector->add(
data => $client,
f => $client->run
->else( sub ( $err, @ ) {
( run in 0.566 second using v1.01-cache-2.11-cpan-acf6aa7dc9e )