AWS Lambda with RESTEasy Reactive, Undertow, or Reactive Routes
With Quarkus you can deploy your favorite Java HTTP frameworks as AWS Lambda’s using either the AWS Gateway HTTP API or AWS Gateway REST API. This means that you can deploy your microservices written with RESTEasy Reactive (our Jakarta REST implementation), Undertow (servlet), Reactive Routes, 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 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
great
guide to help you navigate this decision.
Like most Quarkus extensions, the Quarkus AWS Lambda HTTP/REST extensions support Live Coding.
This technology is considered preview. For a full list of possible statuses, check our FAQ entry. |
准备
要完成本指南,您需要:
-
Roughly 30 minutes
-
An IDE
-
JDK 11+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.6
-
Optionally the Quarkus CLI if you want to use it
-
Optionally Mandrel or GraalVM installed and configured appropriately if you want to build a native executable (or Docker if you use a native container build)
入门
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 AWS 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:
mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-http-archetype \
-DarchetypeVersion=3.6.3
If you want to use the AWS Gateway REST API, generate your project with this script:
mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-rest-archetype \
-DarchetypeVersion=3.6.3
Build and Deploy
Build the project:
quarkus build
./mvnw install
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
quarkus build --native
./mvnw install -Dnative
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.
|
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
./mvnw install -Dnative -DskipTests -Dquarkus.native.container-build=true
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 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:
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 @QuarkusIntegrationTest
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
quarkus.lambda.mock-event-server.dev-port=8082
quarkus.lambda.mock-event-server.test-port=8083
A port value of zero will result in a randomly assigned port.
To turn off the mock event server:
quarkus.lambda.mock-event-server.enabled=false
Simulate AWS 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:
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:
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
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.
Responses for binary types will be automatically encoded with base64. This
is different from 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.
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
./mvnw install -Dnative -DskipTests -Dquarkus.native.container-build=true
You can then test the executable locally with sam local
sam local start-api --template target/sam.native.yaml
To deploy to AWS Lambda:
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
deploying 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-reactive
, quarkus-reactive-routes
, and
quarkus-undertow
dependencies are all optional. Pick which HTTP
framework(s) you want to use (Jakarta REST, 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.
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 (Jakarta REST, 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:
Environment:
Variables:
DISABLE_SIGNAL_HANDLERS: true
This environment variable resolves some incompatibilities between Quarkus and the AWS 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:
Globals:
Api:
EndpointConfiguration: REGIONAL
BinaryMediaTypes:
- "*/*"
Injectable AWS Context Variables
If you are using RESTEasy Reactive and Jakarta REST, you can inject various
AWS Context variables into your Jakarta REST resource classes using the
Jakarta REST @Context
annotation or anywhere else with the CDI @Inject
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:
import jakarta.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:
import jakarta.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
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 (Jakarta REST, servlet, Reactive Routes).
API Gateway supports many 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
event
json document and look for security based metadata and register a
java.security.Principal
internally that can be looked up in Jakarta REST
by injecting a jakarta.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:
Auth Type | Principal Class | Json path of Principal Name |
---|---|---|
Cognito JWT |
|
|
IAM |
|
|
Custom Lambda |
|
|
Auth Type | Principal Class | Json path of Principal Name |
---|---|---|
Cognito |
|
|
IAM |
|
|
Custom Lambda |
|
|
If the cognito:groups
claim is present, then Quarkus will extract and map
those groups to Quarkus roles which can then be used in authorization with
annotations like @RolesAllowed
. If you do not want to map
cognito:groups
to Quarkus roles, then you must explicitly disable it in
configuration:
quarkus.lambda-http.map-cognito-to-roles=false
You can also specify a different Cognito claim to extract roles from:
quarkus.lambda-http.cognito-role-claim=cognito:roles
By default, it expects roles in a space delimited list enclosed in brackets
i.e. [ user admin ]
. You can specify the regular expression to use to
find individual roles in the claim string too:
quarkus.lambda-http.cognito-claim-matcher=[^\[\] \t]+
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 full control on 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.
quarkus-amazon-lambda-http
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
quarkus-amazon-lambda-rest
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:
package org.acme;
import java.security.Principal;
import jakarta.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:
package org.acme;
import java.security.Principal;
import jakarta.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
SnapStart
To optimize your application for Lambda SnapStart, check the SnapStart Configuration Documentation.