Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,25 @@ public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyRe
#### Spring Profiles
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.

#### Spring Boot
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:

```java
SpringApplication springBootApplication = new SpringApplication(SpringBootApplication.class);
springBootApplication.setWebEnvironment(false);
springBootApplication.setBannerMode(Banner.Mode.OFF);

// create a new empty context and set the spring boot application as a parent of it
ConfigurableWebApplicationContext wrappingContext = new AnnotationConfigWebApplicationContext();
wrappingContext.setParent(springBootApplication.run());

// now we can initialize the framework with the wrapping context
SpringLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler =
SpringLambdaContainerHandler.getAwsProxyHandler(wrappingContext);
```

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.

### Spark support
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.

Expand Down
4 changes: 2 additions & 2 deletions aws-serverless-java-container-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1-m01</version>
<version>2.0.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.4</version>
<version>${jackson.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import com.amazonaws.serverless.proxy.internal.model.ErrorModel;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
Expand All @@ -37,6 +39,8 @@
public class AwsProxyExceptionHandler
implements ExceptionHandler<AwsProxyResponse> {

private Logger log = LoggerFactory.getLogger(AwsProxyExceptionHandler.class);

//-------------------------------------------------------------
// Constants
//-------------------------------------------------------------
Expand Down Expand Up @@ -93,7 +97,7 @@ String getErrorJson(String message) {
try {
return objectMapper.writeValueAsString(new ErrorModel(message));
} catch (JsonProcessingException e) {
e.printStackTrace();
log.error("Could not produce error JSON", e);
return "{ \"message\": \"" + message + "\" }";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import com.amazonaws.serverless.proxy.internal.model.ContainerConfig;
import com.amazonaws.services.lambda.runtime.Context;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.SecurityContext;

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

protected Context lambdaContext;

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


//-------------------------------------------------------------
// Variables - Private - Static
Expand All @@ -65,6 +70,7 @@ protected LambdaContainerHandler(RequestReader<RequestType, ContainerRequestType
ResponseWriter<ContainerResponseType, ResponseType> responseWriter,
SecurityContextWriter<RequestType> securityContextWriter,
ExceptionHandler<ResponseType> exceptionHandler) {
log.info("Starting Lambda Container Handler");
this.requestReader = requestReader;
this.responseWriter = responseWriter;
this.securityContextWriter = securityContextWriter;
Expand All @@ -76,7 +82,7 @@ protected LambdaContainerHandler(RequestReader<RequestType, ContainerRequestType
// Methods - Abstract
//-------------------------------------------------------------

protected abstract ContainerResponseType getContainerResponse(CountDownLatch latch);
protected abstract ContainerResponseType getContainerResponse(ContainerRequestType request, CountDownLatch latch);


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

handleRequest(containerRequest, containerResponse, context);

latch.await();

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

return exceptionHandler.handle(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
Expand Down Expand Up @@ -66,6 +69,8 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {

protected DispatcherType dispatcherType;

private Logger log = LoggerFactory.getLogger(AwsHttpServletRequest.class);


//-------------------------------------------------------------
// Constructors
Expand Down Expand Up @@ -280,8 +285,7 @@ protected String generateQueryString(Map<String, String> parameters) {
newValue = URLEncoder.encode(newValue, StandardCharsets.UTF_8.name());
}
} catch (UnsupportedEncodingException e) {
lambdaContext.getLogger().log("Could not URLEncode: " + newKey + "\n" + e.getLocalizedMessage());
e.printStackTrace();
log.error("Could not URLEncode: " + newKey, e);

}
return newKey + "=" + newValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
*/
package com.amazonaws.serverless.proxy.internal.servlet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.Cookie;
Expand All @@ -21,7 +24,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CountDownLatch;
Expand All @@ -38,8 +40,8 @@ public class AwsHttpServletResponse
// Constants
//-------------------------------------------------------------

private static final String HEADER_DATE_FORMAT = "EEE, d MMM yyyy HH:mm:ss z";

static final String HEADER_DATE_PATTERN = "EEE, d MMM yyyy HH:mm:ss z";
static final String COOKIE_DEFAULT_TIME_ZONE = "GMT";

//-------------------------------------------------------------
// Variables - Private
Expand All @@ -51,8 +53,11 @@ public class AwsHttpServletResponse
private String responseBody;
private ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream();
private CountDownLatch writersCountDownLatch;
private AwsHttpServletRequest request;
private boolean isCommitted = false;

private Logger log = LoggerFactory.getLogger(AwsHttpServletResponse.class);


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


Expand All @@ -79,6 +85,25 @@ public void addCookie(Cookie cookie) {
if (cookie.getPath() != null) {
cookieData += "; Path=" + cookie.getPath();
}
if (cookie.getSecure()) {
cookieData += "; Secure";
}
if (cookie.isHttpOnly()) {
cookieData += "; HttpOnly";
}
if (cookie.getDomain() != null && !"".equals(cookie.getDomain().trim())) {
cookieData += "; Domain=" + cookie.getDomain();
}
cookieData += "; Max-Age=" + cookie.getMaxAge();

// we always set the timezone to GMT
TimeZone gmtTimeZone = TimeZone.getTimeZone(COOKIE_DEFAULT_TIME_ZONE);
Calendar currentTimestamp = Calendar.getInstance(gmtTimeZone);
currentTimestamp.add(Calendar.SECOND, cookie.getMaxAge());
SimpleDateFormat cookieDateFormatter = new SimpleDateFormat(HEADER_DATE_PATTERN);
cookieDateFormatter.setTimeZone(gmtTimeZone);
cookieData += "; Expires=" + cookieDateFormatter.format(currentTimestamp.getTime());

setHeader(HttpHeaders.SET_COOKIE, cookieData, false);
}

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

@Override
public void setDateHeader(String s, long l) {
SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_FORMAT);
SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_PATTERN);
Date responseDate = new Date();
responseDate.setTime(l);
setHeader(s, sdf.format(responseDate), true);
Expand All @@ -150,7 +175,7 @@ public void setDateHeader(String s, long l) {

@Override
public void addDateHeader(String s, long l) {
SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_FORMAT);
SimpleDateFormat sdf = new SimpleDateFormat(HEADER_DATE_PATTERN);
Date responseDate = new Date();
responseDate.setTime(l);
setHeader(s, sdf.format(responseDate), false);
Expand Down Expand Up @@ -255,7 +280,7 @@ public void setWriteListener(WriteListener writeListener) {
try {
writeListener.onWritePossible();
} catch (IOException e) {
e.printStackTrace();
log.error("Output stream is not writable", e);
}

listener = writeListener;
Expand All @@ -268,6 +293,7 @@ public void write(int b) throws IOException {
try {
bodyOutputStream.write(b);
} catch (Exception e) {
log.error("Cannot write to output stream", e);
listener.onError(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import com.amazonaws.serverless.proxy.internal.SecurityContextWriter;
import com.amazonaws.services.lambda.runtime.Context;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -46,6 +49,7 @@ public abstract class AwsLambdaServletContainerHandler<RequestType, ResponseType
//-------------------------------------------------------------

protected ServletContext servletContext;
private Logger log = LoggerFactory.getLogger(AwsLambdaServletContainerHandler.class);

//-------------------------------------------------------------
// Variables - Protected
Expand Down Expand Up @@ -81,7 +85,7 @@ public void forward(ContainerRequestType servletRequest, ContainerResponseType s
try {
handleRequest(servletRequest, servletResponse, lambdaContext);
} catch (Exception e) {
e.printStackTrace();
log.error("Could not forward request", e);
throw new ServletException(e);
}
}
Expand All @@ -99,7 +103,7 @@ public void include(ContainerRequestType servletRequest, ContainerResponseType s
try {
handleRequest(servletRequest, servletResponse, lambdaContext);
} catch (Exception e) {
e.printStackTrace();
log.error("Could not include request", e);
throw new ServletException(e);
}
}
Expand Down Expand Up @@ -133,7 +137,7 @@ protected void handleRequest(ContainerRequestType containerRequest, ContainerRes
// context so we only set it once.
// TODO: In the future, if we decide to support multiple servlets/contexts in an instance we only need to modify this method
if (getServletContext() == null) {
setServletContext(new AwsServletContext(lambdaContext, this));
setServletContext(new AwsServletContext(this));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.AsyncContext;
import javax.servlet.ReadListener;
Expand Down Expand Up @@ -75,6 +77,7 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest {
private SecurityContext securityContext;
private Map<String, List<String>> urlEncodedFormParameters;
private Map<String, Part> multipartFormParameters;
private Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class);


//-------------------------------------------------------------
Expand Down Expand Up @@ -125,7 +128,7 @@ public long getDateHeader(String s) {
try {
return dateFormatter.parse(dateString).getTime();
} catch (ParseException e) {
e.printStackTrace();
log.error("Could not parse date header", e);
return new Date().getTime();
}
}
Expand Down Expand Up @@ -405,7 +408,7 @@ public void setReadListener(ReadListener readListener) {
try {
listener.onDataAvailable();
} catch (IOException e) {
e.printStackTrace();
log.error("Data not available on input stream", e);
}
}

Expand Down Expand Up @@ -682,8 +685,7 @@ private Map<String, Part> getMultipartFormParametersMap() {
output.put(item.getFieldName(), newPart);
}
} catch (FileUploadException e) {
// TODO: Should we swallaw this?
e.printStackTrace();
log.error("Could not read multipart upload file", e);
}
return output;
}
Expand All @@ -701,7 +703,7 @@ private Map<String, List<String>> getFormUrlEncodedParametersMap() {
try {
rawBodyContent = URLDecoder.decode(request.getBody(), DEFAULT_CHARACTER_ENCODING);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
log.warn("Could not decode body content - proceeding as if it was already decoded", e);
rawBodyContent = request.getBody();
}

Expand Down
Loading