Skip to content

Commit e631efd

Browse files
authored
Merge pull request #52 from awslabs/servlet-improvements
Merge issues for release 0.6
2 parents 88adfdf + 316a155 commit e631efd

File tree

30 files changed

+373
-128
lines changed

30 files changed

+373
-128
lines changed

README.md

+19
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyRe
6262
#### Spring Profiles
6363
You can enable Spring Profiles (as defined with the `@Profile` annotation) by using the `SpringLambdaContainerHandler.activateSpringProfiles(String...)` method - common drivers of this might be the AWS Lambda stage that you're deployed under, or stage variables. See [@Profile documentation](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html) for details.
6464

65+
#### Spring Boot
66+
You can also use this framework to start Spring Boot applications inside Lambda. The framework does not recognize classes annotated with `@SpringBootApplication` automatically. However, you can wrap the Spring Boot application class in a regular `ConfigurableWebApplicationContext` object. In your handler class, instead of initializing the `SpringLambdaContainerHandler` with the Spring Boot application class, initialize another context and set the Spring Boot app as a parent:
67+
68+
```java
69+
SpringApplication springBootApplication = new SpringApplication(SpringBootApplication.class);
70+
springBootApplication.setWebEnvironment(false);
71+
springBootApplication.setBannerMode(Banner.Mode.OFF);
72+
73+
// create a new empty context and set the spring boot application as a parent of it
74+
ConfigurableWebApplicationContext wrappingContext = new AnnotationConfigWebApplicationContext();
75+
wrappingContext.setParent(springBootApplication.run());
76+
77+
// now we can initialize the framework with the wrapping context
78+
SpringLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler =
79+
SpringLambdaContainerHandler.getAwsProxyHandler(wrappingContext);
80+
```
81+
82+
When using Spring Boot, make sure to configure the shade plugin in your pom file to exclude the embedded container and all unnecessary libraries to reduce the size of your built jar.
83+
6584
### Spark support
6685
The library also supports applications written with the [Spark framework](http://sparkjava.com/). When using the library with Spark, it's important to initialize the `SparkLambdaContainerHandler` before defining routes.
6786

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@
3333
<dependency>
3434
<groupId>javax.ws.rs</groupId>
3535
<artifactId>javax.ws.rs-api</artifactId>
36-
<version>2.1-m01</version>
36+
<version>2.0.1</version>
3737
</dependency>
3838

3939
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
4040
<dependency>
4141
<groupId>com.fasterxml.jackson.core</groupId>
4242
<artifactId>jackson-databind</artifactId>
43-
<version>2.8.4</version>
43+
<version>${jackson.version}</version>
4444
</dependency>
4545

4646
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import com.amazonaws.serverless.proxy.internal.model.ErrorModel;
1818
import com.fasterxml.jackson.core.JsonProcessingException;
1919
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
2022

2123
import javax.ws.rs.core.HttpHeaders;
2224
import javax.ws.rs.core.MediaType;
@@ -37,6 +39,8 @@
3739
public class AwsProxyExceptionHandler
3840
implements ExceptionHandler<AwsProxyResponse> {
3941

42+
private Logger log = LoggerFactory.getLogger(AwsProxyExceptionHandler.class);
43+
4044
//-------------------------------------------------------------
4145
// Constants
4246
//-------------------------------------------------------------
@@ -93,7 +97,7 @@ String getErrorJson(String message) {
9397
try {
9498
return objectMapper.writeValueAsString(new ErrorModel(message));
9599
} catch (JsonProcessingException e) {
96-
e.printStackTrace();
100+
log.error("Could not produce error JSON", e);
97101
return "{ \"message\": \"" + message + "\" }";
98102
}
99103
}

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

+10-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import com.amazonaws.serverless.proxy.internal.model.ContainerConfig;
1717
import com.amazonaws.services.lambda.runtime.Context;
1818

19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
1922
import javax.ws.rs.core.SecurityContext;
2023

2124
import java.util.concurrent.CountDownLatch;
@@ -49,6 +52,8 @@ public abstract class LambdaContainerHandler<RequestType, ResponseType, Containe
4952

5053
protected Context lambdaContext;
5154

55+
private Logger log = LoggerFactory.getLogger(LambdaContainerHandler.class);
56+
5257

5358
//-------------------------------------------------------------
5459
// Variables - Private - Static
@@ -65,6 +70,7 @@ protected LambdaContainerHandler(RequestReader<RequestType, ContainerRequestType
6570
ResponseWriter<ContainerResponseType, ResponseType> responseWriter,
6671
SecurityContextWriter<RequestType> securityContextWriter,
6772
ExceptionHandler<ResponseType> exceptionHandler) {
73+
log.info("Starting Lambda Container Handler");
6874
this.requestReader = requestReader;
6975
this.responseWriter = responseWriter;
7076
this.securityContextWriter = securityContextWriter;
@@ -76,7 +82,7 @@ protected LambdaContainerHandler(RequestReader<RequestType, ContainerRequestType
7682
// Methods - Abstract
7783
//-------------------------------------------------------------
7884

79-
protected abstract ContainerResponseType getContainerResponse(CountDownLatch latch);
85+
protected abstract ContainerResponseType getContainerResponse(ContainerRequestType request, CountDownLatch latch);
8086

8187

8288
protected abstract void handleRequest(ContainerRequestType containerRequest, ContainerResponseType containerResponse, Context lambdaContext)
@@ -95,6 +101,7 @@ protected abstract void handleRequest(ContainerRequestType containerRequest, Con
95101
* @param basePath The base path to be stripped from the request
96102
*/
97103
public void stripBasePath(String basePath) {
104+
log.debug("Setting framework to strip base path: " + basePath);
98105
config.setStripBasePath(true);
99106
config.setServiceBasePath(basePath);
100107
}
@@ -113,17 +120,16 @@ public ResponseType proxy(RequestType request, Context context) {
113120
try {
114121
SecurityContext securityContext = securityContextWriter.writeSecurityContext(request, context);
115122
CountDownLatch latch = new CountDownLatch(1);
116-
ContainerResponseType containerResponse = getContainerResponse(latch);
117123
ContainerRequestType containerRequest = requestReader.readRequest(request, securityContext, context, config);
124+
ContainerResponseType containerResponse = getContainerResponse(containerRequest, latch);
118125

119126
handleRequest(containerRequest, containerResponse, context);
120127

121128
latch.await();
122129

123130
return responseWriter.writeResponse(containerResponse, context);
124131
} catch (Exception e) {
125-
context.getLogger().log("Error while handling request: " + e.getMessage());
126-
e.printStackTrace();
132+
log.error("Error while handling request", e);
127133

128134
return exceptionHandler.handle(e);
129135
}

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
import com.amazonaws.services.lambda.runtime.Context;
1616

17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
1720
import javax.servlet.AsyncContext;
1821
import javax.servlet.DispatcherType;
1922
import javax.servlet.ServletContext;
@@ -66,6 +69,8 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
6669

6770
protected DispatcherType dispatcherType;
6871

72+
private Logger log = LoggerFactory.getLogger(AwsHttpServletRequest.class);
73+
6974

7075
//-------------------------------------------------------------
7176
// Constructors
@@ -280,8 +285,7 @@ protected String generateQueryString(Map<String, String> parameters) {
280285
newValue = URLEncoder.encode(newValue, StandardCharsets.UTF_8.name());
281286
}
282287
} catch (UnsupportedEncodingException e) {
283-
lambdaContext.getLogger().log("Could not URLEncode: " + newKey + "\n" + e.getLocalizedMessage());
284-
e.printStackTrace();
288+
log.error("Could not URLEncode: " + newKey, e);
285289

286290
}
287291
return newKey + "=" + newValue;

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

+33-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
*/
1313
package com.amazonaws.serverless.proxy.internal.servlet;
1414

15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
1518
import javax.servlet.ServletOutputStream;
1619
import javax.servlet.WriteListener;
1720
import javax.servlet.http.Cookie;
@@ -21,7 +24,6 @@
2124
import java.io.ByteArrayOutputStream;
2225
import java.io.IOException;
2326
import java.io.PrintWriter;
24-
import java.net.URLEncoder;
2527
import java.text.SimpleDateFormat;
2628
import java.util.*;
2729
import java.util.concurrent.CountDownLatch;
@@ -38,8 +40,8 @@ public class AwsHttpServletResponse
3840
// Constants
3941
//-------------------------------------------------------------
4042

41-
private static final String HEADER_DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss z";
42-
43+
static final String HEADER_DATE_PATTERN = "EEE, d MMM yyyy HH:mm:ss z";
44+
static final String COOKIE_DEFAULT_TIME_ZONE = "GMT";
4345

4446
//-------------------------------------------------------------
4547
// Variables - Private
@@ -51,8 +53,11 @@ public class AwsHttpServletResponse
5153
private String responseBody;
5254
private ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream();
5355
private CountDownLatch writersCountDownLatch;
56+
private AwsHttpServletRequest request;
5457
private boolean isCommitted = false;
5558

59+
private Logger log = LoggerFactory.getLogger(AwsHttpServletResponse.class);
60+
5661

5762
//-------------------------------------------------------------
5863
// Constructors
@@ -63,8 +68,9 @@ public class AwsHttpServletResponse
6368
* function while the response is asynchronously written by the underlying container/application
6469
* @param latch A latch used to inform the <code>ContainerHandler</code> that we are done receiving the response data
6570
*/
66-
public AwsHttpServletResponse(CountDownLatch latch) {
71+
public AwsHttpServletResponse(AwsHttpServletRequest req, CountDownLatch latch) {
6772
writersCountDownLatch = latch;
73+
request = req;
6874
}
6975

7076

@@ -79,6 +85,25 @@ public void addCookie(Cookie cookie) {
7985
if (cookie.getPath() != null) {
8086
cookieData += "; Path=" + cookie.getPath();
8187
}
88+
if (cookie.getSecure()) {
89+
cookieData += "; Secure";
90+
}
91+
if (cookie.isHttpOnly()) {
92+
cookieData += "; HttpOnly";
93+
}
94+
if (cookie.getDomain() != null && !"".equals(cookie.getDomain().trim())) {
95+
cookieData += "; Domain=" + cookie.getDomain();
96+
}
97+
cookieData += "; Max-Age=" + cookie.getMaxAge();
98+
99+
// we always set the timezone to GMT
100+
TimeZone gmtTimeZone = TimeZone.getTimeZone(COOKIE_DEFAULT_TIME_ZONE);
101+
Calendar currentTimestamp = Calendar.getInstance(gmtTimeZone);
102+
currentTimestamp.add(Calendar.SECOND, cookie.getMaxAge());
103+
SimpleDateFormat cookieDateFormatter = new SimpleDateFormat(HEADER_DATE_PATTERN);
104+
cookieDateFormatter.setTimeZone(gmtTimeZone);
105+
cookieData += "; Expires=" + cookieDateFormatter.format(currentTimestamp.getTime());
106+
82107
setHeader(HttpHeaders.SET_COOKIE, cookieData, false);
83108
}
84109

@@ -141,7 +166,7 @@ public void sendRedirect(String s) throws IOException {
141166

142167
@Override
143168
public void setDateHeader(String s, long l) {
144-
SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_FORMAT);
169+
SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_PATTERN);
145170
Date responseDate = new Date();
146171
responseDate.setTime(l);
147172
setHeader(s, sdf.format(responseDate), true);
@@ -150,7 +175,7 @@ public void setDateHeader(String s, long l) {
150175

151176
@Override
152177
public void addDateHeader(String s, long l) {
153-
SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_FORMAT);
178+
SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_PATTERN);
154179
Date responseDate = new Date();
155180
responseDate.setTime(l);
156181
setHeader(s, sdf.format(responseDate), false);
@@ -255,7 +280,7 @@ public void setWriteListener(WriteListener writeListener) {
255280
try {
256281
writeListener.onWritePossible();
257282
} catch (IOException e) {
258-
e.printStackTrace();
283+
log.error("Output stream is not writable", e);
259284
}
260285

261286
listener = writeListener;
@@ -268,6 +293,7 @@ public void write(int b) throws IOException {
268293
try {
269294
bodyOutputStream.write(b);
270295
} catch (Exception e) {
296+
log.error("Cannot write to output stream", e);
271297
listener.onError(e);
272298
}
273299
}

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import com.amazonaws.serverless.proxy.internal.SecurityContextWriter;
2020
import com.amazonaws.services.lambda.runtime.Context;
2121

22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
24+
2225
import javax.servlet.ServletContext;
2326
import javax.servlet.ServletException;
2427
import javax.servlet.http.HttpServletRequest;
@@ -46,6 +49,7 @@ public abstract class AwsLambdaServletContainerHandler<RequestType, ResponseType
4649
//-------------------------------------------------------------
4750

4851
protected ServletContext servletContext;
52+
private Logger log = LoggerFactory.getLogger(AwsLambdaServletContainerHandler.class);
4953

5054
//-------------------------------------------------------------
5155
// Variables - Protected
@@ -81,7 +85,7 @@ public void forward(ContainerRequestType servletRequest, ContainerResponseType s
8185
try {
8286
handleRequest(servletRequest, servletResponse, lambdaContext);
8387
} catch (Exception e) {
84-
e.printStackTrace();
88+
log.error("Could not forward request", e);
8589
throw new ServletException(e);
8690
}
8791
}
@@ -99,7 +103,7 @@ public void include(ContainerRequestType servletRequest, ContainerResponseType s
99103
try {
100104
handleRequest(servletRequest, servletResponse, lambdaContext);
101105
} catch (Exception e) {
102-
e.printStackTrace();
106+
log.error("Could not include request", e);
103107
throw new ServletException(e);
104108
}
105109
}
@@ -133,7 +137,7 @@ protected void handleRequest(ContainerRequestType containerRequest, ContainerRes
133137
// context so we only set it once.
134138
// TODO: In the future, if we decide to support multiple servlets/contexts in an instance we only need to modify this method
135139
if (getServletContext() == null) {
136-
setServletContext(new AwsServletContext(lambdaContext, this));
140+
setServletContext(new AwsServletContext(this));
137141
}
138142
}
139143

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.apache.commons.fileupload.FileUploadException;
2121
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
2222
import org.apache.commons.fileupload.servlet.ServletFileUpload;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
2325

2426
import javax.servlet.AsyncContext;
2527
import javax.servlet.ReadListener;
@@ -75,6 +77,7 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest {
7577
private SecurityContext securityContext;
7678
private Map<String, List<String>> urlEncodedFormParameters;
7779
private Map<String, Part> multipartFormParameters;
80+
private Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class);
7881

7982

8083
//-------------------------------------------------------------
@@ -125,7 +128,7 @@ public long getDateHeader(String s) {
125128
try {
126129
return dateFormatter.parse(dateString).getTime();
127130
} catch (ParseException e) {
128-
e.printStackTrace();
131+
log.error("Could not parse date header", e);
129132
return new Date().getTime();
130133
}
131134
}
@@ -405,7 +408,7 @@ public void setReadListener(ReadListener readListener) {
405408
try {
406409
listener.onDataAvailable();
407410
} catch (IOException e) {
408-
e.printStackTrace();
411+
log.error("Data not available on input stream", e);
409412
}
410413
}
411414

@@ -682,8 +685,7 @@ private Map<String, Part> getMultipartFormParametersMap() {
682685
output.put(item.getFieldName(), newPart);
683686
}
684687
} catch (FileUploadException e) {
685-
// TODO: Should we swallaw this?
686-
e.printStackTrace();
688+
log.error("Could not read multipart upload file", e);
687689
}
688690
return output;
689691
}
@@ -701,7 +703,7 @@ private Map<String, List<String>> getFormUrlEncodedParametersMap() {
701703
try {
702704
rawBodyContent = URLDecoder.decode(request.getBody(), DEFAULT_CHARACTER_ENCODING);
703705
} catch (UnsupportedEncodingException e) {
704-
e.printStackTrace();
706+
log.warn("Could not decode body content - proceeding as if it was already decoded", e);
705707
rawBodyContent = request.getBody();
706708
}
707709

0 commit comments

Comments
 (0)