Skip to content

Commit e991c56

Browse files
authored
Merge pull request #283 from awslabs/core
Preparing for 1.4 release
2 parents 949807a + aa72b6d commit e991c56

File tree

141 files changed

+4887
-1031
lines changed

Some content is hidden

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

141 files changed

+4887
-1031
lines changed

aws-serverless-java-container-core/pom.xml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
</parent>
1717

1818
<properties>
19-
<jackson.version>2.9.9</jackson.version>
2019
<jaxrs.version>2.1</jaxrs.version>
2120
<servlet.version>3.1.0</servlet.version>
2221
</properties>
@@ -55,6 +54,12 @@
5554
<groupId>com.fasterxml.jackson.module</groupId>
5655
<artifactId>jackson-module-afterburner</artifactId>
5756
<version>${jackson.version}</version>
57+
<exclusions>
58+
<exclusion>
59+
<groupId>com.fasterxml.jackson.core</groupId>
60+
<artifactId>jackson-databind</artifactId>
61+
</exclusion>
62+
</exclusions>
5863
</dependency>
5964

6065

@@ -86,6 +91,12 @@
8691
<version>4.4.10</version>
8792
<scope>compile</scope>
8893
</dependency>
94+
<dependency>
95+
<groupId>org.springframework.security</groupId>
96+
<artifactId>spring-security-web</artifactId>
97+
<version>5.1.5.RELEASE</version>
98+
<scope>test</scope>
99+
</dependency>
89100
</dependencies>
90101

