add files
|
1 |
package Mojo::Server::Morbo; |
2 |
use Mojo::Base -base; |
|
3 | ||
4 |
# "Linda: With Haley's Comet out of ice, Earth is experiencing the devastating |
|
5 |
# effects of sudden, intense global warming. |
|
6 |
# Morbo: Morbo is pleased but sticky." |
|
7 |
use Mojo::Home; |
|
8 |
use Mojo::Server::Daemon; |
|
9 |
use POSIX 'WNOHANG'; |
|
10 | ||
11 |
has watch => sub { [qw(lib templates)] }; |
|
12 | ||
13 |
sub check_file { |
|
14 |
my ($self, $file) = @_; |
|
15 | ||
16 |
# Check if modify time and/or size have changed |
|
17 |
my ($size, $mtime) = (stat $file)[7, 9]; |
|
18 |
return undef unless defined $mtime; |
|
19 |
my $cache = $self->{cache} ||= {}; |
|
20 |
my $stats = $cache->{$file} ||= [$^T, $size]; |
|
21 |
return undef if $mtime <= $stats->[0] && $size == $stats->[1]; |
|
22 |
return !!($cache->{$file} = [$mtime, $size]); |
|
23 |
} |
|
24 | ||
25 |
sub run { |
|
26 |
my ($self, $app) = @_; |
|
27 | ||
28 |
# Clean manager environment |
|
29 |
local $SIG{CHLD} = sub { $self->_reap }; |
|
30 |
local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = sub { |
|
31 |
$self->{finished} = 1; |
|
32 |
kill 'TERM', $self->{running} if $self->{running}; |
|
33 |
}; |
|
34 |
unshift @{$self->watch}, $app; |
|
35 |
$self->{modified} = 1; |
|
36 | ||
37 |
# Prepare and cache listen sockets for smooth restarting |
|
38 |
my $daemon = Mojo::Server::Daemon->new(silent => 1)->start->stop; |
|
39 | ||
40 |
$self->_manage while !$self->{finished} || $self->{running}; |
|
41 |
exit 0; |
|
42 |
} |
|
43 | ||
44 |
sub _manage { |
|
45 |
my $self = shift; |
|
46 | ||
47 |
# Discover files |
|
48 |
my @files; |
|
49 |
for my $watch (@{$self->watch}) { |
|
50 |
if (-d $watch) { |
|
51 |
my $home = Mojo::Home->new->parse($watch); |
|
52 |
push @files, $home->rel_file($_) for @{$home->list_files}; |
|
53 |
} |
|
54 |
elsif (-r $watch) { push @files, $watch } |
|
55 |
} |
|
56 | ||
57 |
# Check files |
|
58 |
for my $file (@files) { |
|
59 |
next unless $self->check_file($file); |
|
60 |
say qq{File "$file" changed, restarting.} if $ENV{MORBO_VERBOSE}; |
|
61 |
kill 'TERM', $self->{running} if $self->{running}; |
|
62 |
$self->{modified} = 1; |
|
63 |
} |
|
64 | ||
65 |
$self->_reap; |
|
66 |
delete $self->{running} if $self->{running} && !kill 0, $self->{running}; |
|
67 |
$self->_spawn if !$self->{running} && delete $self->{modified}; |
|
68 |
sleep 1; |
|
69 |
} |
|
70 | ||
71 |
sub _reap { |
|
72 |
my $self = shift; |
|
73 |
while ((my $pid = waitpid -1, WNOHANG) > 0) { delete $self->{running} } |
|
74 |
} |
|
75 | ||
76 |
sub _spawn { |
|
77 |
my $self = shift; |
|
78 | ||
79 |
# Fork |
|
80 |
my $manager = $$; |
|
81 |
$ENV{MORBO_REV}++; |
|
82 |
die "Can't fork: $!" unless defined(my $pid = fork); |
|
83 | ||
84 |
# Manager |
|
85 |
return $self->{running} = $pid if $pid; |
|
86 | ||
87 |
# Worker |
|
88 |
$SIG{CHLD} = 'DEFAULT'; |
|
89 |
$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub { $self->{finished} = 1 }; |
|
90 |
my $daemon = Mojo::Server::Daemon->new; |
|
91 |
$daemon->load_app($self->watch->[0]); |
|
92 |
$daemon->silent(1) if $ENV{MORBO_REV} > 1; |
|
93 |
$daemon->start; |
|
94 |
my $loop = $daemon->ioloop; |
|
95 |
$loop->recurring( |
|
96 |
1 => sub { shift->stop if !kill(0, $manager) || $self->{finished} }); |
|
97 |
$loop->start; |
|
98 |
exit 0; |
|
99 |
} |
|
100 | ||
101 |
1; |
|
102 | ||
103 |
=encoding utf8 |
|
104 | ||
105 |
=head1 NAME |
|
106 | ||
107 |
Mojo::Server::Morbo - DOOOOOOOOOOOOOOOOOOM! |
|
108 | ||
109 |
=head1 SYNOPSIS |
|
110 | ||
111 |
use Mojo::Server::Morbo; |
|
112 | ||
113 |
my $morbo = Mojo::Server::Morbo->new; |
|
114 |
$morbo->run('/home/sri/myapp.pl'); |
|
115 | ||
116 |
=head1 DESCRIPTION |
|
117 | ||
118 |
L<Mojo::Server::Morbo> is a full featured, self-restart capable non-blocking |
|
119 |
I/O HTTP and WebSocket server, built around the very well tested and reliable |
|
120 |
L<Mojo::Server::Daemon>, with IPv6, TLS, Comet (long polling), keep-alive, |
|
121 |
connection pooling, timeout, cookie, multipart and multiple event loop |
|
122 |
support. Note that the server uses signals for process management, so you |
|
123 |
should avoid modifying signal handlers in your applications. |
|
124 | ||
125 |
To start applications with it you can use the L<morbo> script. |
|
126 | ||
127 |
$ morbo myapp.pl |
|
128 |
Server available at http://127.0.0.1:3000. |
|
129 | ||
130 |
For better scalability (epoll, kqueue) and to provide IPv6 as well as TLS |
|
131 |
support, the optional modules L<EV> (4.0+), L<IO::Socket::IP> (0.16+) and |
|
132 |
L<IO::Socket::SSL> (1.75+) will be used automatically by L<Mojo::IOLoop> if |
|
133 |
they are installed. Individual features can also be disabled with the |
|
134 |
MOJO_NO_IPV6 and MOJO_NO_TLS environment variables. |
|
135 | ||
136 |
See L<Mojolicious::Guides::Cookbook> for more. |
|
137 | ||
138 |
=head1 ATTRIBUTES |
|
139 | ||
140 |
L<Mojo::Server::Morbo> implements the following attributes. |
|
141 | ||
142 |
=head2 watch |
|
143 | ||
144 |
my $watch = $morbo->watch; |
|
145 |
$morbo = $morbo->watch(['/home/sri/myapp']); |
|
146 | ||
147 |
Files and directories to watch for changes, defaults to the application script |
|
148 |
as well as the C<lib> and C<templates> directories in the current working |
|
149 |
directory. |
|
150 | ||
151 |
=head1 METHODS |
|
152 | ||
153 |
L<Mojo::Server::Morbo> inherits all methods from L<Mojo::Base> and implements |
|
154 |
the following new ones. |
|
155 | ||
156 |
=head2 check_file |
|
157 | ||
158 |
my $bool = $morbo->check_file('/home/sri/lib/MyApp.pm'); |
|
159 | ||
160 |
Check if file has been modified since last check. |
|
161 | ||
162 |
=head2 run |
|
163 | ||
164 |
$morbo->run('script/myapp'); |
|
165 | ||
166 |
Run server for application. |
|
167 | ||
168 |
=head1 SEE ALSO |
|
169 | ||
170 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
171 | ||
172 |
=cut |