AWS-Lambda

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

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.

MANIFEST  view on Meta::CPAN

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

README.md  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 [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&parameter1=value2&parameter2=value', 'request uri';
    is $req->path_info, '/my/path', 'path info';
    is $req->query_string, 'parameter1=value1&parameter1=value2&parameter2=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&parameter1=value2&parameter2=value', 'request uri';
    is $req->path_info, '/foo /bar', 'path info';
    is $req->query_string, 'parameter1=value1&parameter1=value2&parameter2=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);



( run in 0.324 second using v1.01-cache-2.11-cpan-a5abf4f5562 )