Skip to content

Commit 361ae5a

Browse files
committed
Added method to simplify the implementation of Lambda's . This addresses #118.
1 parent da248d9 commit 361ae5a

File tree

13 files changed

+197
-56
lines changed

13 files changed

+197
-56
lines changed

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@
2222
import com.amazonaws.serverless.proxy.SecurityContextWriter;
2323
import com.amazonaws.services.lambda.runtime.Context;
2424

25+
import com.fasterxml.jackson.core.JsonParseException;
26+
import com.fasterxml.jackson.databind.JsonMappingException;
2527
import com.fasterxml.jackson.databind.ObjectMapper;
2628
import org.slf4j.Logger;
2729
import org.slf4j.LoggerFactory;
2830

2931
import javax.ws.rs.core.SecurityContext;
3032

33+
import java.io.IOException;
34+
import java.io.InputStream;
35+
import java.io.OutputStream;
3136
import java.util.concurrent.CountDownLatch;
3237

3338

@@ -56,9 +61,10 @@ public abstract class LambdaContainerHandler<RequestType, ResponseType, Containe
5661
private ResponseWriter<ContainerResponseType, ResponseType> responseWriter;
5762
private SecurityContextWriter<RequestType> securityContextWriter;
5863
private ExceptionHandler<ResponseType> exceptionHandler;
64+
private Class<RequestType> requestTypeClass;
5965

6066
protected Context lambdaContext;
61-
protected LogFormatter<ContainerRequestType, ContainerResponseType> logFormatter;
67+
private LogFormatter<ContainerRequestType, ContainerResponseType> logFormatter;
6268

6369
private Logger log = LoggerFactory.getLogger(LambdaContainerHandler.class);
6470

@@ -77,11 +83,13 @@ public abstract class LambdaContainerHandler<RequestType, ResponseType, Containe
7783
// Constructors
7884
//-------------------------------------------------------------
7985

