Newer Older
172 lines | 4.578kb
add files
Yuki Kimoto authored on 2014-03-26
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