File Coverage

lib/Dancer/Plugin/TimeoutManager.pm
Criterion Covered Total %
statement 65 67 97.0
branch 18 22 81.8
condition 18 21 85.7
subroutine 12 12 100.0
pod 0 1 0.0
total 113 123 91.8


line stmt bran cond sub pod time code
1             package Dancer::Plugin::TimeoutManager;
2              
3 1     1   314298 use strict;
  1         3  
  1         29  
4 1     1   5 use warnings;
  1         2  
  1         52  
5             our $VERSION = '0.10'; # VERSION
6              
7 1     1   7 use Try::Tiny;
  1         2  
  1         61  
8 1     1   8 use Dancer ':syntax';
  1         2  
  1         5  
9 1     1   329 use Dancer::Exception ':all';
  1         2  
  1         162  
10 1     1   468 use Dancer::Plugin;
  1         1360  
  1         72  
11 1     1   7 use Carp 'croak';
  1         2  
  1         39  
12 1     1   568 use List::MoreUtils qw( none);
  1         12100  
  1         12  
13              
14              
15             if ( exists $ENV{DISABLE_DANCER_TIMEOUT} && $ENV{DISABLE_DANCER_TIMEOUT} ) {
16             set disable_timeout => 1;
17             }
18              
19             #get the timeout from headers
20             hook(before => sub {
21             var header_timeout => request->header('X-Dancer-Timeout');
22             });
23              
24             register 'timeout' => \&timeout;
25              
26             register_exception ('InvalidArgumentNumber',
27             message_pattern => "the number of arguments must 3 or 4, you've got %s",
28             );
29              
30             register_exception ('InvalidMethod',
31             message_pattern => "method must be one in get, put, post, delete and %s is used as a method",
32             );
33              
34              
35             my @authorized_methods = ('get', 'post', 'put', 'delete');
36              
37             =method exception_message
38              
39             return the exception message
40             This method can be used to catch the exception if the code used already contained a try catch
41              
42             =cut
43              
44             sub exception_message {
45              
46 7     7 0 16 return 'Route Timeout Detected';
47             }
48              
49             =method timeout
50              
51             Method that manage the timeout on a dancer request
52              
53             =cut
54              
55             sub timeout {
56 8     8   4770 my ($timeout,$method, $pattern, @rest);
57              
58 8 100       27 if (scalar(@_) == 4) {
    50          
59 5         23 ($timeout,$method, $pattern, @rest) = @_;
60             }
61             elsif(scalar(@_) == 3) {
62 3         7 ($method, $pattern, @rest) = @_;
63             }
64             else {
65 0         0 raise InvalidMethod => scalar(@_);
66             }
67              
68 8         13 my $code;
69 8 50       21 for my $e (@rest) { $code = $e if (ref($e) eq 'CODE') }
  8         30  
70 8         12 my $request;
71              
72             #if method is not valid an exception is done
73 8 100   11   39 if ( none { $_ eq lc($method) } @authorized_methods ) {
  11         33  
74 1         6 raise InvalidMethod => $method;
75             }
76              
77 7         32 my $exception_message = exception_message();
78             my $timeout_route = sub {
79 19     19   6196 my $response;
80              
81             # maximum possible timeout, set in the plugin config
82 19         89 my $conf = plugin_setting();
83              
84             #if timeout is not defined but a value is set in the headers for timeout
85 19         399 my $request_timeout = 0;
86 19 100       75 $request_timeout = $timeout if (defined $timeout);
87 19 100 100     129 $request_timeout = vars->{header_timeout} if (!defined $timeout && defined vars->{header_timeout});
88             $request_timeout = $conf->{max_timeout}
89 19 100 66     300 if (defined $conf->{max_timeout} && defined $request_timeout && $request_timeout && $conf->{max_timeout} < $request_timeout);
      100        
      66        
90              
91             # if timeout is not defined or equal 0 the timeout manager is not used
92 19         39 my $timeout_exception;
93              
94 19 100 100     101 if (!$request_timeout || (defined setting('disable_timeout') && setting('disable_timeout'))) {
      100        
95 9         155 $response = $code->();
96             }
97             else {
98             try {
99 10         598 local $SIG{ALRM} = sub { croak ($exception_message); };
  8         16004318  
100 10         83 alarm($request_timeout);
101              
102 10         48 $response = $code->();
103 2         2000631 alarm(0);
104             }
105             catch {
106 8         320 $timeout_exception = $_;
107 10         447 };
108              
109 10         330 alarm(0);
110             }
111              
112             # Timeout detected
113 19 100 66     31002216 if ($timeout_exception && $timeout_exception =~ /$exception_message/) {
114 8         112 my $response_with_timeout = Dancer::Response->new(
115             status => 408,
116             content => "Request Timeout : more than $request_timeout seconds elapsed."
117             );
118 8         811 return $response_with_timeout;
119             }
120              
121             # Preserve exceptions caught during route call
122 11 50       50 croak $@ if $@;
123              
124             # else everything is allright
125 11         180 return $response;
126 7         24 };
127              
128 7         13 my @compiled_rest;
129              
130 7         19 for my $e (@rest) {
131 7 50       18 if (ref($e) eq 'CODE') {
132 7         16 push @compiled_rest, $timeout_route;
133             }
134             else {
135 0         0 push @compiled_rest, $e;
136             }
137             }
138              
139             # declare the route in Dancer's registry
140 7         24 any [$method] => $pattern, @compiled_rest;
141             }
142              
143             register_plugin;
144              
145             1;
146             __END__