File: _FutureIO.pm

package info (click to toggle)
libnet-prometheus-perl 0.14-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 304 kB
  • sloc: perl: 1,847; makefile: 8
file content (105 lines) | stat: -rw-r--r-- 2,798 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#  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, 2022-2024 -- leonerd@leonerd.org.uk

package Net::Prometheus::_FutureIO 0.14;

use v5.14;
use warnings;

use Future::IO 0.11;
use Future::Utils qw( repeat );

# TODO: Consider if we want to use Future::AsyncAwait to make this all a lot neater

my %running_clients;

sub start
{
   my $pkg = shift;
   my ( $prometheus, $listensock ) = @_;

   Future::IO->HAVE_MULTIPLE_FILEHANDLES or
      die "Net::Prometheus::_FutureIO requires a Future::IO implementation that supports multiple filehandles\n";

   return ( repeat {
      return Future::IO->accept( $listensock )->then( sub {
         my ( $clientsock ) = @_;
         my $fileno = $clientsock->fileno;

         my $f = $pkg->serve( $prometheus, $clientsock );
         $running_clients{$fileno} = $f;

         $f->on_done( sub {
            delete $running_clients{$fileno};
         });
         $f->on_fail( sub {
            warn "Net::Prometheus builtin HTTP server failed for [$fileno]: $_[0]";
            delete $running_clients{$fileno};
         });

         return Future->done;
      });
   } while => sub { !$_[0]->failure } )->on_fail( sub {
      warn "Net::Prometheus builtin HTTP server crashed: $_[0]";
   });
}

my %HTTP_CODES = (
   200 => "OK",
   400 => "Bad Request",
   405 => "Method Not Allowed",
);

sub serve
{
   my $pkg = shift;
   my ( $prometheus, $fh ) = @_;

   my $buf = "";
   my $f = repeat {
      Future::IO->sysread( $fh, 8192 )->then( sub {
         $buf .= $_[0];
         Future->done;
      } );
   } until => sub { $_[0]->failure or $buf =~ m/\x0d\x0a\x0d\x0a/ };

   # Parse request and generate a response code
   $f = $f->then( sub {
      my ( $req ) = $buf =~ m/^(.*\x0d\x0a\x0d\x0a)/s;
      ( my ( $firstline, $headers ) = split m/\x0d\x0a/, $req, 2 ) == 2 or
         return Future->done( 400 );

      my ( $method, $path, $proto ) = split m/\s+/, $firstline;

      return Future->done( 400 ) unless $proto =~ m(^HTTP/1\.[01]$);

      return Future->done( 200 ) if $method eq "GET";
      return Future->done( 200, 1 ) if $method eq "HEAD";
      return Future->done( 405 );
   });

   # Render an actual response and send it
   $f = $f->then( sub {
      my ( $code, $is_head ) = @_;

      my $body = "";
      $body .= $prometheus->render if $code == 200;

      my $response = "HTTP/1.0 $code $HTTP_CODES{$code}\n";
      $response .= "Content-Type: text/plain\n";
      $response .= sprintf "Content-Length: %d\n", length $body;
      $response .= "\n";

      $response =~ s/\n/\x0d\x0a/g;

      $response .= $body unless $is_head;

      return Future::IO->syswrite( $fh, $response );
   });

   return $f;
}

0x55AA;