1 | package Mojo::Server::Daemon;
|
---|
2 | use Mojo::Base 'Mojo::Server';
|
---|
3 |
|
---|
4 | use Carp 'croak';
|
---|
5 | use Mojo::IOLoop;
|
---|
6 | use Mojo::IOLoop::Stream::HTTPServer;
|
---|
7 | use Mojo::IOLoop::Stream::WebSocketServer;
|
---|
8 | use Mojo::URL;
|
---|
9 | use Scalar::Util 'weaken';
|
---|
10 |
|
---|
11 | use constant DEBUG => $ENV{MOJO_SERVER_DEBUG} || 0;
|
---|
12 |
|
---|
13 | has acceptors => sub { [] };
|
---|
14 | has [qw(backlog max_clients silent)];
|
---|
15 | has inactivity_timeout => sub { $ENV{MOJO_INACTIVITY_TIMEOUT} // 15 };
|
---|
16 | has ioloop => sub { Mojo::IOLoop->singleton };
|
---|
17 | has listen => sub { [split ',', $ENV{MOJO_LISTEN} || 'http://*:3000'] };
|
---|
18 | has max_requests => 100;
|
---|
19 |
|
---|
20 | sub DESTROY {
|
---|
21 | return if Mojo::Util::_global_destruction();
|
---|
22 | my $self = shift;
|
---|
23 | my $loop = $self->ioloop;
|
---|
24 | $loop->remove($_) for keys %{$self->{connections} || {}}, @{$self->acceptors};
|
---|
25 | }
|
---|
26 |
|
---|
27 | sub close_connections {
|
---|
28 | my $self = shift;
|
---|
29 | my $loop = $self->ioloop;
|
---|
30 | $_->max_requests(1)
|
---|
31 | for map { $loop->stream($_) || () } keys %{$self->{connections} || {}};
|
---|
32 | }
|
---|
33 |
|
---|
34 | sub ports { [map { $_[0]->ioloop->acceptor($_)->port } @{$_[0]->acceptors}] }
|
---|
35 |
|
---|
36 | sub run {
|
---|
37 | my $self = shift;
|
---|
38 |
|
---|
39 | # Make sure the event loop can be stopped in regular intervals
|
---|
40 | my $loop = $self->ioloop;
|
---|
41 | my $int = $loop->recurring(1 => sub { });
|
---|
42 | local $SIG{INT} = local $SIG{TERM} = sub { $loop->stop };
|
---|
43 | $self->start->ioloop->start;
|
---|
44 | $loop->remove($int);
|
---|
45 | }
|
---|
46 |
|
---|
47 | sub start {
|
---|
48 | my $self = shift;
|
---|
49 |
|
---|
50 | my $loop = $self->ioloop;
|
---|
51 | if (my $max = $self->max_clients) { $loop->max_connections($max) }
|
---|
52 |
|
---|
53 | # Resume accepting connections
|
---|
54 | if (my $servers = $self->{servers}) {
|
---|
55 | push @{$self->acceptors}, $loop->acceptor(delete $servers->{$_})
|
---|
56 | for keys %$servers;
|
---|
57 | }
|
---|
58 |
|
---|
59 | # Start listening
|
---|
60 | elsif (!@{$self->acceptors}) {
|
---|
61 | $self->app->server($self);
|
---|
62 | $self->_listen($_) for @{$self->listen};
|
---|
63 | }
|
---|
64 |
|
---|
65 | return $self;
|
---|
66 | }
|
---|
67 |
|
---|
68 | sub stop {
|
---|
69 | my $self = shift;
|
---|
70 |
|
---|
71 | # Suspend accepting connections but keep listen sockets open
|
---|
72 | my $loop = $self->ioloop;
|
---|
73 | while (my $id = shift @{$self->acceptors}) {
|
---|
74 | my $server = $self->{servers}{$id} = $loop->acceptor($id);
|
---|
75 | $loop->remove($id);
|
---|
76 | $server->stop;
|
---|
77 | }
|
---|
78 |
|
---|
79 | return $self;
|
---|
80 | }
|
---|
81 |
|
---|
82 | sub _debug { $_[0]->app->log->debug($_[2]) if $_[0]{connections}{$_[1]}{tx} }
|
---|
83 |
|
---|
84 | sub _listen {
|
---|
85 | my ($self, $listen) = @_;
|
---|
86 |
|
---|
87 | my $url = Mojo::URL->new($listen);
|
---|
88 | my $proto = $url->protocol;
|
---|
89 | croak qq{Invalid listen location "$listen"}
|
---|
90 | unless $proto eq 'http' || $proto eq 'https' || $proto eq 'http+unix';
|
---|
91 |
|
---|
92 | my $query = $url->query;
|
---|
93 | my $options = {
|
---|
94 | backlog => $self->backlog,
|
---|
95 | stream_class => 'Mojo::IOLoop::Stream::HTTPServer'
|
---|
96 | };
|
---|
97 | $options->{$_} = $query->param($_) for qw(fd single_accept reuse);
|
---|
98 | if ($proto eq 'http+unix') { $options->{path} = $url->host }
|
---|
99 | else {
|
---|
100 | if ((my $host = $url->host) ne '*') { $options->{address} = $host }
|
---|
101 | if (my $port = $url->port) { $options->{port} = $port }
|
---|
102 | }
|
---|
103 | $options->{"tls_$_"} = $query->param($_) for qw(ca ciphers version);
|
---|
104 | /^(.*)_(cert|key)$/ and $options->{"tls_$2"}{$1} = $query->param($_)
|
---|
105 | for @{$query->names};
|
---|
106 | if (my $cert = $query->param('cert')) { $options->{'tls_cert'}{''} = $cert }
|
---|
107 | if (my $key = $query->param('key')) { $options->{'tls_key'}{''} = $key }
|
---|
108 | my $verify = $query->param('verify');
|
---|
109 | $options->{tls_verify} = hex $verify if defined $verify;
|
---|
110 | $options->{tls} = $proto eq 'https';
|
---|
111 |
|
---|
112 | weaken $self;
|
---|
113 | push @{$self->acceptors}, $self->ioloop->server(
|
---|
114 | $options => sub {
|
---|
115 | my ($loop, $stream, $id) = @_;
|
---|
116 |
|
---|
117 | my $c = $self->{connections}{$id} = {};
|
---|
118 | warn "-- Accept $id (@{[$stream->handle->peerhost]})\n" if DEBUG;
|
---|
119 | $stream->timeout($self->inactivity_timeout);
|
---|
120 | $stream->max_requests($self->max_requests);
|
---|
121 | weaken $stream->app($self)->{app};
|
---|
122 |
|
---|
123 | $stream->on(close => sub { $self && $self->_remove($id) });
|
---|
124 | $stream->on(error =>
|
---|
125 | sub { $self && $self->app->log->error(pop) && $self->_remove($id) });
|
---|
126 | $stream->on(request => sub { $self->_request($id, pop) });
|
---|
127 | $stream->on(start => sub { $c->{tx} = pop->connection($id) });
|
---|
128 | $stream->on(timeout => sub { $self->_debug($id, 'Inactivity timeout') });
|
---|
129 | $stream->on(upgrade => sub { $self->_upgrade($id, pop) });
|
---|
130 | }
|
---|
131 | );
|
---|
132 |
|
---|
133 | return if $self->silent;
|
---|
134 | $self->app->log->info(qq{Listening at "$url"});
|
---|
135 | $query->pairs([]);
|
---|
136 | $url->host('127.0.0.1') if $url->host eq '*';
|
---|
137 | say 'Server available at ', $options->{path} // $url;
|
---|
138 | }
|
---|
139 |
|
---|
140 | sub _remove {
|
---|
141 | my ($self, $id) = @_;
|
---|
142 | $self->ioloop->remove($id);
|
---|
143 | delete $self->{connections}{$id};
|
---|
144 | }
|
---|
145 |
|
---|
146 | sub _request {
|
---|
147 | my ($self, $id, $tx) = @_;
|
---|
148 | if (my $error = $tx->error) { $self->_debug($id, $error->{message}) }
|
---|
149 |
|
---|
150 | weaken $self;
|
---|
151 | $tx->on(finish => sub { delete $self->{connections}{$id}{tx} });
|
---|
152 | $self->emit(request => $tx);
|
---|
153 | }
|
---|
154 |
|
---|
155 | sub _upgrade {
|
---|
156 | my ($self, $id, $ws) = @_;
|
---|
157 |
|
---|
158 | my $loop = $self->ioloop;
|
---|
159 | my $timeout = $loop->stream($id)->timeout;
|
---|
160 | my $stream = $loop->transition($id, 'Mojo::IOLoop::Stream::WebSocketServer');
|
---|
161 | $stream->timeout($timeout);
|
---|
162 |
|
---|
163 | weaken $self;
|
---|
164 | $stream->on(timeout => sub { $self->_debug($id, 'Inactivity timeout') });
|
---|
165 | $stream->on(close => sub { $self && $self->_remove($id) });
|
---|
166 | $stream->on(
|
---|
167 | error => sub { $self && $self->app->log->error(pop) && $self->_remove($id) }
|
---|
168 | );
|
---|
169 |
|
---|
170 | $self->{connections}{$id} = {tx => $ws};
|
---|
171 | $stream->process($ws);
|
---|
172 | }
|
---|
173 |
|
---|
174 | 1;
|
---|
175 |
|
---|
176 | =encoding utf8
|
---|
177 |
|
---|
178 | =head1 NAME
|
---|
179 |
|
---|
180 | Mojo::Server::Daemon - Non-blocking I/O HTTP and WebSocket server
|
---|
181 |
|
---|
182 | =head1 SYNOPSIS
|
---|
183 |
|
---|
184 | use Mojo::Server::Daemon;
|
---|
185 |
|
---|
186 | my $daemon = Mojo::Server::Daemon->new(listen => ['http://*:8080']);
|
---|
187 | $daemon->unsubscribe('request')->on(request => sub {
|
---|
188 | my ($daemon, $tx) = @_;
|
---|
189 |
|
---|
190 | # Request
|
---|
191 | my $method = $tx->req->method;
|
---|
192 | my $path = $tx->req->url->path;
|
---|
193 |
|
---|
194 | # Response
|
---|
195 | $tx->res->code(200);
|
---|
196 | $tx->res->headers->content_type('text/plain');
|
---|
197 | $tx->res->body("$method request for $path!");
|
---|
198 |
|
---|
199 | # Resume transaction
|
---|
200 | $tx->resume;
|
---|
201 | });
|
---|
202 | $daemon->run;
|
---|
203 |
|
---|
204 | =head1 DESCRIPTION
|
---|
205 |
|
---|
206 | L<Mojo::Server::Daemon> is a full featured, highly portable non-blocking I/O
|
---|
207 | HTTP and WebSocket server, with IPv6, TLS, SNI, Comet (long polling), keep-alive
|
---|
208 | and multiple event loop support.
|
---|
209 |
|
---|
210 | For better scalability (epoll, kqueue) and to provide non-blocking name
|
---|
211 | resolution, SOCKS5 as well as TLS support, the optional modules L<EV> (4.0+),
|
---|
212 | L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and
|
---|
213 | L<IO::Socket::SSL> (2.009+) will be used automatically if possible. Individual
|
---|
214 | features can also be disabled with the C<MOJO_NO_NNR>, C<MOJO_NO_SOCKS> and
|
---|
215 | C<MOJO_NO_TLS> environment variables.
|
---|
216 |
|
---|
217 | See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
|
---|
218 |
|
---|
219 | =head1 SIGNALS
|
---|
220 |
|
---|
221 | The L<Mojo::Server::Daemon> process can be controlled at runtime with the
|
---|
222 | following signals.
|
---|
223 |
|
---|
224 | =head2 INT, TERM
|
---|
225 |
|
---|
226 | Shut down server immediately.
|
---|
227 |
|
---|
228 | =head1 EVENTS
|
---|
229 |
|
---|
230 | L<Mojo::Server::Daemon> inherits all events from L<Mojo::Server>.
|
---|
231 |
|
---|
232 | =head1 ATTRIBUTES
|
---|
233 |
|
---|
234 | L<Mojo::Server::Daemon> inherits all attributes from L<Mojo::Server> and
|
---|
235 | implements the following new ones.
|
---|
236 |
|
---|
237 | =head2 acceptors
|
---|
238 |
|
---|
239 | my $acceptors = $daemon->acceptors;
|
---|
240 | $daemon = $daemon->acceptors(['6be0c140ef00a389c5d039536b56d139']);
|
---|
241 |
|
---|
242 | Active acceptor ids.
|
---|
243 |
|
---|
244 | # Check port
|
---|
245 | mu $port = $daemon->ioloop->acceptor($daemon->acceptors->[0])->port;
|
---|
246 |
|
---|
247 | =head2 backlog
|
---|
248 |
|
---|
249 | my $backlog = $daemon->backlog;
|
---|
250 | $daemon = $daemon->backlog(128);
|
---|
251 |
|
---|
252 | Listen backlog size, defaults to C<SOMAXCONN>.
|
---|
253 |
|
---|
254 | =head2 inactivity_timeout
|
---|
255 |
|
---|
256 | my $timeout = $daemon->inactivity_timeout;
|
---|
257 | $daemon = $daemon->inactivity_timeout(5);
|
---|
258 |
|
---|
259 | Maximum amount of time in seconds a connection can be inactive before getting
|
---|
260 | closed, defaults to the value of the C<MOJO_INACTIVITY_TIMEOUT> environment
|
---|
261 | variable or C<15>. Setting the value to C<0> will allow connections to be
|
---|
262 | inactive indefinitely.
|
---|
263 |
|
---|
264 | =head2 ioloop
|
---|
265 |
|
---|
266 | my $loop = $daemon->ioloop;
|
---|
267 | $daemon = $daemon->ioloop(Mojo::IOLoop->new);
|
---|
268 |
|
---|
269 | Event loop object to use for I/O operations, defaults to the global
|
---|
270 | L<Mojo::IOLoop> singleton.
|
---|
271 |
|
---|
272 | =head2 listen
|
---|
273 |
|
---|
274 | my $listen = $daemon->listen;
|
---|
275 | $daemon = $daemon->listen(['https://127.0.0.1:8080']);
|
---|
276 |
|
---|
277 | Array reference with one or more locations to listen on, defaults to the value
|
---|
278 | of the C<MOJO_LISTEN> environment variable or C<http://*:3000> (shortcut for
|
---|
279 | C<http://0.0.0.0:3000>).
|
---|
280 |
|
---|
281 | # Listen on all IPv4 interfaces
|
---|
282 | $daemon->listen(['http://*:3000']);
|
---|
283 |
|
---|
284 | # Listen on all IPv4 and IPv6 interfaces
|
---|
285 | $daemon->listen(['http://[::]:3000']);
|
---|
286 |
|
---|
287 | # Listen on IPv6 interface
|
---|
288 | $daemon->listen(['http://[::1]:4000']);
|
---|
289 |
|
---|
290 | # Listen on IPv4 and IPv6 interfaces
|
---|
291 | $daemon->listen(['http://127.0.0.1:3000', 'http://[::1]:3000']);
|
---|
292 |
|
---|
293 | # Listen on UNIX domain socket "/tmp/myapp.sock" (percent encoded slash)
|
---|
294 | $daemon->listen(['http+unix://%2Ftmp%2Fmyapp.sock']);
|
---|
295 |
|
---|
296 | # File descriptor, as used by systemd
|
---|
297 | $daemon->listen(['http://127.0.0.1?fd=3']);
|
---|
298 |
|
---|
299 | # Allow multiple servers to use the same port (SO_REUSEPORT)
|
---|
300 | $daemon->listen(['http://*:8080?reuse=1']);
|
---|
301 |
|
---|
302 | # Listen on two ports with HTTP and HTTPS at the same time
|
---|
303 | $daemon->listen(['http://*:3000', 'https://*:4000']);
|
---|
304 |
|
---|
305 | # Use a custom certificate and key
|
---|
306 | $daemon->listen(['https://*:3000?cert=/x/server.crt&key=/y/server.key']);
|
---|
307 |
|
---|
308 | # Domain specific certificates and keys (SNI)
|
---|
309 | $daemon->listen(
|
---|
310 | ['https://*:3000?example.com_cert=/x/my.crt&example.com_key=/y/my.key']);
|
---|
311 |
|
---|
312 | # Or even a custom certificate authority
|
---|
313 | $daemon->listen(
|
---|
314 | ['https://*:3000?cert=/x/server.crt&key=/y/server.key&ca=/z/ca.crt']);
|
---|
315 |
|
---|
316 | These parameters are currently available:
|
---|
317 |
|
---|
318 | =over 2
|
---|
319 |
|
---|
320 | =item ca
|
---|
321 |
|
---|
322 | ca=/etc/tls/ca.crt
|
---|
323 |
|
---|
324 | Path to TLS certificate authority file used to verify the peer certificate.
|
---|
325 |
|
---|
326 | =item cert
|
---|
327 |
|
---|
328 | cert=/etc/tls/server.crt
|
---|
329 | mojolicious.org_cert=/etc/tls/mojo.crt
|
---|
330 |
|
---|
331 | Path to the TLS cert file, defaults to a built-in test certificate.
|
---|
332 |
|
---|
333 | =item ciphers
|
---|
334 |
|
---|
335 | ciphers=AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH
|
---|
336 |
|
---|
337 | TLS cipher specification string. For more information about the format see
|
---|
338 | L<https://www.openssl.org/docs/manmaster/apps/ciphers.html#CIPHER-STRINGS>.
|
---|
339 |
|
---|
340 | =item fd
|
---|
341 |
|
---|
342 | fd=3
|
---|
343 |
|
---|
344 | File descriptor with an already prepared listen socket.
|
---|
345 |
|
---|
346 | =item key
|
---|
347 |
|
---|
348 | key=/etc/tls/server.key
|
---|
349 | mojolicious.org_key=/etc/tls/mojo.key
|
---|
350 |
|
---|
351 | Path to the TLS key file, defaults to a built-in test key.
|
---|
352 |
|
---|
353 | =item reuse
|
---|
354 |
|
---|
355 | reuse=1
|
---|
356 |
|
---|
357 | Allow multiple servers to use the same port with the C<SO_REUSEPORT> socket
|
---|
358 | option.
|
---|
359 |
|
---|
360 | =item single_accept
|
---|
361 |
|
---|
362 | single_accept=1
|
---|
363 |
|
---|
364 | Only accept one connection at a time.
|
---|
365 |
|
---|
366 | =item verify
|
---|
367 |
|
---|
368 | verify=0x00
|
---|
369 |
|
---|
370 | TLS verification mode.
|
---|
371 |
|
---|
372 | =item version
|
---|
373 |
|
---|
374 | version=TLSv1_2
|
---|
375 |
|
---|
376 | TLS protocol version.
|
---|
377 |
|
---|
378 | =back
|
---|
379 |
|
---|
380 | =head2 max_clients
|
---|
381 |
|
---|
382 | my $max = $daemon->max_clients;
|
---|
383 | $daemon = $daemon->max_clients(100);
|
---|
384 |
|
---|
385 | Maximum number of accepted connections this server is allowed to handle
|
---|
386 | concurrently, before stopping to accept new incoming connections, passed along
|
---|
387 | to L<Mojo::IOLoop/"max_connections">.
|
---|
388 |
|
---|
389 | =head2 max_requests
|
---|
390 |
|
---|
391 | my $max = $daemon->max_requests;
|
---|
392 | $daemon = $daemon->max_requests(250);
|
---|
393 |
|
---|
394 | Maximum number of keep-alive requests per connection, defaults to C<100>.
|
---|
395 |
|
---|
396 | =head2 silent
|
---|
397 |
|
---|
398 | my $bool = $daemon->silent;
|
---|
399 | $daemon = $daemon->silent($bool);
|
---|
400 |
|
---|
401 | Disable console messages.
|
---|
402 |
|
---|
403 | =head1 METHODS
|
---|
404 |
|
---|
405 | L<Mojo::Server::Daemon> inherits all methods from L<Mojo::Server> and
|
---|
406 | implements the following new ones.
|
---|
407 |
|
---|
408 | =head2 close_connections
|
---|
409 |
|
---|
410 | $daemon->close_connections;
|
---|
411 |
|
---|
412 | Stop accepting new requests and close all connections after finising those
|
---|
413 | currently being processed.
|
---|
414 |
|
---|
415 | =head2 ports
|
---|
416 |
|
---|
417 | my $ports = $daemon->ports;
|
---|
418 |
|
---|
419 | Get all ports this server is currently listening on.
|
---|
420 |
|
---|
421 | # All ports
|
---|
422 | say for @{$daemon->ports};
|
---|
423 |
|
---|
424 | =head2 run
|
---|
425 |
|
---|
426 | $daemon->run;
|
---|
427 |
|
---|
428 | Run server and wait for L</"SIGNALS">.
|
---|
429 |
|
---|
430 | =head2 start
|
---|
431 |
|
---|
432 | $daemon = $daemon->start;
|
---|
433 |
|
---|
434 | Start or resume accepting connections through L</"ioloop">.
|
---|
435 |
|
---|
436 | # Listen on random port
|
---|
437 | my $port = $daemon->listen(['http://127.0.0.1'])->start->ports->[0];
|
---|
438 |
|
---|
439 | # Run multiple web servers concurrently
|
---|
440 | my $daemon1 = Mojo::Server::Daemon->new(listen => ['http://*:3000'])->start;
|
---|
441 | my $daemon2 = Mojo::Server::Daemon->new(listen => ['http://*:4000'])->start;
|
---|
442 | Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
|
---|
443 |
|
---|
444 | =head2 stop
|
---|
445 |
|
---|
446 | $daemon = $daemon->stop;
|
---|
447 |
|
---|
448 | Stop accepting connections through L</"ioloop">.
|
---|
449 |
|
---|
450 | =head1 DEBUGGING
|
---|
451 |
|
---|
452 | You can set the C<MOJO_SERVER_DEBUG> environment variable to get some advanced
|
---|
453 | diagnostics information printed to C<STDERR>.
|
---|
454 |
|
---|
455 | MOJO_SERVER_DEBUG=1
|
---|
456 |
|
---|
457 | =head1 SEE ALSO
|
---|
458 |
|
---|
459 | L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
|
---|
460 |
|
---|
461 | =cut
|
---|