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   2628069 use 5.026000;
  17         184  
3 17     17   752 use utf8;
  17         49  
  17         140  
4 17     17   413 use strict;
  17         23  
  17         311  
5 17     17   80 use warnings;
  17         47  
  17         414  
6 17     17   12504 use HTTP::Tiny;
  17         459595  
  17         725  
7 17     17   12287 use JSON::XS qw/decode_json encode_json/;
  17         92179  
  17         1355  
8 17     17   6511 use Try::Tiny;
  17         26650  
  17         931  
9 17     17   18502 use AWS::Lambda;
  17         163  
  17         761  
10 17     17   7260 use AWS::Lambda::Context;
  17         106  
  17         544  
11 17     17   7127 use AWS::Lambda::ResponseWriter;
  17         39  
  17         574  
12 17     17   142 use Scalar::Util qw(blessed);
  17         34  
  17         772  
13 17     17   89 use Exporter 'import';
  17         35  
  17         27664  
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 28193 my $proto = shift;
27 10   33     528 my $class = ref $proto || $proto;
28 10         98 my %args;
29 10 50 33     273 if (@_ == 1 && ref $_[0] eq 'HASH') {
30 0         0 %args = %{$_[0]};
  0         0  
31             } else {
32 10         328 %args = @_;
33             }
34              
35 10         151 my $api_version = '2018-06-01';
36 10   33     140 my $env_handler = $args{handler} // $ENV{'_HANDLER'} // die '$_HANDLER is not found';
      0        
37 10         175 my ($handler, $function) = split(/[.]/, $env_handler, 2);
38 10   33     160 my $runtime_api = $args{runtime_api} // $ENV{'AWS_LAMBDA_RUNTIME_API'} // die '$AWS_LAMBDA_RUNTIME_API is not found';
      0        
39 10   33     148 my $task_root = $args{task_root} // $ENV{'LAMBDA_TASK_ROOT'} // die '$LAMBDA_TASK_ROOT is not found';
      0        
