view release on metacpan or search on metacpan
0.4.0 2023-11-14T10:45:45Z
- introduce Amazon Linux 2023 based image
0.3.0 2023-09-04T11:31:05Z
- AWS Lambda Functions powered by AWS Graviton2 now available in 6 additional regions #121
- disable HTTP timeout #119
- bump Mozilla::CA 20230821 #120
0.2.0 2023-08-09T11:25:36Z
- bump Mozilla::CA 20230807
- support streaming response
0.1.2 2023-08-05T22:22:19Z
- bump Mozilla::CA 20230801
- install Carton
0.1.1 2023-08-01T13:32:15Z
- ap-south-2, ap-southeast-4, eu-central-2, eu-south-2 and il-central-1 are available.
0.1.0 2023-07-31T11:31:41Z
- switch build environment from lambci/lambda based to public.ecr.aws/lambda/provided based.
script/bootstrap
t/00_compile.t
t/01_echo.t
t/02_error.t
t/03_init_error.t
t/04_handler_not_found.t
t/10_lambda_next.t
t/11_lambda_response.t
t/12_lambda_error.t
t/13_lambda_init_error.t
t/14_streaming.t
t/15_lambda_response_streaming.t
t/20_psgi.t
t/21_psgi_response_streaming.t
t/lib/BootstrapMock.pm
t/test_handlers/echo.pl
t/test_handlers/error.pl
t/test_handlers/init_error.pl
t/test_handlers/streaming.pl
t/testdata/alb-base64-request.json
t/testdata/alb-get-request.json
t/testdata/alb-post-request.json
t/testdata/apigateway-base64-request.json
t/testdata/apigateway-get-request.json
t/testdata/apigateway-post-request.json
t/testdata/apigateway-v2-base64-request.json
t/testdata/apigateway-v2-get-request.json
t/testdata/apigateway-v2-post-request.json
t/testdata/function-urls-get-request.json
Finally, create new function using awscli.
$ aws --region "$REGION" --profile "$PROFILE" lambda create-function \
--function-name "hello-perl" \
--zip-file "fileb://handler.zip" \
--handler "handler.handle" \
--runtime provided.al2023 \
--role arn:aws:iam::xxxxxxxxxxxx:role/service-role/lambda-custom-runtime-perl-role \
--layers "arn:aws:lambda:$REGION:445285296882:layer:perl-5-38-runtime-al2023-x86_64:1"
It also supports [response streaming](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html).
sub handle {
my ($payload, $context) = @_;
return sub {
my $responder = shift;
my $writer = $responder->('application/json');
$writer->write('{"foo": "bar"}');
$writer->close;
};
}
lib/AWS/Lambda.pm view on Meta::CPAN
Finally, create new function using awscli.
$ aws --region "$REGION" --profile "$PROFILE" lambda create-function \
--function-name "hello-perl" \
--zip-file "fileb://handler.zip" \
--handler "handler.handle" \
--runtime provided.al2023 \
--role arn:aws:iam::xxxxxxxxxxxx:role/service-role/lambda-custom-runtime-perl-role \
--layers "arn:aws:lambda:$REGION:445285296882:layer:perl-5-38-runtime-al2023-x86_64:1"
It also supports L<response streaming|https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html>.
sub handle {
my ($payload, $context) = @_;
return sub {
my $responder = shift;
my $writer = $responder->('application/json');
$writer->write('{"foo": "bar"}');
$writer->close;
};
}
lib/AWS/Lambda/Bootstrap.pm view on Meta::CPAN
my $err = $_;
print STDERR "$err";
$self->lambda_error($err, $context);
bless {}, 'AWS::Lambda::ErrorSentinel';
};
my $ref = ref($response);
if ($ref eq 'AWS::Lambda::ErrorSentinel') {
return;
}
if ($ref eq 'CODE') {
$self->lambda_response_streaming($response, $context);
} else {
$self->lambda_response($response, $context);
}
return 1;
}
sub lambda_next {
my $self = shift;
my $resp = $self->{http}->get($self->{next_event_url});
if (!$resp->{success}) {
lib/AWS/Lambda/Bootstrap.pm view on Meta::CPAN
my $request_id = $context->aws_request_id;
my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/response";
my $resp = $self->{http}->post($url, {
content => encode_json($response),
});
if (!$resp->{success}) {
die "failed to response of execution: $resp->{status} $resp->{reason}";
}
}
sub lambda_response_streaming {
my $self = shift;
my ($response, $context) = @_;
my $runtime_api = $self->{runtime_api};
my $api_version = $self->{api_version};
my $request_id = $context->aws_request_id;
my $url = "http://${runtime_api}/${api_version}/runtime/invocation/${request_id}/response";
my $writer = undef;
try {
$response->(sub {
my $content_type = shift;
lib/AWS/Lambda/Bootstrap.pm view on Meta::CPAN
my ($payload, $context) = @_;
# handle the event here.
my $result = {};
return $result;
}
C<$context> is an instance of L<AWS::Lambda::Context>.
=head1 RESPONSE STREAMING
It also supports L<response streaming|https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html>.
sub handle {
my ($payload, $context) = @_;
return sub {
my $responder = shift;
my $writer = $responder->('application/json');
$writer->write('{"foo": "bar"}');
$writer->close;
};
}
lib/AWS/Lambda/PSGI.pm view on Meta::CPAN
sub call {
my($self, $env, $ctx) = @_;
# $ctx is added by #26
# fall back to $AWS::Lambda::context because of backward compatibility.
$ctx ||= $AWS::Lambda::context;
if ($self->{invoke_mode} eq "RESPONSE_STREAM") {
my $input = $self->_format_input_v2($env, $ctx);
$input->{'psgi.streaming'} = Plack::Util::TRUE;
my $res = $self->app->($input);
return $self->_handle_response_stream($res);
} else {
my $input = $self->format_input($env, $ctx);
my $res = $self->app->($input);
return $self->format_output($res);
}
}
sub format_input {
lib/AWS/Lambda/PSGI.pm view on Meta::CPAN
$value = join ", ", @$value;
}
$env->{$key} = $value;
}
$env->{'psgi.version'} = [1, 1];
$env->{'psgi.errors'} = *STDERR;
$env->{'psgi.run_once'} = Plack::Util::FALSE;
$env->{'psgi.multithread'} = Plack::Util::FALSE;
$env->{'psgi.multiprocess'} = Plack::Util::FALSE;
$env->{'psgi.streaming'} = Plack::Util::FALSE;
$env->{'psgi.nonblocking'} = Plack::Util::FALSE;
$env->{'psgix.harakiri'} = Plack::Util::TRUE;
$env->{'psgix.input.buffered'} = Plack::Util::TRUE;
# inject the request id that compatible with Plack::Middleware::RequestId
if ($ctx) {
$env->{'psgix.request_id'} = $ctx->aws_request_id;
$env->{'HTTP_X_REQUEST_ID'} = $ctx->aws_request_id;
}
lib/AWS/Lambda/PSGI.pm view on Meta::CPAN
$key = "HTTP_$key";
}
$env->{$key} = $value;
}
$env->{'psgi.version'} = [1, 1];
$env->{'psgi.errors'} = *STDERR;
$env->{'psgi.run_once'} = Plack::Util::FALSE;
$env->{'psgi.multithread'} = Plack::Util::FALSE;
$env->{'psgi.multiprocess'} = Plack::Util::FALSE;
$env->{'psgi.streaming'} = Plack::Util::FALSE;
$env->{'psgi.nonblocking'} = Plack::Util::FALSE;
$env->{'psgix.harakiri'} = Plack::Util::TRUE;
$env->{'psgix.input.buffered'} = Plack::Util::TRUE;
# inject the request id that compatible with Plack::Middleware::RequestId
if ($ctx) {
$env->{'psgix.request_id'} = $ctx->aws_request_id;
$env->{'HTTP_X_REQUEST_ID'} = $ctx->aws_request_id;
}
lib/AWS/Lambda/PSGI.pm view on Meta::CPAN
1;
And then, L<Set up Lambda Proxy Integrations in API Gateway|https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html> or
L<Lambda Functions as ALB Targets|https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html>
=head1 DESCRIPTION
=head2 Streaming Response
L<AWS::Lambda::PSGI> supports L<response streaming|https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html>.
The function urls's invoke mode is configured as C<"RESPONSE_STREAM">, and Lambda environment variable "PERL5_LAMBDA_PSGI_INVOKE_MODE" is set to C<"RESPONSE_STREAM">.
ExampleApi:
Type: AWS::Serverless::Function
Properties:
FunctionUrlConfig:
AuthType: NONE
InvokeMode: RESPONSE_STREAM
Environment:
Variables:
lib/AWS/Lambda/ResponseWriter.pm view on Meta::CPAN
scheme => $scheme,
host => $host,
port => $port,
host_port => $host_port,
uri => $path_query,
headers => {
"host" => $host_port,
"content-type" => $content_type,
"transfer-encoding" => "chunked",
"trailer" => "Lambda-Runtime-Function-Error-Type, Lambda-Runtime-Function-Error-Body",
"lambda-runtime-function-response-mode" => "streaming",
},
header_case => {
"host" => "Host",
"content-type" => "Content-Type",
"transfer-encoding" => "Transfer-Encoding",
"trailer" => "Trailer",
"lambda-runtime-function-response-mode" => "Lambda-Runtime-Function-Response-Mode",
},
};
my $peer = $host;
t/14_streaming.t view on Meta::CPAN
my $response;
my $context;
my $dummy_context = AWS::Lambda::Context->new(
deadline_ms => 1000,
aws_request_id => '8476a536-e9f4-11e8-9739-2dfe598c3fcd',
invoked_function_arn => 'arn:aws:lambda:us-east-2:123456789012:function:custom-runtime',
trace_id => "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419;Sampled=1",
);
my $bootstrap = BootstrapMock->new(
handler => "streaming.handle",
runtime_api => "example.com",
task_root => "$FindBin::Bin/test_handlers",
lambda_next => sub {
return $payload, $dummy_context;
},
lambda_response_streaming => sub {
my $self = shift;
$response = shift;
$context = shift;
},
);
ok $bootstrap->handle_event;
is ref($response), "CODE", "echo handler";
is $context, $dummy_context, "context";
t/15_lambda_response_streaming.t view on Meta::CPAN
code => sub {
my $port = shift;
my $server = Starman::Server->new;
my $app = sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $headers = $req->headers;
my $body = slurp($req->body);
is $req->method, "POST", "http method is POST";
is $headers->header('Lambda-Runtime-Function-Response-Mode'), "streaming", "streaming is enabled";
is $body, '{"key1":"a","key2":"b","key3":"c"}', "response body is correct";
my $res = $req->new_response(200);
$res->content_type("application/json");
$res->body('{}');
$res->finalize;
};
$server->run($app, {
port => $port, host => '127.0.0.1',
});
},
max_wait => 10, # seconds
);
my $bootstrap = AWS::Lambda::Bootstrap->new(
handler => "streaming.handle",
runtime_api => "example.com",
task_root => "$FindBin::Bin/test_handlers",
runtime_api => "127.0.0.1:" . $app_server->port,
);
my $response = sub {
my $responder = shift;
my $writer = $responder->("application/json");
$writer->write('{"key1":"a","key2":"b",');
$writer->write('"key3":"c"}');
$writer->close;
};
my $context = AWS::Lambda::Context->new(
deadline_ms => 1542409706888,
aws_request_id => "8476a536-e9f4-11e8-9739-2dfe598c3fcd",
invoked_function_arn => 'arn:aws:lambda:us-east-2:123456789012:function:custom-runtime',
);
$bootstrap->lambda_response_streaming($response, $context);
$app_server->stop;
done_testing;
t/20_psgi.t view on Meta::CPAN
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'GET', 'method';
my $content;
warning_is { $content = slurp_fh($req->input) } undef, 'no warning';
is $content, '', 'content';
is $req->request_uri, '/foo%20/bar?query=hoge&query=fuga', 'request uri';
is $req->path_info, '/foo /bar', 'path info';
is $req->query_string, 'query=hoge&query=fuga', 'query string';
is $req->header('Header-Name'), 'Value1, Value2', 'header';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "API Gateway POST Request" => sub {
my $input = slurp_json("testdata/apigateway-post-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'POST', 'method';
is $req->content_type, 'application/json', 'content-type';
my $content;
warning_is { $content = slurp_fh($req->input) } undef, 'no warning';
is $content, '{"hello":"ããã«ã¡ã¯ä¸ç"}', 'content';
is $req->request_uri, '/', 'request uri';
is $req->path_info, '/', 'path info';
is $req->query_string, '', 'query string';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "API Gateway Base64 encoded POST Request" => sub {
my $input = slurp_json("testdata/apigateway-base64-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'POST', 'method';
# You have to add 'application/octet-stream' to binary media types.
# https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-configure-with-console.html
is $req->content_type, 'application/octet-stream', 'content-type';
my $content;
warning_is { $content = slurp_fh($req->input) } undef, 'no warning';
is $content, '{"hello":"world"}', 'content';
is $req->request_uri, '/', 'request uri';
is $req->path_info, '/', 'path info';
is $req->query_string, '', 'query string';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "API Gateway v2 GET Request" => sub {
my $input = slurp_json("testdata/apigateway-v2-get-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'GET', 'method';
my $content;
warning_is { $content = slurp_fh($req->input) } undef, 'no warning';
is $content, '', 'content';
is $req->request_uri, '/my/path?parameter1=value1¶meter1=value2¶meter2=value', 'request uri';
is $req->path_info, '/my/path', 'path info';
is $req->query_string, 'parameter1=value1¶meter1=value2¶meter2=value', 'query string';
is $req->header('Header1'), 'value1,value2', 'header';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "API Gateway v2 POST Request" => sub {
my $input = slurp_json("testdata/apigateway-v2-post-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'POST', 'method';
my $content;
warning_is { $content = slurp_fh($req->input) } undef, 'no warning';
is $content, '{"hello":"ããã«ã¡ã¯ä¸ç"}', 'content';
is $req->request_uri, '/my/path', 'request uri';
is $req->path_info, '/my/path', 'path info';
is $req->query_string, '', 'query string';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "API Gateway v2 base64 Request" => sub {
my $input = slurp_json("testdata/apigateway-v2-base64-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'POST', 'method';
my $content;
warning_is { $content = slurp_fh($req->input) } undef, 'no warning';
is $content, '{"hello":"world"}', 'content';
is $req->request_uri, '/my/path', 'request uri';
is $req->path_info, '/my/path', 'path info';
is $req->query_string, '', 'query string';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "ALB GET Request" => sub {
my $input = slurp_json("testdata/alb-get-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'GET', 'method';
is slurp_fh($req->input), '', 'content';
is $req->request_uri, '/foo/bar?query=hoge&query=fuga', 'request uri';
is $req->path_info, '/foo/bar', 'path info';
is $req->query_string, 'query=hoge&query=fuga', 'query string';
is $req->header('Header-Name'), 'Value1, Value2', 'header';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "ALB POST Request" => sub {
my $input = slurp_json("testdata/alb-post-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'POST', 'method';
is $req->content_type, 'application/json', 'content-type';
is slurp_fh($req->input), '{"hello":"ããã«ã¡ã¯ä¸ç"}', 'content';
is $req->request_uri, '/', 'request uri';
is $req->path_info, '/', 'path info';
is $req->query_string, '', 'query string';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "ALB POST Base64 Request" => sub {
my $input = slurp_json("testdata/alb-base64-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'POST', 'method';
is $req->content_type, 'application/octet-stream', 'content-type';
is slurp_fh($req->input), '{"hello":"world"}', 'content';
is $req->request_uri, '/foo/bar', 'request uri';
is $req->path_info, '/foo/bar', 'path info';
is $req->query_string, '', 'query string';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "Function URLs GET Request" => sub {
my $input = slurp_json("testdata/function-urls-get-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'GET', 'method';
is slurp_fh($req->input), '', 'content';
is $req->request_uri, '/foo /bar?parameter1=value1¶meter1=value2¶meter2=value', 'request uri';
is $req->path_info, '/foo /bar', 'path info';
is $req->query_string, 'parameter1=value1¶meter1=value2¶meter2=value', 'query string';
is $req->header('header1'), 'value1,value2', 'header';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "Function URLs POST Request" => sub {
my $input = slurp_json("testdata/function-urls-post-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'POST', 'method';
is $req->content_type, 'application/json', 'content-type';
is slurp_fh($req->input), '{"hello":"world"}', 'content';
is $req->request_uri, '/my/path', 'request uri';
is $req->path_info, '/my/path', 'path info';
is $req->query_string, '', 'query string';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "Function URLs POST Base64 Request" => sub {
my $input = slurp_json("testdata/function-urls-post-base64-request.json");
my $output = $app->format_input($input);
my $req = Plack::Request->new($output);
is $req->method, 'POST', 'method';
is $req->content_type, 'application/octet-stream', 'content-type';
is slurp_fh($req->input), '{"hello":"world"}', 'content';
is $req->request_uri, '/my/path', 'request uri';
is $req->path_info, '/my/path', 'path info';
is $req->query_string, '', 'query string';
ok !$req->env->{'psgi.streaming'}, 'psgi.streaming';
};
subtest "plain text response" => sub {
my $response = [
200,
[
'Content-Type' => 'text/plain',
'Header-Name' => 'value1',
'header-name' => 'value2',
],
t/21_psgi_response_streaming.t view on Meta::CPAN
cmp_deeply decode_json($prelude), {
statusCode => 200,
headers => {
"content-type" => "text/plain",
},
cookies => [],
}, "prelude";
is $body, "hello", "body";
};
subtest "streaming response" => sub {
my $content_type = undef;
my $buf = "";
my $res = $app->_handle_response_stream(sub {
my $responder = shift;
my $writer = $responder->([200, ["Content-Type" => "text/plain"]]);
$writer->write("hello");
$writer->close;
});
my $responder = sub {
$content_type = shift;
t/lib/BootstrapMock.pm view on Meta::CPAN
use AWS::Lambda::Bootstrap;
our @ISA = qw(AWS::Lambda::Bootstrap);
sub new {
my $class = shift;
my %args = @_;
my $self = $class->SUPER::new(%args);
$self->{lambda_next} = $args{lambda_next} // sub { die "unexpected call of lambda_next" };
$self->{lambda_response} = $args{lambda_response} // sub { die "unexpected call of lambda_response" };
$self->{lambda_response_streaming} = $args{lambda_response_streaming} // sub { die "unexpected call of lambda_response_streaming" };
$self->{lambda_error} = $args{lambda_error} // sub { die "unexpected call of lambda_error" };
$self->{lambda_init_error} = $args{lambda_init_error} // sub { die "unexpected call of lambda_init_error" };
return $self;
}
sub lambda_next {
my $self = shift;
return $self->{lambda_next}->($self, @_);
}
sub lambda_response {
my $self = shift;
return $self->{lambda_response}->($self, @_);
}
sub lambda_response_streaming {
my $self = shift;
return $self->{lambda_response_streaming}->($self, @_);
}
sub lambda_error {
my $self = shift;
return $self->{lambda_error}->($self, @_);
}
sub lambda_init_error {
my $self = shift;
return $self->{lambda_init_error}->($self, @_);
t/test_handlers/streaming.pl view on Meta::CPAN
use utf8;
use strict;
use warnings;
use AWS::Lambda;
# an example of echo using streaming response
# it is called from 14_streaming_response.t
sub handle {
my ($payload, $context) = @_;
die "payload is empty" unless $payload;
die "context is empty" unless $context;
die "AWS::Lambda::context is invalid" if $AWS::Lambda::context != $context;
die "trace_id is empty" unless $ENV{_X_AMZN_TRACE_ID};
return sub {
my $responder = shift;
my $writer = $responder->("application/json");
$writer->write($payload);