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