File Coverage

blib/lib/Async/Microservice/Time.pm
Criterion Covered Total %
statement 29 64 45.3
branch 0 8 0.0
condition 0 7 0.0
subroutine 10 19 52.6
pod 6 7 85.7
total 45 105 42.8


line stmt bran cond sub pod time code
1             package Async::Microservice::Time;
2              
3 2     2   97661 use strict;
  2         6  
  2         107  
4 2     2   15 use warnings;
  2         5  
  2         60  
5 2     2   54 use 5.010;
  2         10  
6 2     2   10 use utf8;
  2         4  
  2         15  
7 2     2   654 use Moose;
  2         487980  
  2         20  
8              
9             with qw(Async::Microservice);
10              
11             our $VERSION = 0.01;
12              
13 2     2   18562 use DateTime;
  2         1014410  
  2         127  
14 2     2   23 use Time::HiRes qw(time);
  2         4  
  2         23  
15 2     2   1519 use AnyEvent;
  2         6109  
  2         92  
16 2     2   1272 use AnyEvent::Future;
  2         26258  
  2         130  
17 2     2   1214 use Future::AsyncAwait;
  2         6615  
  2         14  
18              
19             sub service_name {
20 0     0 1   return 'async-microservice-time';
21             }
22              
23             sub get_routes {
24             return (
25 0     0 1   'datetime' => {
26             defaults => {
27             GET => 'GET_datetime',
28             POST => 'POST_datetime',
29             },
30             },
31             'datetime/:time_zone' => {
32             defaults => {
33             GET => 'GET_datetime',
34             POST => 'POST_datetime',
35             },
36             },
37             'datetime/:time_zone_part1/:time_zone_part2' => {
38             defaults => { GET => 'GET_datetime_capture', },
39             validations => {
40             time_zone_part1 => qr{^\w+$},
41             time_zone_part2 => qr{^\w+$},
42             },
43             },
44             'epoch' => {defaults => {GET => 'GET_epoch'}},
45             'sleep' => {defaults => {GET => 'GET_sleep'}},
46             );
47             }
48              
49             sub GET_datetime {
50 0     0 1   my ( $self, $this_req ) = @_;
51 0   0       my $time_zone = $this_req->params->{time_zone} // 'UTC';
52 0           return $self->_get_datetime_time_zone( $this_req, $time_zone );
53             }
54              
55             sub GET_datetime_capture {
56 0     0 0   my ( $self, $this_req, $match ) = @_;
57             my $time_zone = $this_req->params->{time_zone_part1} . '/'
58 0           . $this_req->params->{time_zone_part2};
59 0           return $self->_get_datetime_time_zone( $this_req, $time_zone );
60             }
61              
62             sub _get_datetime_time_zone {
63 0     0     my ( $self, $this_req, $time_zone ) = @_;
64 0           my $time_dt = eval { DateTime->now( time_zone => $time_zone ); };
  0            
65 0 0         if ($@) {
66             return [
67 0           405,
68             [],
69             { err_status => 405,
70             err_msg => $@,
71             }
72             ];
73             }
74 0           return [ 200, [], _datetime_as_data($time_dt) ];
75             }
76              
77             sub POST_datetime {
78 0     0 1   my ($self, $this_req) = @_;
79 0           my $epoch = eval {$this_req->json_content->{epoch}};
  0            
80 0 0         if (!defined($epoch)) {
81             return [
82 0   0       405,
83             [],
84             { err_status => 405,
85             err_msg => $@ || 'epoch data missing',
86             }
87             ];
88             }
89 0 0         if ($epoch !~ m/^-?[0-9]+$/) {
90             return [
91 0           405,
92             [],
93             { err_status => 405,
94             err_msg => 'epoch not a number',
95             }
96             ];
97             }
98 0           return [200, [], _datetime_as_data(DateTime->from_epoch(epoch => $epoch))];
99             }
100              
101             sub GET_epoch {
102 0     0 1   my ( $self, $this_req ) = @_;
103 0           return [ 200, [], { epoch => time() } ];
104             }
105              
106 0     0 1   async sub GET_sleep {
107 0           my ( $self, $this_req ) = @_;
108 0           my $start_time = time();
109 0   0       my $sleep_time = ( $this_req->params->{duration} // rand(10) ) + 0;
110 0 0         if ( $sleep_time <= 0 ) {
111             return [
112 0           405,
113             [],
114             { err_status => 405,
115             err_msg => 'invalid sleep duration',
116             }
117             ];
118             }
119              
120 0           await AnyEvent::Future->new_delay( after => $sleep_time );
121 0           my $stop_time = time();
122             return [
123 0           200,
124             [],
125             { start => $start_time,
126             stop => $stop_time,
127             duration => ( $stop_time - $start_time ),
128             }
129             ];
130             }
131              
132             sub _datetime_as_data {
133 0     0     my ($dt) = @_;
134             return {
135 0           datetime => $dt->strftime('%Y-%m-%d %H:%M:%S %z'),
136             date => $dt->strftime('%Y-%m-%d'),
137             time => $dt->strftime('%H:%M:%S'),
138             time_zone => $dt->strftime('%z'),
139             time_zone_name => $dt->strftime('%Z'),
140             day => $dt->strftime('%d'),
141             month => $dt->strftime('%m'),
142             year => $dt->strftime('%Y'),
143             hour => $dt->strftime('%H'),
144             minute => $dt->strftime('%M'),
145             second => $dt->strftime('%S'),
146             epoch => $dt->epoch,
147             };
148             }
149              
150             __PACKAGE__->meta->make_immutable;
151              
152             1;
153              
154             __END__
155              
156             =head1 NAME
157              
158             Async::Microservice::Time - example time async microservice
159              
160             =head1 SYNOPSYS
161              
162             # can be started using:
163             plackup --port 8085 -Ilib --access-log /dev/null --server Twiggy bin/async-microservice-time.psgi
164              
165             curl "http://localhost:8085/v1/hcheck" -H "accept: application/json"
166             curl "http://localhost:8085/v1/epoch" -H "accept: application/json"
167             curl "http://localhost:8085/v1/datetime?time_zone=local" -H "accept: application/json"
168              
169             =head1 DESCRIPTION
170              
171             This is an example asynchronous http micro service using L<Async::Microservice>.
172             View the source code it's minimal.
173              
174             =head1 METHODS
175              
176             =head2 service_name
177              
178             Just a name, used to identify process and look for OpenAPI documentation.
179              
180             =head2 get_routes
181              
182             L<Path::Router> configuration for dispatching
183              
184             =head2 http response methods
185              
186             =head3 GET_datetime
187              
188             L<https://time.meon.eu/v1/datetime>
189              
190             =head3 POST_datetime
191              
192             $ curl -X POST "https://time.meon.eu/v1/datetime" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"epoch\":-42}"
193             {
194             "date" : "1969-12-31",
195             "datetime" : "1969-12-31 23:59:18 +0000",
196             "day" : "31",
197             "epoch" : -42,
198             "hour" : "23",
199             "minute" : "59",
200             "month" : "12",
201             "second" : "18",
202             "time" : "23:59:18",
203             "time_zone" : "+0000",
204             "time_zone_name" : "UTC",
205             "year" : "1969"
206             }
207              
208             =head3 GET_epoch
209              
210             L<https://time.meon.eu/v1/epoch>
211              
212             =head3 GET_sleep
213              
214             L<https://time.meon.eu/v1/sleep?duration=2.5>
215              
216             This is the only parallel processed reponse method (the other ones are
217             pure CPU-only bound) that sleep given (or random) number of seconds and
218             only then returns the request response with when it started and how long
219             it took. Normally this the same as what is in duration parameter, but in
220             case the server is overloaded with requests, the event loop may call the
221             timer handler much later than the duration. Try:
222              
223             ab -n 1000 -c 500 http://localhost:8085/v1/sleep?duration=3
224             Connection Times (ms)
225             min mean[+/-sd] median max
226             Connect: 0 259 432.8 21 1033
227             Processing: 3001 3090 72.5 3061 3253
228             Waiting: 3001 3090 72.5 3061 3253
229             Total: 3022 3349 394.1 3155 4065
230              
231             Then try to run together with 100% CPU load:
232              
233             ab -q -n 10000 -c 50 http://localhost:8085/v1/datetime
234              
235             =head3 the rest
236              
237             Check out L<Async::Microservice> for built-in http response methods.
238              
239             =head1 SEE ALSO
240              
241             F<t/02_Async-Microservice-Time.t> for an example how to test this service.
242              
243             =cut