40 10         381 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             }, $class;
49 10         2627 return $self;
50             }
51              
52             sub handle_events {
53 0     0 0 0 my $self = shift;
54 0 0       0 $self->_init or return;
55 0         0 while(1) {
56 0         0 $self->handle_event;
57             }
58             }
59              
60             sub _init {
61 5     5   12 my $self = shift;
62 5 50       19 if (my $func = $self->{function}) {
63 0         0 return $func;
64             }
65              
66 5         12 my $task_root = $self->{task_root};
67 5         13 my $handler = $self->{handler};
68 5         17 my $name = $self->{function_name};
69             return try {
70             package main;
71 5     5   2210 require "${task_root}/${handler}.pl";
72 4   100     898 my $f = main->can($name) // die "handler $name is not found";
73 3         19 $self->{function} = $f;
74             } catch {
75 2     2   63 $self->lambda_init_error($_);
76 2         28 $self->{function} = sub {};
77 2         48 undef;
78 5         42 };
79             }
80              
81             sub handle_event {
82 5     5 0 173 my $self = shift;
83 5 100       29 $self->_init or return;
84 3         99 my ($payload, $context) = $self->lambda_next;
85             my $response = try {
86 3     3   128 local $AWS::Lambda::context = $context;
87 3         31 local $ENV{_X_AMZN_TRACE_ID} = $context->{trace_id};
88 3         13 $self->{function}->($payload, $context);
89             } catch {
90 1     1   26 my $err = $_;
91 1         66 print STDERR "$err";
92 1         8 $self->lambda_error($err, $context);
93 1         22 bless {}, 'AWS::Lambda::ErrorSentinel';
94 3         57 };
95 3         71 my $ref = ref($response);
96 3 100       13 if ($ref eq 'AWS::Lambda::ErrorSentinel') {
97 1         11 return;
98             }
99 2 100       8 if ($ref eq 'CODE') {
100 1         4 $self->lambda_response_streaming($response, $context);
101             } else {
102 1         4 $self->lambda_response($response, $context);
103             }
104 2         26 return 1;
105             }
106              
107             sub lambda_next {
108 1     1 0 17 my $self = shift;
109 1         205 my $resp = $self->{http}->get($self->{next_event_url});
110 1 50       7290 if (!$resp->{success}) {
111 0         0 die "failed to retrieve the next event: $resp->{status} $resp->{reason}";
112             }
113 1         3 my $h = $resp->{headers};
114 1         25 my $payload = decode_json($resp->{content});
115             return $payload, AWS::Lambda::Context->new(
116             deadline_ms => $h->{'lambda-runtime-deadline-ms'},
117             aws_request_id => $h->{'lambda-runtime-aws-request-id'},
118             invoked_function_arn => $h->{'lambda-runtime-invoked-function-arn'},
119 1         46 trace_id => $h->{'lambda-runtime-trace-id'},
120             );
121             }
122              
123             sub lambda_response {
124 1     1 0 38 my $self = shift;
125 1         4 my ($response, $context) = @_;
126 1         34 my $runtime_api = $self->{runtime_api};
127 1         4 my $api_version = $self->{api_version};
128 1         23 my $request_id = $context->aws_request_id;
129 1         23 my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/response";
130 1         253 my $resp = $self->{http}->post($url, {
131             content => encode_json($response),
132             });
133 1 50       26758 if (!$resp->{success}) {
134 0         0 die "failed to response of execution: $resp->{status} $resp->{reason}";
135             }
136             }
137              
138             sub lambda_response_streaming {
139 1     1 0 41 my $self = shift;
140 1         6 my ($response, $context) = @_;
141 1         28 my $runtime_api = $self->{runtime_api};
142 1         8 my $api_version = $self->{api_version};
143 1         13 my $request_id = $context->aws_request_id;
144 1         12 my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/response";
145 1         3 my $writer = undef;
146             try {
147             $response->(sub {
148 1         6 my $content_type = shift;
149             $writer = AWS::Lambda::ResponseWriter->new(
150             response_url => $url,
151             http => $self->{http},
152 1         29 );
153 1         9 $writer->_request($content_type);
154 1         587 return $writer;
155 1     1   182 });
156             } catch {
157 0     0   0 my $err = $_;
158 0         0 print STDERR "$err";
159 0 0       0 if ($writer) {
160 0         0 $writer->_close_with_error($err);
161             } else {
162 0         0 $self->lambda_error($err, $context);
163             }
164 1         81 };
165 1 50       166 if ($writer) {
166 1         7 my $response = $writer->_handle_response;
167 1 50       74 if (!$response->{success}) {
168 0         0 die "failed to response of execution: $response->{status} $response->{reason}";
169             }
170             }
171             }
172              
173             sub lambda_error {
174 1     1 0 23 my $self = shift;
175 1         9 my ($error, $context) = @_;
176 1         43 my $runtime_api = $self->{runtime_api};
177 1         3 my $api_version = $self->{api_version};
178 1         13 my $request_id = $context->aws_request_id;
179 1         9 my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/error";
180 1   50     37 my $type = blessed($error) // "Error";
181 1         244 my $resp = $self->{http}->post($url, {
182             content => encode_json({
183             errorMessage => "$error",
184             errorType => "$type",
185             }),
186             });
187 1 50       25914 if (!$resp->{success}) {
188 0         0 die "failed to send error of execution: $resp->{status} $resp->{reason}";
189             }
190             }
191              
192             sub lambda_init_error {
193 1     1 0 24 my $self = shift;
194 1         16 my $error = shift;
195 1         35 my $runtime_api = $self->{runtime_api};
196 1         4 my $api_version = $self->{api_version};
197 1         5 my $url = "http://${runtime_api}/${api_version}/runtime/init/error";
198 1   50     40 my $type = blessed($error) // "Error";
199 1         275 my $resp = $self->{http}->post($url, {
200             content => encode_json({
201             errorMessage => "$error",
202             errorType => "$type",
203             }),
204             });
205 1 50       27131 if (!$resp->{success}) {
206 0           die "failed to send error of execution: $resp->{status} $resp->{reason}";
207             }
208             }
209              
210             1;
211             __END__