File Coverage

blib/lib/AWS/Lambda/Bootstrap.pm
Criterion Covered Total %
statement 100 114 87.7
branch 10 18 55.5
condition 9 27 33.3
subroutine 22 24 91.6
pod 0 7 0.0
total 141 190 74.2


line stmt bran cond sub pod time code
1             package AWS::Lambda::Bootstrap;
2 9     9   1048690 use 5.026000;
  9         103  
3 9     9   693 use utf8;
  9         42  
  9         56  
4 9     9   245 use strict;
  9         26  
  9         210  
5 9     9   43 use warnings;
  9         21  
  9         262  
6 9     9   6700 use HTTP::Tiny;
  9         308403  
  9         385  
7 9     9   6554 use JSON::XS qw/decode_json encode_json/;
  9         51353  
  9         572  
8 9     9   2737 use Try::Tiny;
  9         10847  
  9         508  
9 9     9   9146 use AWS::Lambda;
  9         70  
  9         427  
10 9     9   3796 use AWS::Lambda::Context;
  9         31  
  9         303  
11 9     9   64 use Scalar::Util qw(blessed);
  9         18  
  9         469  
12 9     9   53 use Exporter 'import';
  9         18  
  9         11777  
13              
14             our @EXPORT = ('bootstrap');
15              
16             sub bootstrap {
17 0     0   0 my $handler = shift;
18 0         0 my $bootstrap = AWS::Lambda::Bootstrap->new(
19             handler => $handler,
20             );
21 0         0 $bootstrap->handle_events;
22             }
23              
24             sub new {
25 8     8 0 13379 my $proto = shift;
26 8   33     428 my $class = ref $proto || $proto;
27 8         94 my %args;
28 8 50 33     233 if (@_ == 1 && ref $_[0] eq 'HASH') {
29 0         0 %args = %{$_[0]};
  0         0  
30             } else {
31 8         230 %args = @_;
32             }
33              
34 8         97 my $api_version = '2018-06-01';
35 8   33     125 my $env_handler = $args{handler} // $ENV{'_HANDLER'} // die '$_HANDLER is not found';
      0        
36 8         154 my ($handler, $function) = split(/[.]/, $env_handler, 2);
37 8   33     171 my $runtime_api = $args{runtime_api} // $ENV{'AWS_LAMBDA_RUNTIME_API'} // die '$AWS_LAMBDA_RUNTIME_API is not found';
      0        
38 8   33     113 my $task_root = $args{task_root} // $ENV{'LAMBDA_TASK_ROOT'} // die '$LAMBDA_TASK_ROOT is not found';
      0        
39 8         375 my $self = bless +{
40             task_root => $task_root,
41             handler => $handler,
42             function_name => $function,
43             runtime_api => $runtime_api,
44             api_version => $api_version,
45             next_event_url => "http://${runtime_api}/${api_version}/runtime/invocation/next",
46             http => HTTP::Tiny->new,
47             }, $class;
48 8         2261 return $self;
49             }
50              
51             sub handle_events {
52 0     0 0 0 my $self = shift;
53 0 0       0 $self->_init or return;
54 0         0 while(1) {
55 0         0 $self->handle_event;
56             }
57             }
58              
59             sub _init {
60 4     4   12 my $self = shift;
61 4 50       19 if (my $func = $self->{function}) {
62 0         0 return $func;
63             }
64              
65 4         11 my $task_root = $self->{task_root};
66 4         11 my $handler = $self->{handler};
67 4         10 my $name = $self->{function_name};
68             return try {
69             package main;
70 4     4   1979 require "${task_root}/${handler}.pl";
71 3   100     743 my $f = main->can($name) // die "handler $name is not found";
72 2         12 $self->{function} = $f;
73             } catch {
74 2     2   76 $self->lambda_init_error($_);
75 2         30 $self->{function} = sub {};
76 2         46 undef;
77 4         48 };
78             }
79              
80             sub handle_event {
81 4     4 0 175 my $self = shift;
82 4 100       24 $self->_init or return;
83 2         76 my ($payload, $context) = $self->lambda_next;
84             my $response = try {
85 2     2   89 local $AWS::Lambda::context = $context;
86 2         30 local $ENV{_X_AMZN_TRACE_ID} = $context->{trace_id};
87 2         12 $self->{function}->($payload, $context);
88             } catch {
89 1     1   32 my $err = $_;
90 1         125 print STDERR "$err";
91 1         13 $self->lambda_error($err, $context);
92 1         26 bless {}, 'AWS::Lambda::ErrorSentinel';
93 2         64 };
94 2 100       50 if (ref($response) eq 'AWS::Lambda::ErrorSentinel') {
95 1         32 return;
96             }
97 1         5 $self->lambda_response($response, $context);
98 1         24 return 1;
99             }
100              
101             sub lambda_next {
102 1     1 0 22 my $self = shift;
103 1         223 my $resp = $self->{http}->get($self->{next_event_url});
104 1 50       8379 if (!$resp->{success}) {
105 0         0 die "failed to retrieve the next event: $resp->{status} $resp->{reason}";
106             }
107 1         5 my $h = $resp->{headers};
108 1         34 my $payload = decode_json($resp->{content});
109             return $payload, AWS::Lambda::Context->new(
110             deadline_ms => $h->{'lambda-runtime-deadline-ms'},
111             aws_request_id => $h->{'lambda-runtime-aws-request-id'},
112             invoked_function_arn => $h->{'lambda-runtime-invoked-function-arn'},
113 1         50 trace_id => $h->{'lambda-runtime-trace-id'},
114             );
115             }
116              
117             sub lambda_response {
118 1     1 0 26 my $self = shift;
119 1         10 my ($response, $context) = @_;
120 1         28 my $runtime_api = $self->{runtime_api};
121 1         3 my $api_version = $self->{api_version};
122 1         18 my $request_id = $context->aws_request_id;
123 1         14 my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/response";
124 1         217 my $resp = $self->{http}->post($url, {
125             content => encode_json($response),
126             });
127 1 50       29074 if (!$resp->{success}) {
128 0         0 die "failed to response of execution: $resp->{status} $resp->{reason}";
129             }
130             }
131              
132             sub lambda_error {
133 1     1 0 27 my $self = shift;
134 1         3 my ($error, $context) = @_;
135 1         37 my $runtime_api = $self->{runtime_api};
136 1         19 my $api_version = $self->{api_version};
137 1         6 my $request_id = $context->aws_request_id;
138 1         19 my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/error";
139 1   50     273 my $resp = $self->{http}->post($url, {
140             content => encode_json({
141             errorMessage => "$error",
142             errorType => blessed($error) // "error",
143             }),
144             });
145 1 50       27478 if (!$resp->{success}) {
146 0         0 die "failed to send error of execution: $resp->{status} $resp->{reason}";
147             }
148             }
149              
150             sub lambda_init_error {
151 1     1 0 43 my $self = shift;
152 1         16 my $error = shift;
153 1         41 my $runtime_api = $self->{runtime_api};
154 1         11 my $api_version = $self->{api_version};
155 1         21 my $url = "http://${runtime_api}/${api_version}/runtime/init/error";
156 1   50     318 my $resp = $self->{http}->post($url, {
157             content => encode_json({
158             errorMessage => "$error",
159             errorType => blessed($error) // "error",
160             }),
161             });
162 1 50       30081 if (!$resp->{success}) {
163 0           die "failed to send error of execution: $resp->{status} $resp->{reason}";
164             }
165             }
166              
167             1;
168             __END__