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 |