File Coverage

blib/lib/Net/Async/HTTP/Server.pm
Criterion Covered Total %
statement 58 58 100.0
branch 7 8 87.5
condition 1 2 50.0
subroutine 15 15 100.0
pod 3 3 100.0
total 84 86 97.6


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2013-2023 -- leonerd@leonerd.org.uk
5              
6             package Net::Async::HTTP::Server 0.14;
7              
8 13     13   2236642 use v5.14;
  13         111  
9 13     13   101 use warnings;
  13         29  
  13         397  
10 13     13   103 use base qw( IO::Async::Listener );
  13         31  
  13         7052  
11             IO::Async::Listener->VERSION( '0.61' );
12              
13 13     13   191680 use Carp;
  13         31  
  13         692  
14              
15 13     13   5810 use Net::Async::HTTP::Server::Protocol;
  13         40  
  13         528  
16 13     13   6373 use Net::Async::HTTP::Server::Request;
  13         64  
  13         627  
17              
18 13         118 use Metrics::Any 0.05 '$metrics',
19             strict => 1,
20 13     13   109 name_prefix => [qw( http server )];
  13         314  
21              
22             $metrics->make_gauge( requests_in_flight =>
23             description => "Count of the number of requests received that have not yet been completed",
24             # no labels
25             );
26             $metrics->make_counter( requests =>
27             description => "Number of HTTP requests received",
28             labels => [qw( method )],
29             );
30             $metrics->make_counter( responses =>
31             description => "Number of HTTP responses served",
32             labels => [qw( method code )],
33             );
34             $metrics->make_timer( request_duration =>
35             description => "Duration of time spent processing requests",
36             # no labels
37             );
38             $metrics->make_distribution( response_bytes =>
39             description => "The size in bytes of responses sent",
40             units => "bytes",
41             # no labels
42             );
43              
44             =head1 NAME
45              
46             C - serve HTTP with C
47              
48             =head1 SYNOPSIS
49              
50             use Net::Async::HTTP::Server;
51             use IO::Async::Loop;
52              
53             use HTTP::Response;
54              
55             my $loop = IO::Async::Loop->new();
56              
57             my $httpserver = Net::Async::HTTP::Server->new(
58             on_request => sub {
59             my $self = shift;
60             my ( $req ) = @_;
61              
62             my $response = HTTP::Response->new( 200 );
63             $response->add_content( "Hello, world!\n" );
64             $response->content_type( "text/plain" );
65             $response->content_length( length $response->content );
66              
67             $req->respond( $response );
68             },
69             );
70              
71             $loop->add( $httpserver );
72              
73             $httpserver->listen(
74             addr => { family => "inet6", socktype => "stream", port => 8080 },
75             )->get
76              
77             $loop->run;
78              
79             =head1 DESCRIPTION
80              
81             This module allows a program to respond asynchronously to HTTP requests, as
82             part of a program based on L. An object in this class listens on a
83             single port and invokes the C callback or subclass method whenever
84             an HTTP request is received, allowing the program to respond to it.
85              
86             For accepting HTTP connections via L and L, see also
87             L.
88              
89             =head2 Metrics
90              
91             I
92              
93             This module reports basic metrics about received requests and sent responses
94             via L.
95              
96             =cut
97              
98             =head1 EVENTS
99              
100             =head2 on_request $req
101              
102             Invoked when a new HTTP request is received. It will be passed a
103             L object.
104              
105             =cut
106              
107             =head1 PARAMETERS
108              
109             The following named parameters may be passed to C or C:
110              
111             =head2 request_class => STRING
112              
113             Gives the name of the class that C will construct. This is
114             provided as an alternative to overriding the C method, for the
115             case where no other methods need overriding or other behaviour changed.
116              
117             =cut
118              
119             =head1 METHODS
120              
121             As a small subclass of L, this class does not provide many
122             new methods of its own. The superclass provides useful methods to control the
123             basic operation of this server.
124              
125             Specifically, see the L method on how to actually
126             bind the server to a listening socket to make it accept requests.
127              
128             =cut
129              
130             sub _init
131             {
132 10     10   57382 my $self = shift;
133 10         31 my ( $params ) = @_;
134              
135 10         33 $params->{handle_class} = "Net::Async::HTTP::Server::Protocol";
136              
137 10   50     93 $params->{request_class} ||= "Net::Async::HTTP::Server::Request";
138              
139 10         72 $self->SUPER::_init( $params );
140             }
141              
142             sub configure
143             {
144 22     22 1 957 my $self = shift;
145 22         66 my %params = @_;
146              
147 22         76 foreach (qw( on_request request_class )) {
148 44 100       165 $self->{$_} = delete $params{$_} if exists $params{$_};
149             }
150              
151 22         106 $self->SUPER::configure( %params );
152             }
153              
154             sub _add_to_loop
155             {
156 10     10   26199 my $self = shift;
157              
158 10 50       42 $self->can_event( "on_request" ) or croak "Expected either a on_request callback or an ->on_request method";
159              
160 10         147 $self->SUPER::_add_to_loop( @_ );
161             }
162              
163             sub on_accept
164             {
165 18     18 1 432747 my $self = shift;
166 18         61 my ( $conn ) = @_;
167              
168             $conn->configure(
169             on_closed => sub {
170 8     8   1889 my $conn = shift;
171 8         34 $conn->on_closed();
172              
173 8         38 $conn->remove_from_parent;
174             },
175 18         131 );
176              
177 18         1466 $self->add_child( $conn );
178              
179 18         5641 return $conn;
180             }
181              
182             =head2 make_request
183              
184             $request = $server->make_request( @args )
185              
186             Invoked by the protocol stream handler to create a new request object
187             representing an incoming request. This is provided as a method for subclasses
188             to overload, if they wish to represent requests with subclasses of the basic
189             request representation.
190              
191             =cut
192              
193             sub make_request
194             {
195 25     25 1 179 my $self = shift;
196 25         204 return $self->{request_class}->new( @_ );
197             }
198              
199             sub _received_request
200             {
201 25     25   144 my $self = shift;
202 25         56 my ( $request ) = @_;
203              
204 25 100       91 if( $metrics ) {
205 10         140 $metrics->inc_gauge( requests_in_flight => );
206              
207 10         1217 $metrics->inc_counter( requests => [ method => $request->method ] );
208 10         676 $self->{request_received_timestamp}{$request} = $self->loop->time;
209             }
210              
211 25         349 $self->invoke_event( on_request => $request );
212             }
213              
214             sub _done_request
215             {
216 19     19   205 my $self = shift;
217 19         47 my ( $request ) = @_;
218              
219 19 100       75 if( $metrics ) {
220 1         20 my $received_timestamp = delete $self->{request_received_timestamp}{$request};
221              
222 1         7 $metrics->dec_gauge( requests_in_flight => );
223              
224 1         68 $metrics->inc_counter( responses => [ method => $request->method, code => $request->response_status_code ] );
225 1         84 $metrics->report_timer( request_duration => $self->loop->time - $received_timestamp );
226 1         103 $metrics->report_distribution( response_bytes => $request->bytes_written );
227             }
228             }
229              
230             =head1 TODO
231              
232             =over 2
233              
234             =item *
235              
236             Don't use L objects as underlying implementation
237              
238             =item *
239              
240             Consider how to do streaming request inbound
241              
242             =item *
243              
244             Lots more testing
245              
246             =back
247              
248             =cut
249              
250             =head1 AUTHOR
251              
252             Paul Evans
253              
254             =cut
255              
256             0x55AA;