////
This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
= Amazon Lambda with RESTEasy, Undertow, or Reactive Routes
:extension-status: preview

include::./attributes.adoc[]
:devtools-no-gradle:

With Quarkus you can deploy your favorite Java HTTP frameworks as Amazon Lambda's using either the https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html[AWS Gateway HTTP API]
or https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html[AWS Gateway REST API].  This means that you can deploy your microservices written with RESTEasy (JAX-RS),
Undertow (servlet), Reactive Routes, xref:funqy-http.adoc[Funqy HTTP] or any other Quarkus HTTP framework as an AWS Lambda.

You can deploy your Lambda as a pure Java jar, or you can compile your project to a native image and deploy that for a smaller
memory footprint and startup time.  Our integration also generates SAM deployment files that can be consumed by https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html[Amazon's SAM framework].

Quarkus has a different extension for each Gateway API.  The HTTP Gateway API is implemented within the `quarkus-amazon-lambda-http` extension.
The REST Gateway API is implemented within the `quarkus-amazon-lambda-rest` extension.  If you are confused on which Gateway product to use,
Amazon has a https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html[great guide] to help you navigate this decision.

Like most Quarkus extensions, the Quarkus AWS Lambda HTTP/REST extensions support Live Coding.

include::./status-include.adoc[]

== Prerequisites

:prerequisites-time: 30 minutes
include::includes/devtools/prerequisites.adoc[]
* https://aws.amazon.com[An Amazon AWS account]
* https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html[AWS SAM CLI]

== Getting Started

This guide walks you through generating an example Java project via a Maven archetype.  Later on it walks through the structure
of the project so you can adapt any existing projects you have to use Amazon Lambda.

== Installing AWS bits

Installing all the AWS bits is probably the most difficult thing about this guide.  Make sure that you follow all the steps
for installing AWS SAM CLI.

== Creating the Maven Deployment Project

Create the Quarkus AWS Lambda Maven project using our Maven Archetype.

If you want to use the AWS Gateway HTTP API, generate your project with this script:
[source,bash,subs=attributes+]
----
mvn archetype:generate \
       -DarchetypeGroupId=io.quarkus \
       -DarchetypeArtifactId=quarkus-amazon-lambda-http-archetype \
       -DarchetypeVersion={quarkus-version}
----

If you want to use the AWS Gateway REST API, generate your project with this script:
[source,bash,subs=attributes+]
----
mvn archetype:generate \
       -DarchetypeGroupId=io.quarkus \
       -DarchetypeArtifactId=quarkus-amazon-lambda-rest-archetype \
       -DarchetypeVersion={quarkus-version}
----


== Build and Deploy

Build the project:

include::includes/devtools/build.adoc[]

This will compile the code and run the unit tests included within the generated project.
Unit testing is the same as any other Java project and does not require running on Amazon.  Quarkus dev mode is also
available with this extension.

If you want to build a native executable, make sure you have GraalVM installed correctly and just add a `native` property
to the build

include::includes/devtools/build-native.adoc[]

NOTE: If you are building on a non-Linux system, you will need to also pass in a property instructing quarkus to use a Docker build as Amazon
Lambda requires Linux binaries.  You can do this by passing `-Dquarkus.native.container-build=true` to your build command.
This requires you to have Docker installed locally, however.

include::includes/devtools/build-native-container.adoc[]

== Extra Build Generated Files

After you run the build, there are a few extra files generated by the Quarkus lambda extension you are using.  These files
are in the the build directory:  `target/` for Maven, `build/` for Gradle.

* `function.zip` - lambda deployment file
* `sam.jvm.yaml` - sam cli deployment script
* `sam.native.yaml` - sam cli deployment script for native

== Live Coding and Simulating AWS Lambda Environment Locally

In dev and test mode, Quarkus will start a mock AWS Lambda event server
that will convert HTTP requests to the corresponding API Gateway event types and post them to the underlying
Quarkus HTTP lambda environment for processing.  This simulates the AWS Lambda environment
as much as possible locally without requiring tools like Docker and SAM CLI.

When using Quarkus Dev Mode just invoke HTTP requests on `http://localhost:8080`
as you normally would when testing your REST endpoints.  This request will hit the Mock Event Server and will
be converted to the API Gateway json message that is consumed by the Quarkus Lambda Poll loop.

For testing, Quarkus starts up a separate Mock Event server under port 8081.  The default port for Rest Assured is
automatically set to 8081 by Quarkus, so you don't have to worry about setting this up.

If you want to simulate more complex
API Gateway events in your tests, then manually do an HTTP POST to `http://localhost:8080/\_lambda_` (port 8081 in test mode) with
the raw API Gateway json events.  These events will be placed directly on the Quarkus Lambda poll loop
for processing.  Here's an example of that:

[source,java]
----
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.equalTo;

import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;

import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class AmazonLambdaSimpleTestCase {
    @Test
    public void testJaxrsCognitoJWTSecurityContext() throws Exception {
        APIGatewayV2HTTPEvent request = request("/security/username");
        request.getRequestContext().setAuthorizer(new APIGatewayV2HTTPEvent.RequestContext.Authorizer());
        request.getRequestContext().getAuthorizer().setJwt(new APIGatewayV2HTTPEvent.RequestContext.Authorizer.JWT());
        request.getRequestContext().getAuthorizer().getJwt().setClaims(new HashMap<>());
        request.getRequestContext().getAuthorizer().getJwt().getClaims().put("cognito:username", "Bill");

        given()
                .contentType("application/json")
                .accept("application/json")
                .body(request)
                .when()
                .post("/_lambda_")
                .then()
                .statusCode(200)
                .body("body", equalTo("Bill"));
    }
----

The above example simulates sending a Cognito principal with an HTTP request to your HTTP Lambda.

If you want to hand code raw events for the AWS HTTP API, the AWS Lambda library has the request event type which is
`com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent` and the response event type
of `com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse`.  This corresponds
to the `quarkus-amazon-lambda-http` extension and the AWS HTTP API.

If you want to hand code raw events for the AWS REST API, Quarkus has its own implementation:  `io.quarkus.amazon.lambda.http.model.AwsProxyRequest`
and `io.quarkus.amazon.lambda.http.model.AwsProxyResponse`.  This corresponds
to `quarkus-amazon-lambda-rest` extension and the AWS REST API.

The mock event server is also started for `@NativeImageTest` unit tests so will work
with native binaries too.  All this provides similar functionality to the SAM CLI local testing, without the overhead of Docker.

Finally, if port 8080 or port 8081 is not available on your computer, you can modify the dev
and test mode ports with application.properties

[source, subs=attributes+]
----
quarkus.lambda.mock-event-server.dev-port=8082
quarkus.lambda.mock-event-server.test-port=8083
----



== Simulate Amazon Lambda Deployment with SAM CLI

The AWS SAM CLI allows you to run your lambda's locally on your laptop in a simulated Lambda environment.  This requires Docker to be installed.
After you have built your Maven project, execute this command:

[source,bash,subs=attributes+]
----
sam local start-api --template target/sam.jvm.yaml
----

This will start a Docker container that mimics Amazon's Lambda's deployment environment. Once the environment
is started you can invoke the example lambda in your browser by going to:

http://127.0.0.1:3000/hello

In the console you'll see startup messages from the lambda.  This particular deployment starts a JVM and loads your
lambda as pure Java.


== Deploy to AWS

[source,bash,subs=attributes+]
----
sam deploy -t target/sam.jvm.yaml -g
----

Answer all the questions and your lambda will be deployed and the necessary hooks to the API Gateway will be set up. If
everything deploys successfully, the root URL of your microservice will be output to the console.  Something like this:

----
Key                 LambdaHttpApi
Description         URL for application
Value               https://234asdf234as.execute-api.us-east-1.amazonaws.com/
----

The `Value` attribute is the root URL for your lambda. Copy it to your browser and add `hello` at the end.

[NOTE]
Responses for binary types will be automatically encoded with base64.  This is different than the behavior using
`quarkus:dev` which will return the raw bytes.  Amazon's API has additional restrictions requiring the base64 encoding.
In general, client code will automatically handle this encoding but in certain custom situations, you should be aware
you may need to manually manage that encoding.

== Deploying a native executable

To deploy a native executable, you must build it with GraalVM.

include::includes/devtools/build-native-container.adoc[]

You can then test the executable locally with sam local

[source,bash,subs=attributes+]
----
sam local start-api --template target/sam.native.yaml
----

To deploy to AWS Lambda:
[source,bash,subs=attributes+]
----
sam deploy -t target/sam.native.yaml -g
----

== Examine the POM

There is nothing special about the POM other than the inclusion of the `quarkus-amazon-lambda-http` extension
(if you are deploying an AWS Gateway HTTP API) or the `quarkus-amazon-lambda-rest` extension (if you are deploy an AWS Gateway REST API).
These extensions automatically generate everything you might need for your lambda deployment.

Also, at least in the generated Maven archetype `pom.xml`, the `quarkus-resteasy`, `quarkus-reactive-routes`, and `quarkus-undertow`
dependencies are all optional.  Pick which HTTP framework(s) you want to use (JAX-RS, Reactive Routes, and/or Servlet) and
remove the other dependencies to shrink your deployment.

=== Examine sam.yaml

The `sam.yaml` syntax is beyond the scope of this document.  There's a couple of things that must be highlighted just in case you are
going to craft your own custom `sam.yaml` deployment files.

The first thing to note is that for pure Java lambda deployments require a specific handler class.
Do not change the Lambda handler name.

[source, subs=attributes+]
----
     Properties:
        Handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest
        Runtime: java11
----

This handler is a bridge between the lambda runtime and the Quarkus HTTP framework you are using (JAX-RS, Servlet, etc.)

If you want to go native, there's an environment variable that must be set for native GraalVM deployments.  If you look at `sam.native.yaml`
you'll see this:

[source, subs=attributes+]
----
        Environment:
          Variables:
            DISABLE_SIGNAL_HANDLERS: true
----

This environment variable resolves some incompatibilities between Quarkus and the Amazon Lambda Custom Runtime environment.

Finally, there is one specific thing for AWS Gateway REST API deployments.
That API assumes that HTTP response bodies are text unless you explicitly tell it which media types are
binary through configuration.  To make things easier, the Quarkus extension forces a binary (base 64) encoding of all
HTTP response messages and the `sam.yaml` file must configure the API Gateway to assume all media types are binary:

[source, subs=attributes+]
----
  Globals:
    Api:
      EndpointConfiguration: REGIONAL
      BinaryMediaTypes:
        - "*/*"
----

== Injectable AWS Context Variables

If you are using RESTEasy and JAX-RS, you can inject various AWS Context variables into your JAX-RS resource classes
using the JAX-RS `@Context` annotation.

For the AWS HTTP API you can inject the AWS variables `com.amazonaws.services.lambda.runtime.Context` and
`com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent`.  Here is an example:

[source, java]
----
import javax.ws.rs.core.Context;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;


@Path("/myresource")
public class MyResource {
    @GET
    public String ctx(@Context com.amazonaws.services.lambda.runtime.Context ctx) { }

    @GET
    public String event(@Context APIGatewayV2HTTPEvent event) { }

    @GET
    public String requestContext(@Context APIGatewayV2HTTPEvent.RequestContext req) { }


}
----

For the AWS REST API you can inject the AWS variables `com.amazonaws.services.lambda.runtime.Context` and
`io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext`.  Here is an example:

[source, java]
----
import javax.ws.rs.core.Context;
import io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext;
import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;


@Path("/myresource")
public class MyResource {
    @GET
    public String ctx(@Context com.amazonaws.services.lambda.runtime.Context ctx) { }

    @GET
    public String reqContext(@Context AwsProxyRequestContext req) { }

    @GET
    public String req(@Context AwsProxyRequest req) { }

}
----

== Tracing with AWS XRay and GraalVM

If you are building native images, and want to use https://aws.amazon.com/xray[AWS X-Ray Tracing] with your lambda
you will need to include `quarkus-amazon-lambda-xray` as a dependency in your pom.  The AWS X-Ray
library is not fully compatible with GraalVM so we had to do some integration work to make this work.

== Security Integration

When you invoke an HTTP request on the API Gateway, the Gateway turns that HTTP request into a JSON event document that is
forwarded to a Quarkus Lambda.  The Quarkus Lambda parses this json and converts in into an internal representation of an HTTP
request that can be consumed by any HTTP framework Quarkus supports (JAX-RS, servlet, Reactive Routes).

API Gateway supports many different ways to securely invoke on your HTTP endpoints that are backed by Lambda and Quarkus.
If you enable it, Quarkus will automatically parse relevant parts of the https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html[event json document]
and look for security based metadata and register a `java.security.Principal` internally that can be looked up in JAX-RS
by injecting a `javax.ws.rs.core.SecurityContext`, via `HttpServletRequest.getUserPrincipal()` in servlet, and `RouteContext.user()` in Reactive Routes.
If you want more security information, the `Principal` object can be typecast to
a class that will give you more information.

To enable this security feature, add this to your `application.properties` file:
----
quarkus.lambda-http.enable-security=true
----


Here's how its mapped:

.HTTP `quarkus-amazon-lambda-http`
[options="header"]
|=======================
|Auth Type       |Principal Class                                 |Json path of Principal Name
|Cognito JWT     |`io.quarkus.amazon.lambda.http.CognitoPrincipal`|`requestContext.authorizer.jwt.claims.cognito:username`
|IAM             |`io.quarkus.amazon.lambda.http.IAMPrincipal`    |`requestContext.authorizer.iam.userId`
|Custom Lambda   |`io.quarkus.amazon.lambda.http.CustomPrincipal` |`requestContext.authorizer.lambda.principalId`

|=======================

.REST `quarkus-amazon-lambda-rest`
[options="header"]
|=======================
|Auth Type       |Principal Class                                 |Json path of Principal Name
|Cognito         |`io.quarkus.amazon.lambda.http.CognitoPrincipal`|`requestContext.authorizer.claims.cognito:username`
|IAM             |`io.quarkus.amazon.lambda.http.IAMPrincipal`    |`requestContext.identity.user`
|Custom Lambda   |`io.quarkus.amazon.lambda.http.CustomPrincipal` |`requestContext.authorizer.principalId`

|=======================

== Custom Security Integration

The default support for AWS security only maps the principal name to Quarkus security
APIs and does nothing to map claims or roles or permissions.  You have can full control
how security metadata in the lambda HTTP event is mapped to Quarkus security APIs using
implementations of the `io.quarkus.amazon.lambda.http.LambdaIdentityProvider`
interface.  By implementing this interface, you can do things like define role mappings for your principal
or publish additional attributes provided by IAM or Cognito or your Custom Lambda security integration.

.HTTP `quarkus-amazon-lambda-http`
[source, java]
----
package io.quarkus.amazon.lambda.http;

/**
 * Helper interface that removes some boilerplate for creating
 * an IdentityProvider that processes APIGatewayV2HTTPEvent
 */
public interface LambdaIdentityProvider extends IdentityProvider<LambdaAuthenticationRequest> {
    @Override
    default public Class<LambdaAuthenticationRequest> getRequestType() {
        return LambdaAuthenticationRequest.class;
    }

    @Override
    default Uni<SecurityIdentity> authenticate(LambdaAuthenticationRequest request, AuthenticationRequestContext context) {
        APIGatewayV2HTTPEvent event = request.getEvent();
        SecurityIdentity identity = authenticate(event);
        if (identity == null) {
            return Uni.createFrom().optional(Optional.empty());
        }
        return Uni.createFrom().item(identity);
    }

    /**
     * You must override this method unless you directly override
     * IdentityProvider.authenticate
     *
     * @param event
     * @return
     */
    default SecurityIdentity authenticate(APIGatewayV2HTTPEvent event) {
        throw new IllegalStateException("You must override this method or IdentityProvider.authenticate");
    }
}
----

For HTTP, the important method to override is `LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event)`.  From this
you will allocate a SecurityIdentity based on how you want to map security data from `APIGatewayV2HTTPEvent`

.REST `quarkus-amazon-lambda-rest`
[source, java]
----
package io.quarkus.amazon.lambda.http;

import java.util.Optional;

import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;

import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;

/**
 * Helper interface that removes some boilerplate for creating
 * an IdentityProvider that processes APIGatewayV2HTTPEvent
 */
public interface LambdaIdentityProvider extends IdentityProvider<LambdaAuthenticationRequest> {
...

    /**
     * You must override this method unless you directly override
     * IdentityProvider.authenticate
     *
     * @param event
     * @return
     */
    default SecurityIdentity authenticate(AwsProxyRequest event) {
        throw new IllegalStateException("You must override this method or IdentityProvider.authenticate");
    }
}
----

For REST, the important method to override is `LambdaIdentityProvider.authenticate(AwsProxyRequest event)`.  From this
you will allocate a SecurityIdentity based on how you want to map security data from `AwsProxyRequest`.

Your implemented provider must be a CDI bean.  Here's an example:

[source,java]
----
package org.acme;

import java.security.Principal;

import javax.enterprise.context.ApplicationScoped;

import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;

import io.quarkus.amazon.lambda.http.LambdaIdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;

@ApplicationScoped
public class CustomSecurityProvider implements LambdaIdentityProvider {
    @Override
    public SecurityIdentity authenticate(APIGatewayV2HTTPEvent event) {
        if (event.getHeaders() == null || !event.getHeaders().containsKey("x-user"))
            return null;
        Principal principal = new QuarkusPrincipal(event.getHeaders().get("x-user"));
        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
        builder.setPrincipal(principal);
        return builder.build();
    }
}
----

Here's the same example, but with the AWS Gateway REST API:

[source,java]
----
package org.acme;

import java.security.Principal;

import javax.enterprise.context.ApplicationScoped;

import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;

import io.quarkus.amazon.lambda.http.LambdaIdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;

@ApplicationScoped
public class CustomSecurityProvider implements LambdaIdentityProvider {
    @Override
    public SecurityIdentity authenticate(AwsProxyRequest event) {
        if (event.getMultiValueHeaders() == null || !event.getMultiValueHeaders().containsKey("x-user"))
            return null;
        Principal principal = new QuarkusPrincipal(event.getMultiValueHeaders().getFirst("x-user"));
        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
        builder.setPrincipal(principal);
        return builder.build();
    }
}
----

Quarkus should automatically discover this implementation and use it instead of the default implementation
discussed earlier.

== Simple SAM Local Principal

If you are testing your application with `sam local` you can
hardcode a principal name to use when your application runs by setting
the `QUARKUS_AWS_LAMBDA_FORCE_USER_NAME` environment variable
