Skip to content

Commit b540553

Browse files
authored
Merge pull request #61 from awslabs/spark-upgrade
Spark upgrade
2 parents 628ed1b + 4fbe380 commit b540553

File tree

13 files changed

+168
-68
lines changed

13 files changed

+168
-68
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyRe
104104
}
105105
```
106106

107+
If you configure an [`initExceptionHandler` method](http://sparkjava.com/documentation#stopping-the-server), make sure that you call `System.exit` at the end of the method. This framework keeps a `CountDownLatch` on the request
108+
and unless you forcefully exit from the thread, the Lambda function will hang waiting for a latch that is never released.
109+
110+
```java
111+
initExceptionHandler((e) -> {
112+
LOG.error("ignite failed", e);
113+
System.exit(100);
114+
});
115+
```
116+
107117
# Security context
108118
The `aws-serverless-java-container-core` contains a default implementation of the `SecurityContextWriter` that supports API Gateway's proxy integration. The generated security context uses the API Gateway `$context` object to establish the request security context. The context looks for the following values in order and returns the first matched type:
109119

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

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,6 @@
5050
<version>1.3.2</version>
5151
</dependency>
5252

53-
<!-- https://mvnrepository.com/artifact/junit/junit -->
54-
<dependency>
55-
<groupId>junit</groupId>
56-
<artifactId>junit</artifactId>
57-
<version>4.12</version>
58-
<scope>test</scope>
59-
</dependency>
60-
61-
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
62-
<dependency>
63-
<groupId>org.mockito</groupId>
64-
<artifactId>mockito-all</artifactId>
65-
<version>1.10.19</version>
66-
<scope>test</scope>
67-
</dependency>
68-
6953
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime -->
7054
<dependency>
7155
<groupId>org.apache.httpcomponents</groupId>

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@
3333
<version>${jersey.version}</version>
3434
</dependency>
3535

36-
<!-- https://mvnrepository.com/artifact/junit/junit -->
37-
<dependency>
38-
<groupId>junit</groupId>
39-
<artifactId>junit</artifactId>
40-
<version>4.12</version>
41-
<scope>test</scope>
42-
</dependency>
43-
4436
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
4537
<dependency>
4638
<groupId>commons-codec</groupId>

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

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
</parent>
1616

1717
<properties>
18-
<spark.version>2.5.3</spark.version>
18+
<spark.version>2.6.0</spark.version>
1919
</properties>
2020

2121
<dependencies>
@@ -31,21 +31,14 @@
3131
<groupId>com.sparkjava</groupId>
3232
<artifactId>spark-core</artifactId>
3333
<version>${spark.version}</version>
34+
<!-- excluding slf4j from spark because it includes 1.7.13, the framework already pulls in 1.7.25 -->
3435
<exclusions>
3536
<exclusion>
3637
<groupId>org.slf4j</groupId>
3738
<artifactId>slf4j-api</artifactId>
3839
</exclusion>
3940
</exclusions>
4041
</dependency>
41-
42-
<!-- https://mvnrepository.com/artifact/junit/junit -->
43-
<dependency>
44-
<groupId>junit</groupId>
45-
<artifactId>junit</artifactId>
46-
<version>4.12</version>
47-
<scope>test</scope>
48-
</dependency>
4942
</dependencies>
5043

5144
</project>

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.slf4j.LoggerFactory;
2727
import spark.Service;
2828
import spark.Spark;
29+
import spark.embeddedserver.EmbeddedServerFactory;
2930
import spark.embeddedserver.EmbeddedServers;
3031

3132
import java.lang.reflect.Field;
@@ -91,7 +92,8 @@ public static SparkLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> get
9192
return new SparkLambdaContainerHandler<>(new AwsProxyHttpServletRequestReader(),
9293
new AwsProxyHttpServletResponseWriter(),
9394
new AwsProxySecurityContextWriter(),
94-
new AwsProxyExceptionHandler());
95+
new AwsProxyExceptionHandler(),
96+
new LambdaEmbeddedServerFactory());
9597
}
9698

9799

@@ -102,31 +104,34 @@ public static SparkLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> get
102104
public SparkLambdaContainerHandler(RequestReader<RequestType, AwsProxyHttpServletRequest> requestReader,
103105
ResponseWriter<AwsHttpServletResponse, ResponseType> responseWriter,
104106
SecurityContextWriter<RequestType> securityContextWriter,
105-
ExceptionHandler<ResponseType> exceptionHandler)
107+
ExceptionHandler<ResponseType> exceptionHandler,
108+
EmbeddedServerFactory embeddedServerFactory)
106109
throws ContainerInitializationException {
107110
super(requestReader, responseWriter, securityContextWriter, exceptionHandler);
108111

109-
EmbeddedServers.add(LAMBDA_EMBEDDED_SERVER_CODE, new LambdaEmbeddedServerFactory());
112+
EmbeddedServers.add(LAMBDA_EMBEDDED_SERVER_CODE, embeddedServerFactory);
110113

111114
// TODO: This is pretty bad but we are not given access to the embeddedServerIdentifier property of the
112115
// Service object
113116
try {
117+
log.debug("Changing visibility of getInstance method and embeddedServerIdentifier properties");
114118
Method serviceInstanceMethod = Spark.class.getDeclaredMethod("getInstance");
115119
serviceInstanceMethod.setAccessible(true);
116120
Service sparkService = (Service) serviceInstanceMethod.invoke(null);
117121
Field serverIdentifierField = Service.class.getDeclaredField("embeddedServerIdentifier");
118122
serverIdentifierField.setAccessible(true);
119123
serverIdentifierField.set(sparkService, LAMBDA_EMBEDDED_SERVER_CODE);
120-
121-
// remove Jetty from embedded servers
122-
EmbeddedServers.class.getDeclaredMethod("initialize");
123124
} catch (NoSuchFieldException e) {
125+
log.error("Could not fine embeddedServerIdentifier field in Service class", e);
124126
throw new ContainerInitializationException("Cannot find embeddedServerIdentifier field in Service class", e);
125127
} catch (NoSuchMethodException e) {
128+
log.error("Could not find getInstance method in Spark class", e);
126129
throw new ContainerInitializationException("Cannot find getInstance method in Spark class", e);
127130
} catch (IllegalAccessException e) {
131+
log.error("Could not access getInstance method in Spark class", e);
128132
throw new ContainerInitializationException("Cannot access getInstance method in Spark class", e);
129133
} catch (InvocationTargetException e) {
134+
log.error("Could not invoke getInstance method in Spark class", e);
130135
throw new ContainerInitializationException("Cannot invoke getInstance method in Spark class", e);
131136
}
132137
}
@@ -145,10 +150,12 @@ protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest
145150
@Override
146151
protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext)
147152
throws Exception {
153+
148154
// this method of the AwsLambdaServletContainerHandler sets the request context
149155
super.handleRequest(httpServletRequest, httpServletResponse, lambdaContext);
150156

151157
if (embeddedServer == null) {
158+
log.debug("First request, getting new server instance");
152159
embeddedServer = LambdaEmbeddedServerFactory.getServerInstance();
153160

154161
// call the onStartup event if set to give developers a chance to set filters in the context

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

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import java.io.IOException;
1616
import java.util.Map;
1717
import java.util.Optional;
18-
import java.util.concurrent.CountDownLatch;
1918

2019
public class LambdaEmbeddedServer
2120
implements EmbeddedServer {
@@ -45,19 +44,14 @@ public class LambdaEmbeddedServer
4544
//-------------------------------------------------------------
4645
// Implementation - EmbeddedServer
4746
//-------------------------------------------------------------
48-
4947
@Override
50-
public int ignite(String host,
51-
int port,
52-
SslStores sslStores,
53-
CountDownLatch countDownLatch,
54-
int maxThreads,
55-
int minThreads,
56-
int threadIdleTimeoutMillis) {
48+
public int ignite(String s, int i, SslStores sslStores, int i1, int i2, int i3)
49+
throws Exception {
50+
log.info("Starting Spark server, ignoring port and host");
5751
sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, false, hasMultipleHandler);
5852
sparkFilter.init(null);
5953

60-
countDownLatch.countDown();
54+
//countDownLatch.countDown();
6155

6256
return 0;
6357
}
@@ -71,18 +65,31 @@ public void configureWebSockets(Map<String, WebSocketHandlerWrapper> webSocketHa
7165
}
7266

7367

68+
@Override
69+
public void join()
70+
throws InterruptedException {
71+
log.info("Called join method, nothing to do here since Lambda only runs a single event per container");
72+
}
73+
74+
7475
@Override
7576
public void extinguish() {
77+
log.info("Called extinguish method, nothing to do here.");
7678
}
7779

7880

81+
@Override
82+
public int activeThreadCount() {
83+
log.debug("Called activeThreadCount, since Lambda only runs one event per container we always return 1");
84+
return 1;
85+
}
86+
7987
//-------------------------------------------------------------
8088
// Methods - Public
8189
//-------------------------------------------------------------
8290

8391
public void handle(HttpServletRequest request, HttpServletResponse response)
8492
throws IOException, ServletException {
85-
//RouteMatch route = applicationRoutes.find(HttpMethod.get(request.requestMethod()), request.contextPath(), "*/*");
8693
sparkFilter.doFilter(request, response, null);
8794
}
8895
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ public class LambdaEmbeddedServerFactory implements EmbeddedServerFactory {
1414
private static LambdaEmbeddedServer embeddedServer;
1515

1616

17+
/**
18+
* Empty constructor, applications should always use this constructor.
19+
*/
20+
public LambdaEmbeddedServerFactory() {}
21+
22+
23+
/**
24+
* Constructor used in unit tests to inject a mocked embedded server
25+
* @param server The mocked server
26+
*/
27+
public LambdaEmbeddedServerFactory(LambdaEmbeddedServer server) {
28+
embeddedServer = server;
29+
}
30+
31+
1732
//-------------------------------------------------------------
1833
// Implementation - EmbeddedServerFactory
1934
//-------------------------------------------------------------

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder;
88
import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext;
99

10+
import org.junit.AfterClass;
1011
import org.junit.BeforeClass;
1112
import org.junit.Test;
13+
import spark.Spark;
1214

1315
import javax.servlet.http.Cookie;
1416
import javax.ws.rs.core.HttpHeaders;
@@ -22,10 +24,10 @@ public class HelloWorldSparkTest {
2224
private static final String CUSTOM_HEADER_VALUE = "My Header Value";
2325
private static final String BODY_TEXT_RESPONSE = "Hello World";
2426

25-
public static final String COOKIE_NAME = "MyCookie";
26-
public static final String COOKIE_VALUE = "CookieValue";
27-
public static final String COOKIE_DOMAIN = "mydomain.com";
28-
public static final String COOKIE_PATH = "/";
27+
private static final String COOKIE_NAME = "MyCookie";
28+
private static final String COOKIE_VALUE = "CookieValue";
29+
private static final String COOKIE_DOMAIN = "mydomain.com";
30+
private static final String COOKIE_PATH = "/";
2931

3032
private static SparkLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
3133

@@ -42,6 +44,11 @@ public static void initializeServer() {
4244
}
4345
}
4446

47+
@AfterClass
48+
public static void stopSpark() {
49+
Spark.stop();
50+
}
51+
4552
@Test
4653
public void basicServer_handleRequest_emptyFilters() {
4754
AwsProxyRequest req = new AwsProxyRequestBuilder().method("GET").path("/hello").build();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.amazonaws.serverless.proxy.spark;
2+
3+
4+
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
5+
import com.amazonaws.serverless.proxy.internal.AwsProxyExceptionHandler;
6+
import com.amazonaws.serverless.proxy.internal.AwsProxySecurityContextWriter;
7+
import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader;
8+
import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter;
9+
import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServer;
10+
import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServerFactory;
11+
12+
import org.junit.AfterClass;
13+
import org.junit.Test;
14+
import spark.Spark;
15+
16+
import static org.junit.Assert.assertEquals;
17+
import static org.junit.Assert.fail;
18+
import static org.mockito.Matchers.anyInt;
19+
import static org.mockito.Matchers.anyObject;
20+
import static org.mockito.Matchers.anyString;
21+
import static org.mockito.Mockito.mock;
22+
import static org.mockito.Mockito.reset;
23+
import static org.mockito.Mockito.when;
24+
import static spark.Spark.get;
25+
import static spark.Spark.initExceptionHandler;
26+
27+
28+
public class InitExceptionHandlerTest {
29+
30+
private static final String TEST_EXCEPTION_MESSAGE = "test exception";
31+
private static LambdaEmbeddedServer embeddedServer = mock(LambdaEmbeddedServer.class);
32+
33+
@Test
34+
public void initException_mockException_expectHandlerToRun() {
35+
try {
36+
37+
when(embeddedServer.ignite(anyString(), anyInt(), anyObject(), anyInt(), anyInt(), anyInt()))
38+
.thenThrow(new ContainerInitializationException(TEST_EXCEPTION_MESSAGE, null));
39+
LambdaEmbeddedServerFactory serverFactory = new LambdaEmbeddedServerFactory(embeddedServer);
40+
new SparkLambdaContainerHandler<>(new AwsProxyHttpServletRequestReader(),
41+
new AwsProxyHttpServletResponseWriter(),
42+
new AwsProxySecurityContextWriter(),
43+
new AwsProxyExceptionHandler(),
44+
serverFactory);
45+
46+
configureRoutes();
47+
48+
} catch (Exception e) {
49+
e.printStackTrace();
50+
fail("Error while mocking server");
51+
}
52+
53+
}
54+
55+
@AfterClass
56+
public static void stopSpark() {
57+
// un-mock the embedded server to avoid blocking other tests
58+
reset(embeddedServer);
59+
// reset the static variable in the factory so that it will be instantiated again next time
60+
new LambdaEmbeddedServerFactory(null);
61+
Spark.stop();
62+
}
63+
64+
private static void configureRoutes() {
65+
initExceptionHandler((e) -> {
66+
System.out.println("Exception Handler called: " + e.getLocalizedMessage());
67+
assertEquals(TEST_EXCEPTION_MESSAGE, e.getLocalizedMessage());
68+
});
69+
70+
get("/test-route", (req, res) -> {
71+
res.status(200);
72+
return "test";
73+
});
74+
}
75+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext;
99
import com.amazonaws.serverless.proxy.spark.filter.CustomHeaderFilter;
1010

11+
import org.junit.AfterClass;
1112
import org.junit.Test;
13+
import spark.Spark;
1214

1315
import javax.servlet.DispatcherType;
1416
import javax.servlet.FilterRegistration;
@@ -57,6 +59,11 @@ public void filters_onStartupMethod_executeFilters() {
5759

5860
}
5961

62+
@AfterClass
63+
public static void stopSpark() {
64+
Spark.stop();
65+
}
66+
6067
private void configureRoutes() {
6168
get("/header-filter", (req, res) -> {
6269
res.status(200);

0 commit comments

Comments
 (0)