File Coverage

blib/lib/AWS/Lambda/Bootstrap.pm
Criterion Covered Total %
statement 124 144 86.1
branch 14 26 53.8
condition 9 27 33.3
subroutine 25 28 89.2
pod 0 8 0.0
total 172 233 73.8


line stmt bran cond sub pod time code
1             package AWS::Lambda::Bootstrap;
2 17     17   2779562 use 5.026000;
  17         209  
3 17     17   751 use utf8;
  17         53  
  17         139  
4 17     17   394 use strict;
  17         36  
  17         333  
5 17     17   80 use warnings;
  17         38  
  17         570  
6 17     17   12834 use HTTP::Tiny;
  17         472937  
  17         725  
7 17     17   13051 use JSON::XS qw/decode_json encode_json/;
  17         94635  
  17         1129  
8 17     17   6722 use Try::Tiny;
  17         27744  
  17         944  
9 17     17   19415 use AWS::Lambda;
  17         211  
  17         745  
10 17     17   7525 use AWS::Lambda::Context;
  17         66  
  17         581  
11 17     17   7248 use AWS::Lambda::ResponseWriter;
  17         47  
  17         630  
12 17     17   107 use Scalar::Util qw(blessed);
  17         40  
  17         835  
13 17     17   95 use Exporter 'import';
  17         59  
  17         28800  
