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 )