The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2005-2011 -- leonerd@leonerd.org.uk

package Net::Async::FastCGI::ServerProtocol;

use strict;
use warnings;

use base qw( Net::Async::FastCGI::Protocol );
use IO::Async::Stream 0.33;

use Net::FastCGI::Constant qw( FCGI_VERSION_1 :type :role :protocol_status );
use Net::FastCGI::Protocol qw(
   build_params parse_params
   parse_begin_request_body
   build_end_request_body
);

use Net::Async::FastCGI::Request;

sub _init
{
   my $self = shift;
   my ( $params ) = @_;

   $self->{fcgi} = delete $params->{fcgi};
   $self->{reqs} = {}; # {$reqid} = $req
}

sub on_closed
{
   my ( $self ) = @_;
   $_->_abort for values %{ $self->{reqs} };

   # TODO: This might want to live in IO::Async::Protocol
   if( my $parent = $self->parent ) {
      $parent->remove_child( $self );
   }
}

sub on_mgmt_record
{
   my $self = shift;
   my ( $type, $rec ) = @_;

   return $self->_get_values( $rec ) if $type == FCGI_GET_VALUES;

   return $self->SUPER::on_mgmt_record( $type, $rec );
}

sub on_record
{
   my $self = shift;
   my ( $reqid, $rec ) = @_;

   my $type = $rec->{type};

   if( $type == FCGI_BEGIN_REQUEST ) {
      ( my $role, $rec->{flags} ) = parse_begin_request_body( $rec->{content} );

      if( $role == FCGI_RESPONDER ) {
         my $req = Net::Async::FastCGI::Request->new( 
            conn => $self,
            fcgi => $self->{fcgi},
            rec  => $rec,
         );
         $self->{reqs}->{$reqid} = $req;
      }
      else {
         $self->write_record( { type => FCGI_END_REQUEST, reqid => $rec->{reqid} }, 
            build_end_request_body( 0, FCGI_UNKNOWN_ROLE )
         );
      }

      return;
   }

   # FastCGI spec says we're supposed to ignore any record apart from
   # FCGI_BEGIN_REQUEST on unrecognised request IDs
   my $req = $self->{reqs}->{$reqid} or return;

   $req->incomingrecord( $rec );
}

sub _req_needs_flush
{
   my $self = shift;

   $self->{gensub_queued}++ or $self->write( sub {
      my ( $self ) = @_;

      undef $self->{gensub_queued};

      my $want_more = 0;

      foreach my $req ( values %{ $self->{reqs} } ) {
         $req->_flush_streams;
         $want_more = 1 if $req->_needs_flush;
      }

      $self->_req_needs_flush if $want_more;

      return undef;
   } );
}

sub _removereq
{
   my $self = shift;
   my ( $reqid ) = @_;

   delete $self->{reqs}->{$reqid};
}

sub _get_values
{
   my $self = shift;
   my ( $rec ) = @_;

   my $content = $rec->{content};

   my $ret = "";

   foreach my $name ( keys %{ parse_params( $content ) } ) {
      my $value = $self->_get_value( $name );
      if( defined $value ) {
         $ret .= build_params( { $name => $value } );
      }
   }

   $self->write_record(
      {
         type  => FCGI_GET_VALUES_RESULT,
         reqid => 0,
      },
      $ret
   );
}

# This is a method so subclasses could hook extra values if they want
sub _get_value
{
   my $self = shift;
   my ( $name ) = @_;

   return 1 if $name eq "FCGI_MPXS_CONNS";

   return $Net::Async::FastCGI::MAX_CONNS if $name eq "FCGI_MAX_CONNS";
   return $Net::Async::FastCGI::MAX_REQS  if $name eq "FCGI_MAX_REQS";

   return undef;
}

0x55AA;