Skip to content

Commit f0accf1

Browse files
authored
Merge pull request #4 from awslabs/spring-support
Spring support merge
2 parents af1ec22 + 5605117 commit f0accf1

File tree

50 files changed

+2427
-82
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2427
-82
lines changed

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Serverless Java container
2-
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/).
2+
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/).
33

44
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`.
55
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`).
@@ -18,16 +18,39 @@ To include the library in your Maven project, add the desired implementation to
1818
The simplest way to run your application serverlessly is to configure [API Gateway](https://aws.amazon.com/api-gateway/) to use the
1919
[`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
2020
configure your desired `LambdaContainerHandler` implementation to use `AwsProxyRequest`/`AwsProxyResponse` readers and writers. Both Spark and Jersey implementations provide static helper methods that
21-
pre-configure this for you, so your handler method would look like this:
21+
pre-configure this for you.
22+
23+
### Jersey support
24+
The library expects to receive a valid Jax RS application object. For the Jersey implementation this is the `ResourceConfig` object.
2225

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

29-
// for Spark applications, use the SparkLambdaContainerHandler.getAwsProxyHandler() helper method;
32+
public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
33+
return handler.proxy(awsProxyRequest, context);
34+
}
35+
}
36+
```
37+
38+
### Spring support
39+
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.
3040

41+
```java
42+
@Configuration
43+
@ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp")
44+
public class EchoSpringAppConfig {
45+
}
46+
```
47+
48+
Once you have declared a configuration class, you can initialize the library with the class name:
49+
```java
50+
public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
51+
SpringLambdacontainerHandler<AwsProxyRequest, AwsProxyResponse> handler =
52+
SpringLambdacontainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class)
53+
3154
public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) {
3255
return handler.proxy(awsProxyRequest, context);
3356
}

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ protected LambdaContainerHandler(RequestReader<RequestType, ContainerRequestType
6262
protected abstract ContainerResponseType getContainerResponse(CountDownLatch latch);
6363

6464

65-
protected abstract void handleRequest(ContainerRequestType containerRequest, ContainerResponseType containerResponse)
65+
protected abstract void handleRequest(ContainerRequestType containerRequest, ContainerResponseType containerResponse, Context lambdaContext)
6666
throws Exception;
6767

6868

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

88-
handleRequest(containerRequest, containerResponse);
88+
handleRequest(containerRequest, containerResponse, context);
8989

9090
latch.await();
9191

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

96-
for (StackTraceElement el : e.getStackTrace()) {
96+
/*for (StackTraceElement el : e.getStackTrace()) {
9797
context.getLogger().log(el.toString());
98-
}
98+
}*/
99+
e.printStackTrace();
99100

100101
return exceptionHandler.handle(e);
101102
}

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/ResponseWriter.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,48 @@ public abstract class ResponseWriter<ContainerResponseType, ResponseType> {
4646
*/
4747
protected abstract ResponseType writeResponse(ContainerResponseType containerResponse, Context lambdaContext)
4848
throws InvalidResponseObjectException;
49+
50+
/**
51+
* Checks whether the given byte array contains a UTF-8 encoded string
52+
* @param input The byte[] to check against
53+
* @return true if the contend is valid UTF-8, false otherwise
54+
*/
55+
protected boolean isValidUtf8(final byte[] input) {
56+
int i = 0;
57+
// Check for BOM
58+
if (input.length >= 3 && (input[0] & 0xFF) == 0xEF
59+
&& (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) {
60+
i = 3;
61+
}
62+
63+
int end;
64+
for (int j = input.length; i < j; ++i) {
65+
int octet = input[i];
66+
if ((octet & 0x80) == 0) {
67+
continue; // ASCII
68+
}
69+
70+
// Check for UTF-8 leading byte
71+
if ((octet & 0xE0) == 0xC0) {
72+
end = i + 1;
73+
} else if ((octet & 0xF0) == 0xE0) {
74+
end = i + 2;
75+
} else if ((octet & 0xF8) == 0xF0) {
76+
end = i + 3;
77+
} else {
78+
// Java only supports BMP so 3 is max
79+
return false;
80+
}
81+
82+
while (i < end) {
83+
i++;
84+
octet = input[i];
85+
if ((octet & 0xC0) != 0x80) {
86+
// Not a valid trailing byte
87+
return false;
88+
}
89+
}
90+
}
91+
return true;
92+
}
4993
}

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,27 @@
1212
*/
1313
package com.amazonaws.serverless.proxy.internal.model;
1414

15+
import java.util.HashMap;
16+
1517
/**
1618
* Custom authorizer context object for the API Gateway request context.
1719
*/
18-
public class ApiGatewayAuthorizerContext {
19-
20-
//-------------------------------------------------------------
21-
// Variables - Private
22-
//-------------------------------------------------------------
23-
24-
private String principalId;
25-
20+
public class ApiGatewayAuthorizerContext extends HashMap<String, String> {
2621

2722
//-------------------------------------------------------------
2823
// Methods - Getter/Setter
2924
//-------------------------------------------------------------
3025

3126
public String getPrincipalId() {
32-
return principalId;
27+
return get("principalId");
3328
}
3429

3530

3631
public void setPrincipalId(String principalId) {
37-
this.principalId = principalId;
32+
put("principalId", principalId);
33+
}
34+
35+
public String getContextValue(String key) {
36+
return get(key);
3837
}
3938
}

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyResponse.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class AwsProxyResponse {
2727
private int statusCode;
2828
private Map<String, String> headers;
2929
private String body;
30+
private boolean isBase64Encoded;
3031

3132

3233
//-------------------------------------------------------------
@@ -100,4 +101,12 @@ public String getBody() {
100101
public void setBody(String body) {
101102
this.body = body;
102103
}
104+
105+
public boolean isBase64Encoded() {
106+
return isBase64Encoded;
107+
}
108+
109+
public void setBase64Encoded(boolean base64Encoded) {
110+
isBase64Encoded = base64Encoded;
111+
}
103112
}

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,17 @@ public Locale getLocale() {
364364
// Methods - Package
365365
//-------------------------------------------------------------
366366

367-
String getAwsResponseBody() {
367+
String getAwsResponseBodyString() {
368368
return responseBody;
369369
}
370370

371+
byte[] getAwsResponseBodyBytes() {
372+
if (bodyOutputStream != null) {
373+
return bodyOutputStream.toByteArray();
374+
}
375+
return new byte[0];
376+
}
377+
371378

372379
Map<String, String> getAwsResponseHeaders() {
373380
Map<String, String> responseHeaders = new HashMap<>();

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ public Enumeration<String> getHeaders(String s) {
180180

181181
@Override
182182
public Enumeration<String> getHeaderNames() {
183+
if (request.getHeaders() == null) {
184+
return Collections.emptyEnumeration();
185+
}
183186
return Collections.enumeration(request.getHeaders().keySet());
184187
}
185188

@@ -220,7 +223,7 @@ public String getPathTranslated() {
220223

221224
@Override
222225
public String getContextPath() {
223-
return request.getResource();
226+
return "/";
224227
}
225228

226229

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

531534
for (Map.Entry<String, List<String>> entry : params.entrySet()) {
532-
output.put(entry.getKey(), (String[])entry.getValue().toArray());
535+
String[] valuesArray = new String[entry.getValue().size()];
536+
valuesArray = entry.getValue().toArray(valuesArray);
537+
output.put(entry.getKey(), valuesArray);
533538
}
534539
return output;
535540
}

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ public class AwsProxyHttpServletRequestReader extends RequestReader<AwsProxyRequ
3232
@Override
3333
public AwsProxyHttpServletRequest readRequest(AwsProxyRequest request, SecurityContext securityContext, Context lambdaContext)
3434
throws InvalidRequestEventException {
35-
return new AwsProxyHttpServletRequest(request, lambdaContext, securityContext);
35+
AwsProxyHttpServletRequest servletRequest = new AwsProxyHttpServletRequest(request, lambdaContext, securityContext);
36+
servletRequest.setAttribute(API_GATEWAY_CONTEXT_PROPERTY, request.getRequestContext());
37+
servletRequest.setAttribute(API_GATEWAY_STAGE_VARS_PROPERTY, request.getStageVariables());
38+
servletRequest.setAttribute(LAMBDA_CONTEXT_PROPERTY, lambdaContext);
39+
return servletRequest;
3640
}
3741

3842

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse;
1919
import com.amazonaws.services.lambda.runtime.Context;
2020

21+
import java.io.IOException;
22+
import java.util.Base64;
23+
2124
/**
2225
* Creates an <code>AwsProxyResponse</code> object given an <code>AwsHttpServletResponse</code> object. If the
2326
* response is not populated with a status code we infer a default 200 status code.
@@ -32,7 +35,18 @@ public class AwsProxyHttpServletResponseWriter extends ResponseWriter<AwsHttpSer
3235
public AwsProxyResponse writeResponse(AwsHttpServletResponse containerResponse, Context lambdaContext)
3336
throws InvalidResponseObjectException {
3437
AwsProxyResponse awsProxyResponse = new AwsProxyResponse();
35-
awsProxyResponse.setBody(containerResponse.getAwsResponseBody());
38+
if (containerResponse.getAwsResponseBodyString() != null) {
39+
String responseString;
40+
41+
if (isValidUtf8(containerResponse.getAwsResponseBodyBytes())) {
42+
responseString = containerResponse.getAwsResponseBodyString();
43+
} else {
44+
responseString = Base64.getMimeEncoder().encodeToString(containerResponse.getAwsResponseBodyBytes());
45+
awsProxyResponse.setBase64Encoded(true);
46+
}
47+
48+
awsProxyResponse.setBody(responseString);
49+
}
3650
awsProxyResponse.setHeaders(containerResponse.getAwsResponseHeaders());
3751

3852
if (containerResponse.getStatus() <= 0) {

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyServletContext.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
import java.util.Set;
4242

4343

44-
class AwsProxyServletContext
44+
public class AwsProxyServletContext
4545
implements ServletContext {
4646

4747
//-------------------------------------------------------------
@@ -218,7 +218,7 @@ public String getServerInfo() {
218218

219219
@Override
220220
public String getInitParameter(String s) {
221-
return "AWS Lambda (JDK " + System.getProperty("java.version") + ")";
221+
return null;
222222
}
223223

224224

@@ -411,11 +411,7 @@ public String getVirtualServerName() {
411411
}
412412

413413

414-
//-------------------------------------------------------------
415-
// Methods - Package
416-
//-------------------------------------------------------------
417-
418-
static ServletContext getInstance(AwsProxyRequest request, Context lambdaContext) {
414+
public static ServletContext getInstance(AwsProxyRequest request, Context lambdaContext) {
419415
if (instance == null) {
420416
instance = new AwsProxyServletContext(request, lambdaContext);
421417
}

0 commit comments

Comments
 (0)