91102
<build>
@@ -97,6 +108,9 @@
97108
<configuration>
98109
<destFile>${basedir}/target/coverage-reports/jacoco-unit.exec</destFile>
99110
<dataFile>${basedir}/target/coverage-reports/jacoco-unit.exec</dataFile>
111+
<excludes>
112+
<exclude>com/amazonaws/serverless/proxy/internal/testutils/**</exclude>
113+
</excludes>
100114
</configuration>
101115
<executions>
102116
<execution>
@@ -183,6 +197,7 @@
183197
<suppressionFile>${project.basedir}/../owasp-suppression.xml</suppressionFile>
184198
</suppressionFiles>
185199
<failBuildOnCVSS>7</failBuildOnCVSS>
200+
<failOnError>false</failOnError>
186201
</configuration>
187202
<executions>
188203
<execution>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.amazonaws.serverless.proxy;
2+
3+
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
4+
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
5+
import com.amazonaws.services.lambda.runtime.Context;
6+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import java.time.Instant;
11+
import java.util.concurrent.CountDownLatch;
12+
import java.util.concurrent.TimeUnit;
13+
14+
/**
15+
* An async implementation of the <code>InitializationWrapper</code> interface. This initializer calls the
16+
* {@link LambdaContainerHandler#initialize()} in a separate thread. Then uses a latch to wait for the maximum Lambda
17+
* initialization time of 10 seconds, if the <code>initialize</code> method takes longer than 10 seconds to return, the
18+
* {@link #start(LambdaContainerHandler)} returns control to the caller and lets the initialization thread continue in
19+
* the background. The {@link LambdaContainerHandler#proxy(Object, Context)} automatically waits for the latch of the
20+
* initializer to be released.
21+
*
22+
* The constructor of this class expects an epoch long. This is meant to be as close as possible to the time the Lambda
23+
* function actually started. In most cases, the first action in the constructor of the handler class should be to populate
24+
* this long value ({@code Instant.now().toEpochMs();}). This class uses the value to estimate how much of the init 10
25+
* seconds has already been used up.
26+
*/
27+
public class AsyncInitializationWrapper extends InitializationWrapper {
28+
private static final int INIT_GRACE_TIME_MS = 250;
29+
private static final int LAMBDA_MAX_INIT_TIME_MS = 10_000;
30+
31+
private CountDownLatch initializationLatch;
32+
private long actualStartTime;
33+
private Logger log = LoggerFactory.getLogger(AsyncInitializationWrapper.class);
34+
35+
/**
36+
* Creates a new instance of the async initializer.
37+
* @param startTime The epoch ms start time of the Lambda function, this should be measured as close as possible to
38+
* the initialization of the function.
39+
*/
40+
public AsyncInitializationWrapper(long startTime) {
41+
actualStartTime = startTime;
42+
}
43+
44+
@Override
45+
public void start(LambdaContainerHandler handler) throws ContainerInitializationException {
46+
initializationLatch = new CountDownLatch(1);
47+
AsyncInitializer initializer = new AsyncInitializer(initializationLatch, handler);
48+
Thread initThread = new Thread(initializer);
49+
initThread.start();
50+
try {
51+
long curTime = Instant.now().toEpochMilli();
52+
// account for the time it took to call the various constructors with the actual start time + a grace of 500ms
53+
long awaitTime = LAMBDA_MAX_INIT_TIME_MS - (curTime - actualStartTime) - INIT_GRACE_TIME_MS;
54+
log.info("Async initialization will wait for " + awaitTime + "ms");
55+
if (!initializationLatch.await(awaitTime, TimeUnit.MILLISECONDS)) {
56+
log.info("Initialization took longer than " + LAMBDA_MAX_INIT_TIME_MS + ", setting new CountDownLatch and " +
57+
"continuing in event handler");
58+
initializationLatch = new CountDownLatch(1);
59+
initializer.replaceLatch(initializationLatch);
60+
}
61+
} catch (InterruptedException e) {
62+
// at the moment we assume that this happened because of a timeout since the init thread calls System.exit
63+
// when an exception is thrown.
64+
throw new ContainerInitializationException("Container initialization interrupted", e);
65+
}
66+
}
67+
68+
@Override
69+
public CountDownLatch getInitializationLatch() {
70+
return initializationLatch;
71+
}
72+
73+
private static class AsyncInitializer implements Runnable {
74+
private LambdaContainerHandler handler;
75+
private CountDownLatch initLatch;
76+
private Logger log = LoggerFactory.getLogger(AsyncInitializationWrapper.class);
77+
78+
AsyncInitializer(CountDownLatch latch, LambdaContainerHandler h) {
79+
initLatch = latch;
80+
handler = h;
81+
}
82+
83+
synchronized void replaceLatch(CountDownLatch newLatch) {
84+
initLatch = newLatch;
85+
}
86+
87+
@Override
88+
@SuppressFBWarnings("DM_EXIT")
89+
public void run() {
90+
log.info("Starting async initializer");
91+
try {
92+
handler.initialize();
93+
} catch (ContainerInitializationException e) {
94+
log.error("Failed to initialize container handler", e);
95+
// we cannot return the exception so we crash the whole kaboodle here
96+
System.exit(1);
97+
}
98+
synchronized(this) {
99+
initLatch.countDown();
100+
}
101+
}
102+
}
103+
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.slf4j.Logger;
2323
import org.slf4j.LoggerFactory;
2424

25+
import javax.ws.rs.InternalServerErrorException;
2526
import javax.ws.rs.core.HttpHeaders;
2627
import javax.ws.rs.core.MediaType;
2728

@@ -31,7 +32,8 @@
3132
/**
3233
* Default implementation of the <code>ExceptionHandler</code> object that returns AwsProxyResponse objects.
3334
*
34-
* Returns application/json messages with a status code of 500 when the RequestReader failed to read the incoming event.
35+
* Returns application/json messages with a status code of 500 when the RequestReader failed to read the incoming event
36+
* or if InternalServerErrorException is thrown.
3537
* For all other exceptions returns a 502. Responses are populated with a JSON object containing a message property.
3638
*
3739
* @see ExceptionHandler
@@ -76,7 +78,7 @@ public AwsProxyResponse handle(Throwable ex) {
7678
// adding a print stack trace in case we have no appender or we are running inside SAM local, where need the
7779
// output to go to the stderr.
7880
ex.printStackTrace();
79-
if (ex instanceof InvalidRequestEventException) {
81+
if (ex instanceof InvalidRequestEventException || ex instanceof InternalServerErrorException) {
8082
return new AwsProxyResponse(500, headers, getErrorJson(INTERNAL_SERVER_ERROR));
8183
} else {
8284
return new AwsProxyResponse(502, headers, getErrorJson(GATEWAY_TIMEOUT_ERROR));
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.amazonaws.serverless.proxy;
2+
3+
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
4+
import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler;
5+
6+
import java.util.concurrent.CountDownLatch;
7+
8+
/**
9+
* This class is in charge of initializing a {@link LambdaContainerHandler}.
10+
* In most cases, this means calling the {@link LambdaContainerHandler#initialize()} method. Some implementations may
11+
* require additional initialization steps, in this case implementations should provide their own
12+
* <code>InitializationWrapper</code>. This library includes an async implementation of this class
13+
* {@link AsyncInitializationWrapper} for frameworks that are likely to take longer than 10 seconds to start.
14+
*/
15+
public class InitializationWrapper {
16+
/**
17+
* This is the main entry point. Container handler builder and the static <code>getAwsProxyHandler()</code> methods
18+
* of the various implementations will call this to initialize the underlying framework
19+
* @param handler The container handler to be initializer
20+
* @throws ContainerInitializationException If anything goes wrong during container initialization.
21+
*/
22+
public void start(LambdaContainerHandler handler) throws ContainerInitializationException {
23+
handler.initialize();
24+
}
25+
26+
/**
27+
* Asynchronous implementations of the framework should return a latch that the container handler can use to decide
28+
* whether it can start handling events. Synchronous implementations of this interface should return <code>null</code>.
29+
* @return An initialized latch if the underlying container is starting in a separate thread, null otherwise.
30+
*/
31+
public CountDownLatch getInitializationLatch() {
32+
return null;
33+
}
34+
}

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

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,9 @@
1414

1515

1616
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
17-
import com.amazonaws.serverless.proxy.LogFormatter;
17+
import com.amazonaws.serverless.proxy.*;
1818
import com.amazonaws.serverless.proxy.internal.servlet.ApacheCombinedServletLogFormatter;
1919
import com.amazonaws.serverless.proxy.model.ContainerConfig;
20-
import com.amazonaws.serverless.proxy.ExceptionHandler;
21-
import com.amazonaws.serverless.proxy.RequestReader;
22-
import com.amazonaws.serverless.proxy.ResponseWriter;
23-
import com.amazonaws.serverless.proxy.SecurityContextWriter;
2420
import com.amazonaws.services.lambda.runtime.Context;
2521

2622
import com.fasterxml.jackson.core.JsonParseException;
@@ -38,6 +34,7 @@
3834
import java.io.InputStream;
3935
import java.io.OutputStream;
4036
import java.util.concurrent.CountDownLatch;
37+
import java.util.concurrent.TimeUnit;
4138

4239

4340
/**
@@ -67,6 +64,7 @@ public abstract class LambdaContainerHandler<RequestType, ResponseType, Containe
6764
private ExceptionHandler<ResponseType> exceptionHandler;
6865
private Class<RequestType> requestTypeClass;
6966
private Class<ResponseType> responseTypeClass;
67+
private InitializationWrapper initializationWrapper;
7068

7169
protected Context lambdaContext;
7270
private LogFormatter<ContainerRequestType, ContainerResponseType> logFormatter;
@@ -97,16 +95,28 @@ protected LambdaContainerHandler(Class<RequestType> requestClass,
9795
RequestReader<RequestType, ContainerRequestType> requestReader,
9896
ResponseWriter<ContainerResponseType, ResponseType> responseWriter,
9997
SecurityContextWriter<RequestType> securityContextWriter,
100-
ExceptionHandler<ResponseType> exceptionHandler) {
98+
ExceptionHandler<ResponseType> exceptionHandler,
99+
InitializationWrapper init) {
101100
log.info("Starting Lambda Container Handler");
102101
requestTypeClass = requestClass;
103102
responseTypeClass = responseClass;
104103
this.requestReader = requestReader;
105104
this.responseWriter = responseWriter;
106105
this.securityContextWriter = securityContextWriter;
107106
this.exceptionHandler = exceptionHandler;
107+
initializationWrapper = init;
108108
objectReader = getObjectMapper().readerFor(requestTypeClass);
109109
objectWriter = getObjectMapper().writerFor(responseTypeClass);
110+
111+
}
112+
113+
protected LambdaContainerHandler(Class<RequestType> requestClass,
114+
Class<ResponseType> responseClass,
115+
RequestReader<RequestType, ContainerRequestType> requestReader,
116+
ResponseWriter<ContainerResponseType, ResponseType> responseWriter,
117+
SecurityContextWriter<RequestType> securityContextWriter,
118+
ExceptionHandler<ResponseType> exceptionHandler) {
119+
this(requestClass, responseClass, requestReader, responseWriter, securityContextWriter, exceptionHandler, new InitializationWrapper());
110120
}
111121

112122

@@ -131,6 +141,23 @@ public static ObjectMapper getObjectMapper() {
131141
return objectMapper;
132142
}
133143

144+
/**
145+
* Returns the initialization wrapper this container handler will monitor to handle events
146+
* @return The initialization wrapper that was passed to the constructor and this instance will use to decide
147+
* whether it can start handling events.
148+
*/
149+
public InitializationWrapper getInitializationWrapper() {
150+
return initializationWrapper;
151+
}
152+
153+
/**
154+
* Sets a new initialization wrapper.
155+
* @param wrapper The wrapper this instance will use to decide whether it can start handling events.
156+
*/
157+
public void setInitializationWrapper(InitializationWrapper wrapper) {
158+
initializationWrapper = wrapper;
159+
}
160+
134161
/**
135162
* Configures the library to strip a base path from incoming requests before passing them on to the wrapped
136163
* framework. This was added in response to issue #34 (https://github.com/awslabs/aws-serverless-java-container/issues/34).
@@ -168,12 +195,19 @@ public void setLogFormatter(LogFormatter<ContainerRequestType, ContainerResponse
168195
*/
169196
public ResponseType proxy(RequestType request, Context context) {
170197
lambdaContext = context;
198+
CountDownLatch latch = new CountDownLatch(1);
171199
try {
172200
SecurityContext securityContext = securityContextWriter.writeSecurityContext(request, context);
173-
CountDownLatch latch = new CountDownLatch(1);
174201
ContainerRequestType containerRequest = requestReader.readRequest(request, securityContext, context, config);
175202
ContainerResponseType containerResponse = getContainerResponse(containerRequest, latch);
176203

204+
if (initializationWrapper != null && initializationWrapper.getInitializationLatch() != null) {
205+
// we let the potential InterruptedException bubble up
206+
if (!initializationWrapper.getInitializationLatch().await(config.getInitializationTimeout(), TimeUnit.MILLISECONDS)) {
207+
throw new ContainerInitializationException("Could not initialize framework within the " + config.getInitializationTimeout() + "ms timeout", null);
208+
}
209+
}
210+
177211
handleRequest(containerRequest, containerResponse, context);
178212

179213
latch.await();
@@ -185,6 +219,9 @@ public ResponseType proxy(RequestType request, Context context) {
185219
return responseWriter.writeResponse(containerResponse, context);
186220
} catch (Exception e) {
187221
log.error("Error while handling request", e);
222+
// release all waiting threads. This is safe here because if the count was already 0
223+
// the latch will do nothing
224+
latch.countDown();
188225

189226
return exceptionHandler.handle(e);
190227
}

0 commit comments

Comments
 (0)