File Coverage

blib/lib/Mojolicious/Plugin/Narada.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::Narada;
2              
3 1     1   14461 use strict;
  1         2  
  1         41  
4 1     1   4 use warnings;
  1         1  
  1         23  
5              
6 1     1   493 use version; our $VERSION = qv('0.4.0'); # REMINDER: update Changes
  1         1207  
  1         4  
7              
8             # REMINDER: update dependencies in Build.PL
9 1     1   425 use Mojo::Base 'Mojolicious::Plugin';
  1         6495  
  1         7  
10              
11 1     1   1135 use MojoX::Log::Fast;
  1         74855  
  1         15  
12 1     1   265 use Narada::Config qw( get_config get_config_line );
  0            
  0            
13             use Narada::Lock qw( unlock );
14             use Scalar::Util qw( weaken );
15              
16             my ($Log, $Ident);
17             our $IN_CB = 0;
18              
19              
20             sub register {
21             my ($self, $app, $conf) = @_;
22              
23             $Log = MojoX::Log::Fast->new($conf->{log});
24             $Ident = $Log->ident();
25              
26             # Replace default logger with Log::Fast.
27             $app->log($Log);
28              
29             # Load Mojo-specific config files.
30             if ($app->can('secrets')) {
31             $app->secrets([split /\n/ms, get_config('cookie.secret')]);
32             } else {
33             $app->secret(get_config_line('cookie.secret'));
34             }
35             $app->config(hypnotoad => {
36             listen => [split /\n/ms, get_config('hypnotoad/listen')],
37             proxy => get_config_line('hypnotoad/proxy'),
38             accepts => get_config_line('hypnotoad/accepts'),
39             workers => get_config_line('hypnotoad/workers'),
40             pid_file => 'var/hypnotoad.pid',
41             });
42              
43             # * Fix url->path and url->base->path.
44             # * Set correct ident while handler runs.
45             # * unlock() if handler died.
46             my $realbase = Mojo::Path->new( get_config_line('basepath') )
47             ->trailing_slash(0)
48             ->leading_slash(1)
49             ->to_string;
50             $app->hook(around_dispatch => sub {
51             my ($next, $c) = @_;
52             my $url = $c->req->url;
53             my $base = $url->base->path;
54             my $path = $url->path;
55             if ($base eq q{} && $path =~ m{\A\Q$realbase\E(.*)\z}mso) {
56             $path->parse($1);
57             }
58             $base->parse($realbase);
59             $path->leading_slash(1);
60             $Log->ident($url->path);
61             my $err = eval { $next->(); 1 } ? undef : $@;
62             unlock();
63             die $err if defined $err; ## no critic(RequireCarping)
64             });
65              
66             $app->helper(proxy => sub { return _proxy(0, @_) });
67             $app->helper(weak_proxy => sub { return _proxy(1, @_) });
68              
69             return;
70             }
71              
72             sub _proxy {
73             my ($is_weak, $this, $cb, @p) = @_;
74             if ($is_weak) {
75             weaken($this);
76             }
77             my $is_global_cb= ref $this eq 'Mojolicious::Controller';
78             my $ident = $is_global_cb ? $Ident : $Log->ident;
79             my $__warn__ = $SIG{__WARN__};
80             return sub {
81             return if !$this;
82             my $cur_ident = $Log->ident($ident);
83             local $SIG{__WARN__} = $__warn__;
84             my $err = eval { local $IN_CB=1; $cb->($this, @p, @_); 1 } ? undef : $@;
85             if (defined $err) {
86             $Log->ident($ident);
87             if (!$IN_CB) {
88             unlock()
89             }
90             if ($is_global_cb || $is_weak) {
91             die $err; ## no critic(RequireCarping)
92             }
93             else {
94             $this->reply->exception($err);
95             }
96             }
97             $Log->ident($cur_ident);
98             };
99             }
100              
101              
102             1; # Magic true value required at end of module
103             __END__
104              
105             =encoding utf8
106              
107             =head1 NAME
108              
109             Mojolicious::Plugin::Narada - Narada configuration plugin
110              
111              
112             =head1 SYNOPSIS
113              
114             # Mojolicious
115             $self->plugin('Narada');
116             $self->plugin(Narada => (log => Log::Fast->global));
117              
118             # Mojolicious::Lite
119             plugin 'Narada';
120             plugin Narada => (log => Log::Fast->global);
121              
122             # Global timer
123             package MyApp;
124             sub startup {
125             my $app = shift;
126             Mojo::IOLoop->timer(0 => $app->proxy(sub { say 'Next tick.' }));
127             }
128              
129             # Request-related timer
130             package MyApp::MyController;
131             sub myaction {
132             my $c = shift;
133             $c->render_later;
134             Mojo::IOLoop->timer(1 => $c->weak_proxy(sub { say 'Alive' }));
135             Mojo::IOLoop->timer(2 => $c->proxy(sub {
136             $c->render(text => 'Delayed by 2 seconds!');
137             }));
138             Mojo::IOLoop->timer(3 => $c->weak_proxy(sub { say 'Dead' }));
139             }
140              
141             =head1 DESCRIPTION
142              
143             L<Mojolicious::Plugin::Narada> is a plugin that configure L<Mojolicious>
144             to work in L<Narada> project management environment.
145              
146             Also this plugin add helpers C<proxy> and C<weak_proxy>, and you B<MUST>
147             use them to wrap all callbacks you setup for handling delayed events like
148             timers or I/O (both global in your app and related to requests in your
149             actions).
150              
151             There is also one feature unrelated to Narada - if callback started by any
152             action throw unhandled exception it will be sent to browser using same
153             C<< $c->reply->exception >> as it already works for actions without
154             delayed response.
155              
156             =over
157              
158             =item Logging
159              
160             L<Mojolicious> default L<Mojo::Log> replaced with L<MojoX::Log::Fast> to
161             support logging to project-local syslog daemon in addition to files.
162             In most cases it works as drop-in replacement and doesn't require any
163             modifications in user code.
164              
165             Also it set C<< $app->log->ident() >> to C<< $c->req->url->path >> to
166             ease log file analyse.
167              
168             =item Configuration
169              
170             You should manually add these lines to C<./you_app> starting script before
171             call to C<< Mojolicious::Commands->start_app() >>:
172              
173             use Narada::Config qw( get_config_line );
174             # mode should be set here because it's used before executing MyApp::startup()
175             local $ENV{MOJO_MODE} = get_config_line('mode');
176              
177             Config file C<config/cookie.secret> automatically loaded and used to
178             initialize C<< $app->secrets() >> (each line of file became separate
179             param).
180              
181             Config file C<config/basepath> automatically loaded and used to fix
182             C<< $c->req->url->base->path >> and C<< $c->req->url->path >> to
183             guarantee their consistency in any environment:
184              
185             =over
186              
187             =item * url->path doesn't contain base->path
188              
189             =item * url->path does have leading slash
190              
191             =item * url->base->path set to content of config/basepath
192              
193             =back
194              
195              
196             These config files automatically loaded from C<config/hypnotoad/*>
197             and used to initialize C<< $app->config(hypnotoad) >>:
198              
199             listen
200             proxy
201             accepts
202             workers
203              
204             Also hypnotoad configured to keep it lock/pid files in C<var/>.
205              
206             =item Locking
207              
208             C<unlock()> will be automatically called after all actions and callbacks,
209             even if they throw unhandled exception.
210              
211             =back
212              
213              
214             =head1 OPTIONS
215              
216             L<Mojolicious::Plugin::Narada> supports the following options.
217              
218             =head2 log
219              
220             plugin Narada => (log => Log::Fast->global);
221              
222             Value for L<MojoX::Log::Fast>->new().
223              
224              
225             =head1 METHODS
226              
227             L<Mojolicious::Plugin::Narada> inherits all methods from
228             L<Mojolicious::Plugin> and implements the following new ones.
229              
230             =head2 register
231              
232             $plugin->register(Mojolicious->new);
233             $plugin->register(Mojolicious->new, {log => Log::Fast->global});
234              
235             Register hooks in L<Mojolicious> application.
236              
237              
238             =head1 SEE ALSO
239              
240             L<Narada>, L<MojoX::Log::Fast>, L<Mojolicious>.
241              
242              
243             =head1 BUGS AND LIMITATIONS
244              
245             No bugs have been reported.
246              
247              
248             =head1 SUPPORT
249              
250             Please report any bugs or feature requests through the web interface at
251             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Mojolicious-Plugin-Narada>.
252             I will be notified, and then you'll automatically be notified of progress
253             on your bug as I make changes.
254              
255             You can also look for information at:
256              
257             =over
258              
259             =item * RT: CPAN's request tracker
260              
261             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Mojolicious-Plugin-Narada>
262              
263             =item * AnnoCPAN: Annotated CPAN documentation
264              
265             L<http://annocpan.org/dist/Mojolicious-Plugin-Narada>
266              
267             =item * CPAN Ratings
268              
269             L<http://cpanratings.perl.org/d/Mojolicious-Plugin-Narada>
270              
271             =item * Search CPAN
272              
273             L<http://search.cpan.org/dist/Mojolicious-Plugin-Narada/>
274              
275             =back
276              
277              
278             =head1 AUTHOR
279              
280             Alex Efros C<< <powerman@cpan.org> >>
281              
282              
283             =head1 LICENSE AND COPYRIGHT
284              
285             Copyright 2013,2014-2015 Alex Efros <powerman@cpan.org>.
286              
287             This program is distributed under the MIT (X11) License:
288             L<http://www.opensource.org/licenses/mit-license.php>
289              
290             Permission is hereby granted, free of charge, to any person
291             obtaining a copy of this software and associated documentation
292             files (the "Software"), to deal in the Software without
293             restriction, including without limitation the rights to use,
294             copy, modify, merge, publish, distribute, sublicense, and/or sell
295             copies of the Software, and to permit persons to whom the
296             Software is furnished to do so, subject to the following
297             conditions:
298              
299             The above copyright notice and this permission notice shall be
300             included in all copies or substantial portions of the Software.
301              
302             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
303             EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
304             OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
305             NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
306             HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
307             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
308             FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
309             OTHER DEALINGS IN THE SOFTWARE.
310