14              
15             our @EXPORT = ('bootstrap');
16              
17             sub bootstrap {
18 0     0   0 my $handler = shift;
19 0         0 my $bootstrap = AWS::Lambda::Bootstrap->new(
20             handler => $handler,
21             );
22 0         0 $bootstrap->handle_events;
23             }
24              
25             sub new {
26 10     10 0 40809 my $proto = shift;
27 10   33     515 my $class = ref $proto || $proto;
28 10         116 my %args;
29 10 50 33     313 if (@_ == 1 && ref $_[0] eq 'HASH') {
30 0         0 %args = %{$_[0]};
  0         0  
31             } else {
32 10         321 %args = @_;
33             }
34              
35 10         103 my $api_version = '2018-06-01';
36 10   33     163 my $env_handler = $args{handler} // $ENV{'_HANDLER'} // die '$_HANDLER is not found';
      0        
37 10         174 my ($handler, $function) = split(/[.]/, $env_handler, 2);
38 10   33     192 my $runtime_api = $args{runtime_api} // $ENV{'AWS_LAMBDA_RUNTIME_API'} // die '$AWS_LAMBDA_RUNTIME_API is not found';
      0        
39 10   33     169 my $task_root = $args{task_root} // $ENV{'LAMBDA_TASK_ROOT'} // die '$LAMBDA_TASK_ROOT is not found';
      0        
40 10         470 my $self = bless +{
41             task_root => $task_root,
42             handler => $handler,
43             function_name => $function,
44             runtime_api => $runtime_api,
45             api_version => $api_version,
46             next_event_url => "http://${runtime_api}/${api_version}/runtime/invocation/next",
47             http => HTTP::Tiny->new(
48             # XXX: I want to disable timeout, but it seems HTTP::Tiny does not support it.
49             # So, I set a long timeout.
50             timeout => 365*24*60*60, # 365 days
51             ),
52             }, $class;
53 10         2719 return $self;
54             }
55              
56             sub handle_events {
57 0     0 0 0 my $self = shift;
58 0 0       0 $self->_init or return;
59 0         0 while(1) {
60 0         0 $self->handle_event;
61             }
62             }
63              
64             sub _init {
65 5     5   14 my $self = shift;
66 5 50       22 if (my $func = $self->{function}) {
67 0         0 return $func;
68             }
69              
70 5         13 my $task_root = $self->{task_root};
71 5         14 my $handler = $self->{handler};
72 5         12 my $name = $self->{function_name};
73             return try {
74             package main;
75 5     5   2305 require "${task_root}/${handler}.pl";
76 4   100     932 my $f = main->can($name) // die "handler $name is not found";
77 3         16 $self->{function} = $f;
78             } catch {
79 2     2   66 $self->lambda_init_error($_);
80 2         37 $self->{function} = sub {};
81 2         46 undef;
82 5         50 };
83             }
84              
85             sub handle_event {
86 5     5 0 207 my $self = shift;
87 5 100       35 $self->_init or return;
88 3         100 my ($payload, $context) = $self->lambda_next;
89             my $response = try {
90 3     3   148 local $AWS::Lambda::context = $context;
91 3         45 local $ENV{_X_AMZN_TRACE_ID} = $context->{trace_id};
92 3         14 $self->{function}->($payload, $context);
93             } catch {
94 1     1   27 my $err = $_;
95 1         78 print STDERR "$err";
96 1         14 $self->lambda_error($err, $context);
97 1         23 bless {}, 'AWS::Lambda::ErrorSentinel';
98 3         62 };
99 3         84 my $ref = ref($response);
100 3 100       16 if ($ref eq 'AWS::Lambda::ErrorSentinel') {
101 1         11 return;
102             }
103 2 100       11 if ($ref eq 'CODE') {
104 1         4 $self->lambda_response_streaming($response, $context);
105             } else {
106 1         5 $self->lambda_response($response, $context);
107             }
108 2         28 return 1;
109             }
110              
111             sub lambda_next {
112 1     1 0 18 my $self = shift;
113 1         228 my $resp = $self->{http}->get($self->{next_event_url});
114 1 50       9754 if (!$resp->{success}) {
115 0         0 die "failed to retrieve the next event: $resp->{status} $resp->{reason}";
116             }
117 1         6 my $h = $resp->{headers};
118 1         37 my $payload = decode_json($resp->{content});
119             return $payload, AWS::Lambda::Context->new(
120             deadline_ms => $h->{'lambda-runtime-deadline-ms'},
121             aws_request_id => $h->{'lambda-runtime-aws-request-id'},
122             invoked_function_arn => $h->{'lambda-runtime-invoked-function-arn'},
123 1         85 trace_id => $h->{'lambda-runtime-trace-id'},
124             );
125             }
126              
127             sub lambda_response {
128 1     1 0 24 my $self = shift;
129 1         10 my ($response, $context) = @_;
130 1         33 my $runtime_api = $self->{runtime_api};
131 1         21 my $api_version = $self->{api_version};
132 1         13 my $request_id = $context->aws_request_id;
133 1         14 my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/response";
134 1         206 my $resp = $self->{http}->post($url, {
135             content => encode_json($response),
136             });
137 1 50       27394 if (!$resp->{success}) {
138 0         0 die "failed to response of execution: $resp->{status} $resp->{reason}";
139             }
140             }
141              
142             sub lambda_response_streaming {
143 1     1 0 21 my $self = shift;
144 1         11 my ($response, $context) = @_;
145 1         22 my $runtime_api = $self->{runtime_api};
146 1         3 my $api_version = $self->{api_version};
147 1         13 my $request_id = $context->aws_request_id;
148 1         14 my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/response";
149 1         3 my $writer = undef;
150             try {
151             $response->(sub {
152 1         6 my $content_type = shift;
153             $writer = AWS::Lambda::ResponseWriter->new(
154             response_url => $url,
155             http => $self->{http},
156 1         37 );
157 1         6 $writer->_request($content_type);
158 1         643 return $writer;
159 1     1   178 });
160             } catch {
161 0     0   0 my $err = $_;
162 0         0 print STDERR "$err";
163 0 0       0 if ($writer) {
164 0         0 $writer->_close_with_error($err);
165             } else {
166 0         0 $self->lambda_error($err, $context);
167             }
168 1         76 };
169 1 50       156 if ($writer) {
170 1         6 my $response = $writer->_handle_response;
171 1 50       66 if (!$response->{success}) {
172 0         0 die "failed to response of execution: $response->{status} $response->{reason}";
173             }
174             }
175             }
176              
177             sub lambda_error {
178 1     1 0 9 my $self = shift;
179 1         22 my ($error, $context) = @_;
180 1         41 my $runtime_api = $self->{runtime_api};
181 1         13 my $api_version = $self->{api_version};
182 1         36 my $request_id = $context->aws_request_id;
183 1         16 my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/error";
184 1   50     50 my $type = blessed($error) // "Error";
185 1         266 my $resp = $self->{http}->post($url, {
186             content => encode_json({
187             errorMessage => "$error",
188             errorType => "$type",
189             }),
190             });
191 1 50       30953 if (!$resp->{success}) {
192 0         0 die "failed to send error of execution: $resp->{status} $resp->{reason}";
193             }
194             }
195              
196             sub lambda_init_error {
197 1     1 0 37 my $self = shift;
198 1         12 my $error = shift;
199 1         37 my $runtime_api = $self->{runtime_api};
200 1         4 my $api_version = $self->{api_version};
201 1         13 my $url = "http://${runtime_api}/${api_version}/runtime/init/error";
202 1   50     46 my $type = blessed($error) // "Error";
203 1         271 my $resp = $self->{http}->post($url, {
204             content => encode_json({
205             errorMessage => "$error",
206             errorType => "$type",
207             }),
208             });
209 1 50       30027 if (!$resp->{success}) {
210 0           die "failed to send error of execution: $resp->{status} $resp->{reason}";
211             }
212             }
213              
214             1;
215             __END__