File Coverage

blib/lib/Net/Async/HTTP/Server.pm
Criterion Covered Total %
statement 59 59 100.0
branch 7 8 87.5
condition 1 2 50.0
subroutine 15 15 100.0
pod 3 3 100.0
total 85 87 97.7


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