80-
protected LambdaContainerHandler(RequestReader<RequestType, ContainerRequestType> requestReader,
86+
protected LambdaContainerHandler(Class<RequestType> requestClass,
87+
RequestReader<RequestType, ContainerRequestType> requestReader,
8188
ResponseWriter<ContainerResponseType, ResponseType> responseWriter,
8289
SecurityContextWriter<RequestType> securityContextWriter,
8390
ExceptionHandler<ResponseType> exceptionHandler) {
8491
log.info("Starting Lambda Container Handler");
92+
requestTypeClass = requestClass;
8593
this.requestReader = requestReader;
8694
this.responseWriter = responseWriter;
8795
this.securityContextWriter = securityContextWriter;
@@ -166,6 +174,34 @@ public ResponseType proxy(RequestType request, Context context) {
166174
}
167175

168176

177+
/**
178+
* Handles Lambda <code>RequestStreamHandler</code> method. The method uses an <code>ObjectMapper</code>
179+
* to transform the incoming input stream into the given {@link RequestType} and then calls the
180+
* {@link #proxy(Object, Context)} method to handle the request. The output from the proxy method is
181+
* written on the given output stream.
182+
* @param input Lambda's incoming input stream
183+
* @param output Lambda's response output stream
184+
* @param context Lambda's context object
185+
* @throws IOException If an error occurs during the stream processing
186+
*/
187+
public void proxyStream(InputStream input, OutputStream output, Context context)
188+
throws IOException {
189+
190+
try {
191+
RequestType request = getObjectMapper().readValue(input, requestTypeClass);
192+
ResponseType resp = proxy(request, context);
193+
194+
getObjectMapper().writeValue(output, resp);
195+
} catch (JsonParseException e) {
196+
log.error("Error while parsing request object stream", e);
197+
getObjectMapper().writeValue(output, exceptionHandler.handle(e));
198+
} catch (JsonMappingException e) {
199+
log.error("Error while mapping object to RequestType class", e);
200+
getObjectMapper().writeValue(output, exceptionHandler.handle(e));
201+
}
202+
}
203+
204+
169205
//-------------------------------------------------------------
170206
// Methods - Getter/Setter
171207
//-------------------------------------------------------------

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,12 @@ public abstract class AwsLambdaServletContainerHandler<RequestType, ResponseType
6969
// Constructors
7070
//-------------------------------------------------------------
7171

72-
protected AwsLambdaServletContainerHandler(RequestReader<RequestType, ContainerRequestType> requestReader,
72+
protected AwsLambdaServletContainerHandler(Class<RequestType> requestTypeClass,
73+
RequestReader<RequestType, ContainerRequestType> requestReader,
7374
ResponseWriter<ContainerResponseType, ResponseType> responseWriter,
7475
SecurityContextWriter<RequestType> securityContextWriter,
7576
ExceptionHandler<ResponseType> exceptionHandler) {
76-
super(requestReader, responseWriter, securityContextWriter, exceptionHandler);
77+
super(requestTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler);
7778
// set the default log formatter for servlet implementations
7879
setLogFormatter(new ApacheCombinedServletLogFormatter<>());
7980
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@
2727
import javax.ws.rs.core.HttpHeaders;
2828
import javax.ws.rs.core.MediaType;
2929

30+
import java.io.ByteArrayInputStream;
3031
import java.io.File;
3132
import java.io.IOException;
3233
import java.io.InputStream;
34+
import java.nio.charset.Charset;
35+
import java.nio.charset.StandardCharsets;
3336
import java.util.Base64;
3437
import java.util.HashMap;
3538

@@ -277,4 +280,13 @@ public AwsProxyRequestBuilder fromJsonPath(String filePath)
277280
public AwsProxyRequest build() {
278281
return this.request;
279282
}
283+
284+
public InputStream buildStream() {
285+
try {
286+
String requestJson = LambdaContainerHandler.getObjectMapper().writeValueAsString(request);
287+
return new ByteArrayInputStream(requestJson.getBytes(StandardCharsets.UTF_8));
288+
} catch (JsonProcessingException e) {
289+
return null;
290+
}
291+
}
280292
}

aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public class JerseyLambdaContainerHandler<RequestType, ResponseType> extends Aws
9595
* @return A <code>JerseyLambdaContainerHandler</code> object
9696
*/
9797
public static JerseyLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> getAwsProxyHandler(Application jaxRsApplication) {
98-
return new JerseyLambdaContainerHandler<>(new AwsProxyHttpServletRequestReader(),
98+
return new JerseyLambdaContainerHandler<>(AwsProxyRequest.class,
99+
new AwsProxyHttpServletRequestReader(),
99100
new AwsProxyHttpServletResponseWriter(),
100101
new AwsProxySecurityContextWriter(),
101102
new AwsProxyExceptionHandler(),
@@ -110,19 +111,21 @@ public static JerseyLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> ge
110111
/**
111112
* Private constructor for a LambdaContainer. Sets the application object, sets the ApplicationHandler,
112113
* and initializes the application using the <code>onStartup</code> method.
114+
* @param requestTypeClass The class for the expected event type
113115
* @param requestReader A request reader instance
114116
* @param responseWriter A response writer instance
115117
* @param securityContextWriter A security context writer object
116118
* @param exceptionHandler An exception handler
117119
* @param jaxRsApplication The JaxRs application
118120
*/
119-
public JerseyLambdaContainerHandler(RequestReader<RequestType, AwsProxyHttpServletRequest> requestReader,
121+
public JerseyLambdaContainerHandler(Class<RequestType> requestTypeClass,
122+
RequestReader<RequestType, AwsProxyHttpServletRequest> requestReader,
120123
ResponseWriter<AwsHttpServletResponse, ResponseType> responseWriter,
121124
SecurityContextWriter<RequestType> securityContextWriter,
122125
ExceptionHandler<ResponseType> exceptionHandler,
123126
Application jaxRsApplication) {
124127

125-
super(requestReader, responseWriter, securityContextWriter, exceptionHandler);
128+
super(requestTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler);
126129
Timer.start("JERSEY_CONTAINER_CONSTRUCTOR");
127130
this.jaxRsApplication = jaxRsApplication;
128131
this.initialized = false;

aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ public class SparkLambdaContainerHandler<RequestType, ResponseType>
106106
*/
107107
public static SparkLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> getAwsProxyHandler()
108108
throws ContainerInitializationException {
109-
return new SparkLambdaContainerHandler<>(new AwsProxyHttpServletRequestReader(),
109+
return new SparkLambdaContainerHandler<>(AwsProxyRequest.class,
110+
new AwsProxyHttpServletRequestReader(),
110111
new AwsProxyHttpServletResponseWriter(),
111112
new AwsProxySecurityContextWriter(),
112113
new AwsProxyExceptionHandler(),
@@ -118,13 +119,14 @@ public static SparkLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> get
118119
//-------------------------------------------------------------
119120

120121

121-
public SparkLambdaContainerHandler(RequestReader<RequestType, AwsProxyHttpServletRequest> requestReader,
122+
public SparkLambdaContainerHandler(Class<RequestType> requestTypeClass,
123+
RequestReader<RequestType, AwsProxyHttpServletRequest> requestReader,
122124
ResponseWriter<AwsHttpServletResponse, ResponseType> responseWriter,
123125
SecurityContextWriter<RequestType> securityContextWriter,
124126
ExceptionHandler<ResponseType> exceptionHandler,
125127
LambdaEmbeddedServerFactory embeddedServerFactory)
126128
throws ContainerInitializationException {
127-
super(requestReader, responseWriter, securityContextWriter, exceptionHandler);
129+
super(requestTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler);
128130
Timer.start("SPARK_CONTAINER_HANDLER_CONSTRUCTOR");
129131

130132
EmbeddedServers.add(LAMBDA_EMBEDDED_SERVER_CODE, embeddedServerFactory);
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.amazonaws.serverless.proxy.spark;
2+
3+
4+
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
5+
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
6+
import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder;
7+
import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext;
8+
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
9+
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
10+
11+
import org.apache.commons.io.IOUtils;
12+
import org.junit.AfterClass;
13+
import org.junit.BeforeClass;
14+
import org.junit.Test;
15+
import spark.Spark;
16+
17+
import javax.servlet.http.Cookie;
18+
import javax.ws.rs.core.HttpHeaders;
19+
20+
import java.io.ByteArrayOutputStream;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.io.OutputStream;
24+
25+
import static org.junit.Assert.assertEquals;
26+
import static org.junit.Assert.assertTrue;
27+
import static org.junit.Assert.fail;
28+
import static spark.Spark.get;
29+
30+
// This class doesn't actually test Spark. Instead it tests the proxyStream method of the
31+
// LambdaContainerHandler object. We use the Spark implementation for this because it's the
32+
// fastest to start
33+
public class HelloWorldSparkStreamTest {
34+
private static final String CUSTOM_HEADER_KEY = "X-Custom-Header";
35+
private static final String CUSTOM_HEADER_VALUE = "My Header Value";
36+
private static final String BODY_TEXT_RESPONSE = "Hello World";
37+
38+
private static final String COOKIE_NAME = "MyCookie";
39+
private static final String COOKIE_VALUE = "CookieValue";
40+
private static final String COOKIE_DOMAIN = "mydomain.com";
41+
private static final String COOKIE_PATH = "/";
42+
43+
private static SparkLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
44+
45+
@BeforeClass
46+
public static void initializeServer() {
47+
try {
48+
handler = SparkLambdaContainerHandler.getAwsProxyHandler();
49+
50+
configureRoutes();
51+
Spark.awaitInitialization();
52+
} catch (RuntimeException | ContainerInitializationException e) {
53+
e.printStackTrace();
54+
fail();
55+
}
56+
}
57+
58+
@AfterClass
59+
public static void stopSpark() {
60+
Spark.stop();
61+
}
62+
63+
@Test
64+
public void helloRequest_basicStream_populatesOutputSuccessfully() {
65+
InputStream req = new AwsProxyRequestBuilder().method("GET").path("/hello").buildStream();
66+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
67+
try {
68+
handler.proxyStream(req, outputStream, new MockLambdaContext());
69+
AwsProxyResponse response = LambdaContainerHandler.getObjectMapper().readValue(outputStream.toByteArray(), AwsProxyResponse.class);
70+
71+
assertEquals(200, response.getStatusCode());
72+
assertTrue(response.getHeaders().containsKey(CUSTOM_HEADER_KEY));
73+
assertEquals(CUSTOM_HEADER_VALUE, response.getHeaders().get(CUSTOM_HEADER_KEY));
74+
assertEquals(BODY_TEXT_RESPONSE, response.getBody());
75+
} catch (IOException e) {
76+
e.printStackTrace();
77+
fail();
78+
}
79+
}
80+
81+
private static void configureRoutes() {
82+
get("/hello", (req, res) -> {
83+
res.status(200);
84+
res.header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE);
85+
return BODY_TEXT_RESPONSE;
86+
});
87+
88+
get("/cookie", (req, res) -> {
89+
Cookie testCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE);
90+
testCookie.setDomain(COOKIE_DOMAIN);
91+
testCookie.setPath(COOKIE_PATH);
92+
res.raw().addCookie(testCookie);
93+
return BODY_TEXT_RESPONSE;
94+
});
95+
96+
get("/multi-cookie", (req, res) -> {
97+
Cookie testCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE);
98+
testCookie.setDomain(COOKIE_DOMAIN);
99+
testCookie.setPath(COOKIE_PATH);
100+
Cookie testCookie2 = new Cookie(COOKIE_NAME + "2", COOKIE_VALUE + "2");
101+
testCookie2.setDomain(COOKIE_DOMAIN);
102+
testCookie2.setPath(COOKIE_PATH);
103+
res.raw().addCookie(testCookie);
104+
res.raw().addCookie(testCookie2);
105+
return BODY_TEXT_RESPONSE;
106+
});
107+
}
108+
}

aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter;
77
import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader;
88
import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter;
9+
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
910
import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServer;
1011
import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServerFactory;
1112

@@ -37,11 +38,12 @@ public void initException_mockException_expectHandlerToRun() {
3738
when(embeddedServer.ignite(anyString(), anyInt(), anyObject(), anyInt(), anyInt(), anyInt()))
3839
.thenThrow(new ContainerInitializationException(TEST_EXCEPTION_MESSAGE, null));
3940
LambdaEmbeddedServerFactory serverFactory = new LambdaEmbeddedServerFactory(embeddedServer);
40-
new SparkLambdaContainerHandler<>(new AwsProxyHttpServletRequestReader(),
41-
new AwsProxyHttpServletResponseWriter(),
42-
new AwsProxySecurityContextWriter(),
43-
new AwsProxyExceptionHandler(),
44-
serverFactory);
41+
new SparkLambdaContainerHandler<>(AwsProxyRequest.class,
42+
new AwsProxyHttpServletRequestReader(),
43+
new AwsProxyHttpServletResponseWriter(),
44+
new AwsProxySecurityContextWriter(),
45+
new AwsProxyExceptionHandler(),
46+
serverFactory);
4547

4648
configureRoutes();
4749
Spark.awaitInitialization();

aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public class SpringBootLambdaContainerHandler<RequestType, ResponseType> extends
6666
public static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> getAwsProxyHandler(Class<? extends WebApplicationInitializer> springBootInitializer)
6767
throws ContainerInitializationException {
6868
return new SpringBootLambdaContainerHandler<>(
69+
AwsProxyRequest.class,
6970
new AwsProxyHttpServletRequestReader(),
7071
new AwsProxyHttpServletResponseWriter(),
7172
new AwsProxySecurityContextWriter(),
@@ -77,20 +78,22 @@ public static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse
7778
/**
7879
* Creates a new container handler with the given reader and writer objects
7980
*
81+
* @param requestTypeClass The class for the incoming Lambda event
8082
* @param requestReader An implementation of `RequestReader`
8183
* @param responseWriter An implementation of `ResponseWriter`
8284
* @param securityContextWriter An implementation of `SecurityContextWriter`
8385
* @param exceptionHandler An implementation of `ExceptionHandler`
8486
* @param springBootInitializer {@code SpringBootServletInitializer} class
8587
* @throws ContainerInitializationException If an error occurs while initializing the Spring framework
8688
*/
87-
public SpringBootLambdaContainerHandler(RequestReader<RequestType, AwsProxyHttpServletRequest> requestReader,
89+
public SpringBootLambdaContainerHandler(Class<RequestType> requestTypeClass,
90+
RequestReader<RequestType, AwsProxyHttpServletRequest> requestReader,
8891
ResponseWriter<AwsHttpServletResponse, ResponseType> responseWriter,
8992
SecurityContextWriter<RequestType> securityContextWriter,
9093
ExceptionHandler<ResponseType> exceptionHandler,
9194
Class<? extends WebApplicationInitializer> springBootInitializer)
9295
throws ContainerInitializationException {
93-
super(requestReader, responseWriter, securityContextWriter, exceptionHandler);
96+
super(requestTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler);
9497
Timer.start("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR");
9598
this.springBootInitializer = springBootInitializer;
9699
Timer.stop("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR");

0 commit comments

Comments
 (0)