Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Serverless Java container
The `aws-serverless-java-container` is collection of interfaces and their implementations that let you run Java application written with frameworks such as [Jersey](https://jersey.java.net/) or [Spark]() in [AWS Lambda](https://aws.amazon.com/lambda/).
The `aws-serverless-java-container` is collection of interfaces and their implementations that let you run Java application written with frameworks such as [Jersey](https://jersey.java.net/) or [Spark](http://sparkjava.com/) in [AWS Lambda](https://aws.amazon.com/lambda/).

The library contains a core artifact called `aws-serverless-java-container-core` that defines the interfaces and base classes required as well as default implementation of the Java servlet `HttpServletRequest` and `HttpServletResponse`.
The library also includes two initial implementations of the interfaces to support Jersey apps (`aws-serverless-java-container-jersey`) and Spark (`aws-serverless-java-container-spark`).
Expand All @@ -18,16 +18,39 @@ To include the library in your Maven project, add the desired implementation to
The simplest way to run your application serverlessly is to configure [API Gateway](https://aws.amazon.com/api-gateway/) to use the
[`AWS_PROXY`](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-set-up-lambda-proxy-integration-on-proxy-resource) integration type and
configure your desired `LambdaContainerHandler` implementation to use `AwsProxyRequest`/`AwsProxyResponse` readers and writers. Both Spark and Jersey implementations provide static helper methods that
pre-configure this for you, so your handler method would look like this:
pre-configure this for you.

### Jersey support
The library expects to receive a valid Jax RS application object. For the Jersey implementation this is the `ResourceConfig` object.

```java
public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
private ResourceConfig jerseyApplication = new ResourceConfig().packages("my.jersey.app.package");
private JerseyLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler
= JerseyLambdaContainerHandler.getAwsProxyHandler(jerseyApplication);

// for Spark applications, use the SparkLambdaContainerHandler.getAwsProxyHandler() helper method;
public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
return handler.proxy(awsProxyRequest, context);
}
}
```

### Spring support
The library supports Spring applications that are configured using annotations (in code) rather than in an XML file. The simplest possible configuration uses the `@ComponentScan` annotation to load all controller classes from a package. For example, our unit test application has the following configuuration class.

```java
@Configuration
@ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp")
public class EchoSpringAppConfig {
}
```

Once you have declared a configuration class, you can initialize the library with the class name:
```java
public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
SpringLambdacontainerHandler<AwsProxyRequest, AwsProxyResponse> handler =
SpringLambdacontainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class)

public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
return handler.proxy(awsProxyRequest, context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protected LambdaContainerHandler(RequestReader<RequestType, ContainerRequestType
protected abstract ContainerResponseType getContainerResponse(CountDownLatch latch);


protected abstract void handleRequest(ContainerRequestType containerRequest, ContainerResponseType containerResponse)
protected abstract void handleRequest(ContainerRequestType containerRequest, ContainerResponseType containerResponse, Context lambdaContext)
throws Exception;


Expand All @@ -85,17 +85,18 @@ public ResponseType proxy(RequestType request, Context context) {
ContainerResponseType containerResponse = getContainerResponse(latch);
ContainerRequestType containerRequest = requestReader.readRequest(request, securityContext, context);

handleRequest(containerRequest, containerResponse);
handleRequest(containerRequest, containerResponse, context);

latch.await();

return responseWriter.writeResponse(containerResponse, context);
} catch (Exception e) {
context.getLogger().log("Error while handling request: " + e.getMessage());

for (StackTraceElement el : e.getStackTrace()) {
/*for (StackTraceElement el : e.getStackTrace()) {
context.getLogger().log(el.toString());
}
}*/
e.printStackTrace();

return exceptionHandler.handle(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,48 @@ public abstract class ResponseWriter<ContainerResponseType, ResponseType> {
*/
protected abstract ResponseType writeResponse(ContainerResponseType containerResponse, Context lambdaContext)
throws InvalidResponseObjectException;

/**
* Checks whether the given byte array contains a UTF-8 encoded string
* @param input The byte[] to check against
* @return true if the contend is valid UTF-8, false otherwise
*/
protected boolean isValidUtf8(final byte[] input) {
int i = 0;
// Check for BOM
if (input.length >= 3 && (input[0] & 0xFF) == 0xEF
&& (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) {
i = 3;
}

int end;
for (int j = input.length; i < j; ++i) {
int octet = input[i];
if ((octet & 0x80) == 0) {
continue; // ASCII
}

// Check for UTF-8 leading byte
if ((octet & 0xE0) == 0xC0) {
end = i + 1;
} else if ((octet & 0xF0) == 0xE0) {
end = i + 2;
} else if ((octet & 0xF8) == 0xF0) {
end = i + 3;
} else {
// Java only supports BMP so 3 is max
return false;
}

while (i < end) {
i++;
octet = input[i];
if ((octet & 0xC0) != 0x80) {
// Not a valid trailing byte
return false;
}
}
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,27 @@
*/
package com.amazonaws.serverless.proxy.internal.model;

import java.util.HashMap;

/**
* Custom authorizer context object for the API Gateway request context.
*/
public class ApiGatewayAuthorizerContext {

//-------------------------------------------------------------
// Variables - Private
//-------------------------------------------------------------

private String principalId;

public class ApiGatewayAuthorizerContext extends HashMap<String, String> {

//-------------------------------------------------------------
// Methods - Getter/Setter
//-------------------------------------------------------------

public String getPrincipalId() {
return principalId;
return get("principalId");
}


public void setPrincipalId(String principalId) {
this.principalId = principalId;
put("principalId", principalId);
}

public String getContextValue(String key) {
return get(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class AwsProxyResponse {
private int statusCode;
private Map<String, String> headers;
private String body;
private boolean isBase64Encoded;


//-------------------------------------------------------------
Expand Down Expand Up @@ -100,4 +101,12 @@ public String getBody() {
public void setBody(String body) {
this.body = body;
}

public boolean isBase64Encoded() {
return isBase64Encoded;
}

public void setBase64Encoded(boolean base64Encoded) {
isBase64Encoded = base64Encoded;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,17 @@ public Locale getLocale() {
// Methods - Package
//-------------------------------------------------------------

String getAwsResponseBody() {
String getAwsResponseBodyString() {
return responseBody;
}

byte[] getAwsResponseBodyBytes() {
if (bodyOutputStream != null) {
return bodyOutputStream.toByteArray();
}
return new byte[0];
}


Map<String, String> getAwsResponseHeaders() {
Map<String, String> responseHeaders = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ public Enumeration<String> getHeaders(String s) {

@Override
public Enumeration<String> getHeaderNames() {
if (request.getHeaders() == null) {
return Collections.emptyEnumeration();
}
return Collections.enumeration(request.getHeaders().keySet());
}

Expand Down Expand Up @@ -220,7 +223,7 @@ public String getPathTranslated() {

@Override
public String getContextPath() {
return request.getResource();
return "/";
}


Expand Down Expand Up @@ -529,7 +532,9 @@ public Map<String, String[]> getParameterMap() {
}

for (Map.Entry<String, List<String>> entry : params.entrySet()) {
output.put(entry.getKey(), (String[])entry.getValue().toArray());
String[] valuesArray = new String[entry.getValue().size()];
valuesArray = entry.getValue().toArray(valuesArray);
output.put(entry.getKey(), valuesArray);
}
return output;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ public class AwsProxyHttpServletRequestReader extends RequestReader<AwsProxyRequ
@Override
public AwsProxyHttpServletRequest readRequest(AwsProxyRequest request, SecurityContext securityContext, Context lambdaContext)
throws InvalidRequestEventException {
return new AwsProxyHttpServletRequest(request, lambdaContext, securityContext);
AwsProxyHttpServletRequest servletRequest = new AwsProxyHttpServletRequest(request, lambdaContext, securityContext);
servletRequest.setAttribute(API_GATEWAY_CONTEXT_PROPERTY, request.getRequestContext());
servletRequest.setAttribute(API_GATEWAY_STAGE_VARS_PROPERTY, request.getStageVariables());
servletRequest.setAttribute(LAMBDA_CONTEXT_PROPERTY, lambdaContext);
return servletRequest;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse;
import com.amazonaws.services.lambda.runtime.Context;

import java.io.IOException;
import java.util.Base64;

/**
* Creates an <code>AwsProxyResponse</code> object given an <code>AwsHttpServletResponse</code> object. If the
* response is not populated with a status code we infer a default 200 status code.
Expand All @@ -32,7 +35,18 @@ public class AwsProxyHttpServletResponseWriter extends ResponseWriter<AwsHttpSer
public AwsProxyResponse writeResponse(AwsHttpServletResponse containerResponse, Context lambdaContext)
throws InvalidResponseObjectException {
AwsProxyResponse awsProxyResponse = new AwsProxyResponse();
awsProxyResponse.setBody(containerResponse.getAwsResponseBody());
if (containerResponse.getAwsResponseBodyString() != null) {
String responseString;

if (isValidUtf8(containerResponse.getAwsResponseBodyBytes())) {
responseString = containerResponse.getAwsResponseBodyString();
} else {
responseString = Base64.getMimeEncoder().encodeToString(containerResponse.getAwsResponseBodyBytes());
awsProxyResponse.setBase64Encoded(true);
}

awsProxyResponse.setBody(responseString);
}
awsProxyResponse.setHeaders(containerResponse.getAwsResponseHeaders());

if (containerResponse.getStatus() <= 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
import java.util.Set;


class AwsProxyServletContext
public class AwsProxyServletContext
implements ServletContext {

//-------------------------------------------------------------
Expand Down Expand Up @@ -218,7 +218,7 @@ public String getServerInfo() {

@Override
public String getInitParameter(String s) {
return "AWS Lambda (JDK " + System.getProperty("java.version") + ")";
return null;
}


Expand Down Expand Up @@ -411,11 +411,7 @@ public String getVirtualServerName() {
}


//-------------------------------------------------------------
// Methods - Package
//-------------------------------------------------------------

static ServletContext getInstance(AwsProxyRequest request, Context lambdaContext) {
public static ServletContext getInstance(AwsProxyRequest request, Context lambdaContext) {
if (instance == null) {
instance = new AwsProxyServletContext(request, lambdaContext);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ public AwsProxyRequestBuilder(String path, String httpMethod) {
this.request.setPath(path);
this.request.setQueryStringParameters(new HashMap<>());
this.request.setRequestContext(new ApiGatewayRequestContext());
this.request.getRequestContext().setIdentity(new ApiGatewayRequestIdentity());
ApiGatewayRequestIdentity identity = new ApiGatewayRequestIdentity();
identity.setSourceIp("127.0.0.1");
this.request.getRequestContext().setIdentity(identity);
}


Expand Down Expand Up @@ -120,11 +122,21 @@ public AwsProxyRequestBuilder body(String body) {


public AwsProxyRequestBuilder authorizerPrincipal(String principal) {
this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext());
if (this.request.getRequestContext().getAuthorizer() == null) {
this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext());
}
this.request.getRequestContext().getAuthorizer().setPrincipalId(principal);
return this;
}

public AwsProxyRequestBuilder authorizerContextValue(String key, String value) {
if (this.request.getRequestContext().getAuthorizer() == null) {
this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext());
}
this.request.getRequestContext().getAuthorizer().put(key, value);
return this;
}


public AwsProxyRequestBuilder cognitoUserPool(String identityId) {
this.request.getRequestContext().getIdentity().setCognitoAuthenticationType("POOL");
Expand Down
Loading