diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml index 13b2c9b43..9794788d2 100644 --- a/aws-serverless-java-container-core/pom.xml +++ b/aws-serverless-java-container-core/pom.xml @@ -65,4 +65,78 @@ + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.5 + + + Max + + Low + + true + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.5 + + + Max + + Low + + true + + ${project.build.directory}/findbugs + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + analyze-compile + compile + + check + + + + + + + diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriter.java index 9a38bbf2a..1929d3a4d 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxySecurityContextWriter.java @@ -28,7 +28,7 @@ public class AwsProxySecurityContextWriter implements SecurityContextWriter { */ public static final String LAMBDA_CONTEXT_PROPERTY = "com.amazonaws.lambda.context"; + /** + * The key for the JAX RS security context properties stored in the request attributes + */ + public static final String JAX_SECURITY_CONTEXT_PROPERTY = "com.amazonaws.serverless.jaxrs.securityContext"; + //------------------------------------------------------------- // Methods - Abstract diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/ResponseWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/ResponseWriter.java index b759a0209..68d6e7b17 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/ResponseWriter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/ResponseWriter.java @@ -16,10 +16,7 @@ import com.amazonaws.serverless.exceptions.InvalidResponseObjectException; import com.amazonaws.services.lambda.runtime.Context; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.IOException; -import java.io.OutputStream; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** @@ -52,6 +49,7 @@ public abstract ResponseType writeResponse(ContainerResponseType containerRespon * @param input The byte[] to check against * @return true if the contend is valid UTF-8, false otherwise */ + @SuppressFBWarnings("NS_NON_SHORT_CIRCUIT") protected boolean isValidUtf8(final byte[] input) { int i = 0; // Check for BOM diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java index 260037422..d9729a222 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java @@ -65,7 +65,7 @@ public abstract class LambdaContainerHandler 0xfff) { + buffer.append("\\u" + Integer.toHexString(ch).toUpperCase(Locale.ENGLISH)); + } else if (ch > 0xff) { + buffer.append("\\u0" + Integer.toHexString(ch).toUpperCase(Locale.ENGLISH)); + } else if (ch > 0x7f) { + buffer.append("\\u00" + Integer.toHexString(ch).toUpperCase(Locale.ENGLISH)); + } else if (ch < 32) { + switch (ch) { + case '\b': + buffer.append('\\'); + buffer.append('b'); + break; + case '\n': + buffer.append('\\'); + buffer.append('n'); + break; + case '\t': + buffer.append('\\'); + buffer.append('t'); + break; + case '\f': + buffer.append('\\'); + buffer.append('f'); + break; + case '\r': + buffer.append('\\'); + buffer.append('r'); + break; + default: + if (ch > 0xf) { + buffer.append("\\u00" + Integer.toHexString(ch).toUpperCase(Locale.ENGLISH)); + } else { + buffer.append("\\u000" + Integer.toHexString(ch).toUpperCase(Locale.ENGLISH)); + } + break; + } + } else { + switch (ch) { + case '\'': + + buffer.append('\''); + break; + case '"': + buffer.append('\\'); + buffer.append('"'); + break; + case '\\': + buffer.append('\\'); + buffer.append('\\'); + break; + case '/': + buffer.append('/'); + break; + default: + buffer.append(ch); + break; + } + } + } + + return buffer.toString(); + } + + public static String getValidFilePath(String inputPath) { + return getValidFilePath(inputPath, false); + } + + /** + * Returns an absolute file path given an input path and validates that it is not trying + * to write/read from a directory other than /tmp. + * @param inputPath The input path + * @return The absolute path to the file + * @throws IllegalArgumentException If the given path is not valid or outside of /tmp + */ + @SuppressFBWarnings("PATH_TRAVERSAL_IN") + public static String getValidFilePath(String inputPath, boolean isWrite) { + if (inputPath == null || "".equals(inputPath.trim())) { + return null; + } + + File f = new File(inputPath); + try { + String canonicalPath = f.getCanonicalPath(); + + if (isWrite && canonicalPath.startsWith("/var/task")) { + throw new IllegalArgumentException("Trying to write to /var/task folder"); + } + + boolean isAllowed = false; + for (String allowedPath : LambdaContainerHandler.getContainerConfig().getValidFilePaths()) { + if (canonicalPath.startsWith(allowedPath)) { + isAllowed = true; + break; + } + } + if (!isAllowed) { + throw new IllegalArgumentException("File path not allowed: " + encode(canonicalPath)); + } + + return canonicalPath; + } catch (IOException e) { + log.error("Invalid file path: {}", encode(inputPath)); + throw new IllegalArgumentException("Invalid file path", e); + } + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java index 0e561a22a..5d91e142c 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContext.java @@ -43,10 +43,19 @@ public class AwsProxySecurityContext // Variables - Private //------------------------------------------------------------- - protected Context lambdaContext; - protected AwsProxyRequest event; + private Context lambdaContext; + private AwsProxyRequest event; + public Context getLambdaContext() { + return lambdaContext; + } + + + public AwsProxyRequest getEvent() { + return event; + } + //------------------------------------------------------------- // Constructors //------------------------------------------------------------- @@ -119,7 +128,7 @@ public String getAuthenticationScheme() { * Custom object for request authorized with a Cognito User Pool authorizer. By casting the Principal * object to this you can extract the Claims object included in the token. */ - public class CognitoUserPoolPrincipal implements Principal { + public static class CognitoUserPoolPrincipal implements Principal { private CognitoAuthorizerClaims claims; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java index 59143d918..2530d67b2 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java @@ -13,6 +13,7 @@ package com.amazonaws.serverless.proxy.internal.servlet; import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.internal.SecurityUtils; import com.amazonaws.serverless.proxy.model.ApiGatewayRequestContext; import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.services.lambda.runtime.Context; @@ -31,6 +32,7 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.security.Security; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collections; @@ -335,7 +337,7 @@ protected String decodeRequestPath(String requestPath, ContainerConfig config) { try { return URLDecoder.decode(requestPath, config.getUriEncoding()); } catch (UnsupportedEncodingException ex) { - log.error("Could not URL decode the request path, configured encoding not supported: " + config.getUriEncoding(), ex); + log.error("Could not URL decode the request path, configured encoding not supported: {}", SecurityUtils.encode(config.getUriEncoding())); // we do not fail at this. return requestPath; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java index 52b6b80a3..45e272ca9 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java @@ -13,7 +13,9 @@ package com.amazonaws.serverless.proxy.internal.servlet; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.SecurityUtils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +27,9 @@ import javax.ws.rs.core.MultivaluedHashMap; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.CountDownLatch; @@ -82,6 +86,7 @@ public AwsHttpServletResponse(AwsHttpServletRequest req, CountDownLatch latch) { //------------------------------------------------------------- + @SuppressFBWarnings("COOKIE_USAGE") @Override public void addCookie(Cookie cookie) { String cookieData = cookie.getName() + "=" + cookie.getValue(); @@ -300,7 +305,9 @@ public void write(int b) throws IOException { bodyOutputStream.write(b); } catch (Exception e) { log.error("Cannot write to output stream", e); - listener.onError(e); + if (listener != null) { + listener.onError(e); + } } } @@ -318,7 +325,7 @@ public void close() @Override public PrintWriter getWriter() throws IOException { if (null == writer) { - writer = new PrintWriter(bodyOutputStream); + writer = new PrintWriter(new OutputStreamWriter(bodyOutputStream, StandardCharsets.UTF_8)); } return writer; } @@ -365,7 +372,7 @@ public void flushBuffer() throws IOException { if (null != writer) { writer.flush(); } - responseBody = new String(bodyOutputStream.toByteArray()); + responseBody = new String(bodyOutputStream.toByteArray(), StandardCharsets.UTF_8); log.debug("Response buffer flushed with {} bytes, latch={}", responseBody.length(), writersCountDownLatch.getCount()); isCommitted = true; writersCountDownLatch.countDown(); @@ -450,14 +457,16 @@ Map getAwsResponseHeaders() { //------------------------------------------------------------- private void setHeader(String key, String value, boolean overwrite) { - List values = headers.get(key); + String encodedKey = SecurityUtils.crlf(key); + String encodedValue = SecurityUtils.crlf(value); + List values = headers.get(encodedKey); if (values == null || overwrite) { values = new ArrayList<>(); } - values.add(value); + values.add(encodedValue); - headers.put(key, values); + headers.put(encodedKey, values); } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java index 76f56416a..2b7b42572 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java @@ -38,7 +38,6 @@ public AwsHttpSession(String id) { if (null == id) { throw new RuntimeException("HTTP session id (from request ID) cannot be null"); } - log.debug("Creating session " + id); this.id = id; attributes = new HashMap<>(); creationTime = Instant.now().getEpochSecond(); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java index c8347cafd..c39094528 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsLambdaServletContainerHandler.java @@ -188,7 +188,6 @@ protected FilterChain getFilterChain(ContainerRequestType req, Servlet servlet) */ protected void doFilter(ContainerRequestType request, ContainerResponseType response, Servlet servlet) throws IOException, ServletException { FilterChain chain = getFilterChain(request, servlet); - log.debug("FilterChainHolder.doFilter {}", chain); chain.doFilter(request, response); } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index d48a18b45..a964a1b43 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -14,10 +14,13 @@ import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.SecurityUtils; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.services.lambda.runtime.Context; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; @@ -43,9 +46,11 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.security.Principal; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -79,18 +84,19 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest { private SecurityContext securityContext; private Map> urlEncodedFormParameters; private Map multipartFormParameters; - private Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class); + private static Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class); private ContainerConfig config; - //------------------------------------------------------------- // Constructors //------------------------------------------------------------- + public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambdaContext, SecurityContext awsSecurityContext) { this(awsProxyRequest, lambdaContext, awsSecurityContext, ContainerConfig.defaultConfig()); } + public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambdaContext, SecurityContext awsSecurityContext, ContainerConfig config) { super(lambdaContext); this.request = awsProxyRequest; @@ -101,15 +107,16 @@ public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambd this.multipartFormParameters = getMultipartFormParametersMap(); } + public AwsProxyRequest getAwsProxyRequest() { return this.request; } - //------------------------------------------------------------- // Implementation - HttpServletRequest //------------------------------------------------------------- + @Override public String getAuthType() { return securityContext.getAuthenticationScheme(); @@ -239,6 +246,7 @@ public Principal getUserPrincipal() { return securityContext.getUserPrincipal(); } + @Override public String getRequestURI() { return cleanUri(getContextPath()) + cleanUri(request.getPath()); @@ -262,6 +270,7 @@ public String getServletPath() { return ""; } + @Override public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { @@ -275,6 +284,7 @@ public void login(String s, String s1) throw new UnsupportedOperationException(); } + @Override public void logout() throws ServletException { @@ -302,11 +312,11 @@ public T upgrade(Class aClass) return null; } - //------------------------------------------------------------- // Implementation - ServletRequest //------------------------------------------------------------- + @Override public String getCharacterEncoding() { // we only look at content-type because content-encoding should only be used for @@ -335,7 +345,8 @@ public String getCharacterEncoding() { @Override - public void setCharacterEncoding(String s) throws UnsupportedEncodingException { + public void setCharacterEncoding(String s) + throws UnsupportedEncodingException { String currentContentType = request.getHeaders().get(HttpHeaders.CONTENT_TYPE); if (currentContentType == null) { request.getHeaders().put( @@ -364,6 +375,7 @@ public void setCharacterEncoding(String s) throws UnsupportedEncodingException { } } + @Override public int getContentLength() { String headerValue = getHeaderCaseInsensitive(HttpHeaders.CONTENT_LENGTH); @@ -391,51 +403,19 @@ public String getContentType() { @Override - public ServletInputStream getInputStream() throws IOException { + public ServletInputStream getInputStream() + throws IOException { if (request.getBody() == null) { return null; } - byte[] bodyBytes = request.getBody().getBytes(); + byte[] bodyBytes = null; if (request.isBase64Encoded()) { bodyBytes = Base64.getMimeDecoder().decode(request.getBody()); + } else { + bodyBytes = request.getBody().getBytes(StandardCharsets.UTF_8); } ByteArrayInputStream requestBodyStream = new ByteArrayInputStream(bodyBytes); - return new ServletInputStream() { - - private ReadListener listener; - - @Override - public boolean isFinished() { - return true; - } - - - @Override - public boolean isReady() { - return true; - } - - - @Override - public void setReadListener(ReadListener readListener) { - listener = readListener; - try { - listener.onDataAvailable(); - } catch (IOException e) { - log.error("Data not available on input stream", e); - } - } - - - @Override - public int read() throws IOException { - int readByte = requestBodyStream.read(); - if (requestBodyStream.available() == 0 && listener != null) { - listener.onAllDataRead(); - } - return readByte; - } - }; + return new AwsServletInputStream(requestBodyStream); } @@ -480,7 +460,7 @@ public String[] getParameterValues(String s) { } if (values.size() == 0) { - return null; + return new String[0]; } else { String[] valuesArray = new String[values.size()]; valuesArray = values.toArray(valuesArray); @@ -535,6 +515,7 @@ public String getScheme() { return headerValue; } + @Override public String getServerName() { String name = getHeaderCaseInsensitive(HttpHeaders.HOST); @@ -545,6 +526,7 @@ public String getServerName() { return name; } + @Override public BufferedReader getReader() throws IOException { @@ -563,6 +545,7 @@ public String getRemoteHost() { return getHeaderCaseInsensitive(HttpHeaders.HOST); } + @Override public Locale getLocale() { List> values = this.parseHeaderValue( @@ -604,6 +587,7 @@ public RequestDispatcher getRequestDispatcher(String s) { return getServletContext().getRequestDispatcher(s); } + @Override @Deprecated public String getRealPath(String s) { @@ -611,19 +595,23 @@ public String getRealPath(String s) { return null; } + @Override public int getRemotePort() { return 0; } + @Override - public AsyncContext startAsync() throws IllegalStateException { + public AsyncContext startAsync() + throws IllegalStateException { return null; } @Override - public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) + throws IllegalStateException { return null; } @@ -631,12 +619,13 @@ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse se // Methods - Private //------------------------------------------------------------- + private String getHeaderCaseInsensitive(String key) { if (request.getHeaders() == null) { return null; } for (String requestHeaderKey : request.getHeaders().keySet()) { - if (key.toLowerCase().equals(requestHeaderKey.toLowerCase())) { + if (key.toLowerCase(Locale.ENGLISH).equals(requestHeaderKey.toLowerCase(Locale.ENGLISH))) { return request.getHeaders().get(requestHeaderKey); } } @@ -650,7 +639,7 @@ private String getQueryStringParameterCaseInsensitive(String key) { } for (String requestParamKey : request.getQueryStringParameters().keySet()) { - if (key.toLowerCase().equals(requestParamKey.toLowerCase())) { + if (key.toLowerCase(Locale.ENGLISH).equals(requestParamKey.toLowerCase(Locale.ENGLISH))) { return request.getQueryStringParameters().get(requestParamKey); } } @@ -665,24 +654,26 @@ private String[] getFormBodyParameterCaseInsensitive(String key) { valuesArray = values.toArray(valuesArray); return valuesArray; } else { - return null; + return new String[0]; } } + @SuppressFBWarnings("FILE_UPLOAD_FILENAME") private Map getMultipartFormParametersMap() { if (!ServletFileUpload.isMultipartContent(this)) { // isMultipartContent also checks the content type return new HashMap<>(); } - + Timer.start("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); Map output = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); try { List items = upload.parseRequest(this); for (FileItem item : items) { + String fileName = SecurityUtils.getValidFilePath(item.getName(), true); AwsProxyRequestPart newPart = new AwsProxyRequestPart(item.get()); - newPart.setName(item.getName()); + newPart.setName(fileName); newPart.setSubmittedFileName(item.getFieldName()); newPart.setContentType(item.getContentType()); newPart.setSize(item.getSize()); @@ -699,11 +690,14 @@ private Map getMultipartFormParametersMap() { output.put(item.getFieldName(), newPart); } } catch (FileUploadException e) { + Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); log.error("Could not read multipart upload file", e); } + Timer.stop("SERVLET_REQUEST_GET_MULTIPART_PARAMS"); return output; } + private String cleanUri(String uri) { String finalUri = uri; @@ -726,10 +720,10 @@ private Map> getFormUrlEncodedParametersMap() { if (contentType == null) { return new HashMap<>(); } - if (!contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED) || !getMethod().toLowerCase().equals("post")) { + if (!contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED) || !getMethod().toLowerCase(Locale.ENGLISH).equals("post")) { return new HashMap<>(); } - + Timer.start("SERVLET_REQUEST_GET_FORM_PARAMS"); String rawBodyContent = request.getBody(); Map> output = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); @@ -745,16 +739,67 @@ private Map> getFormUrlEncodedParametersMap() { values.add(decodeValueIfEncoded(parameterKeyValue[1])); output.put(decodeValueIfEncoded(parameterKeyValue[0]), values); } - + Timer.stop("SERVLET_REQUEST_GET_FORM_PARAMS"); return output; } - private String decodeValueIfEncoded(String value) { + + public static String decodeValueIfEncoded(String value) { + if (value == null) { + return null; + } + try { - return URLDecoder.decode(value, DEFAULT_CHARACTER_ENCODING); + return URLDecoder.decode(value, LambdaContainerHandler.getContainerConfig().getUriEncoding()); } catch (UnsupportedEncodingException e) { log.warn("Could not decode body content - proceeding as if it was already decoded", e); return value; } } + + + public static class AwsServletInputStream extends ServletInputStream { + + private InputStream bodyStream; + private ReadListener listener; + + public AwsServletInputStream(InputStream body) { + bodyStream = body; + } + + + @Override + public boolean isFinished() { + return true; + } + + + @Override + public boolean isReady() { + return true; + } + + + @Override + public void setReadListener(ReadListener readListener) { + listener = readListener; + try { + listener.onDataAvailable(); + } catch (IOException e) { + log.error("Data not available on input stream", e); + } + } + + + @Override + public int read() + throws IOException { + int readByte = bodyStream.read(); + if (bodyStream.available() == 0 && listener != null) { + listener.onAllDataRead(); + } + return readByte; + } + + } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java index 949ed7e35..e7905bd97 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java @@ -38,6 +38,7 @@ public AwsProxyHttpServletRequest readRequest(AwsProxyRequest request, SecurityC servletRequest.setAttribute(API_GATEWAY_CONTEXT_PROPERTY, request.getRequestContext()); servletRequest.setAttribute(API_GATEWAY_STAGE_VARS_PROPERTY, request.getStageVariables()); servletRequest.setAttribute(LAMBDA_CONTEXT_PROPERTY, lambdaContext); + servletRequest.setAttribute(JAX_SECURITY_CONTEXT_PROPERTY, securityContext); return servletRequest; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java index d03c4018d..cb0dd73c0 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java @@ -15,6 +15,7 @@ import com.amazonaws.serverless.exceptions.InvalidResponseObjectException; import com.amazonaws.serverless.proxy.ResponseWriter; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.services.lambda.runtime.Context; @@ -33,6 +34,7 @@ public class AwsProxyHttpServletResponseWriter extends ResponseWriter getHeaders(String s) { + if (headers == null) { + return Collections.EMPTY_LIST; + } return headers.get(s); } @Override public Collection getHeaderNames() { + if (headers == null) { + return Collections.EMPTY_LIST; + } return headers.keySet(); } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java index 4385cfd39..7cc73aadd 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java @@ -14,8 +14,9 @@ import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; -import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.serverless.proxy.internal.SecurityUtils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -140,18 +141,19 @@ public int getEffectiveMinorVersion() { @Override + @SuppressFBWarnings("PATH_TRAVERSAL_IN") // suppressing because we are using the getValidFilePath public String getMimeType(String s) { try { - + String validatedPath = SecurityUtils.getValidFilePath(s); if (s.startsWith("file:")) { // Support paths such as file:/D:/something/hello.txt - return Files.probeContentType(Paths.get(URI.create(s))); + return Files.probeContentType(Paths.get(URI.create(validatedPath))); } else if (s.startsWith("/")) { // Support paths such as file:/D:/something/hello.txt - return Files.probeContentType(Paths.get(URI.create("file://" + s))); + return Files.probeContentType(Paths.get(URI.create("file://" + validatedPath))); } else { - return Files.probeContentType(Paths.get(s)); + return Files.probeContentType(Paths.get(validatedPath)); } } catch (IOException e) { - log.warn("Could not find content type for file {}", s, e); + log.warn("Could not find content type for file: " + SecurityUtils.encode(s), e); return null; } } @@ -167,13 +169,13 @@ public Set getResourcePaths(String s) { @Override public URL getResource(String s) throws MalformedURLException { - return getClass().getResource(s); + return AwsServletContext.class.getResource(s); } @Override public InputStream getResourceAsStream(String s) { - return getClass().getResourceAsStream(s); + return AwsServletContext.class.getResourceAsStream(s); } @@ -212,20 +214,20 @@ public Enumeration getServletNames() { @Override public void log(String s) { - log.info(s); + log.info(SecurityUtils.encode(s)); } @Override @Deprecated public void log(Exception e, String s) { - log.error(s, e); + log.error(SecurityUtils.encode(s), e); } @Override public void log(String s, Throwable throwable) { - log.error(s, throwable); + log.error(SecurityUtils.encode(s), throwable); } @@ -237,7 +239,7 @@ public String getRealPath(String s) { try { absPath = new File(fileUrl.toURI()).getAbsolutePath(); } catch (URISyntaxException e) { - log.error("Error while looking for real path: " + s, e); + log.error("Error while looking for real path: {}", SecurityUtils.encode(s), SecurityUtils.encode(e.getMessage())); } } return absPath; @@ -373,7 +375,7 @@ public FilterRegistration.Dynamic addFilter(String name, Filter filter) { if (filters.containsKey(name)) { return null; } else { - log.debug("Adding filter '{}' from {}", name, filter); + log.debug("Adding filter '{}' from {}", SecurityUtils.encode(name), SecurityUtils.encode(filter.toString())); } FilterHolder newFilter = new FilterHolder(name, filter, this); @@ -386,7 +388,7 @@ public FilterRegistration.Dynamic addFilter(String name, Filter filter) { @Override public FilterRegistration.Dynamic addFilter(String name, Class filterClass) { try { - log.debug("Adding filter '{}' from {}", name, filterClass.getName()); + log.debug("Adding filter '{}' from {}", SecurityUtils.encode(name), SecurityUtils.encode(filterClass.getName())); Filter newFilter = createFilter(filterClass); return addFilter(name, newFilter); } catch (ServletException e) { @@ -422,9 +424,10 @@ public FilterRegistration getFilterRegistration(String s) { @Override public Map getFilterRegistrations() { Map registrations = new LinkedHashMap<>(); - for (String filter : filters.keySet()) { - registrations.put(filter, filters.get(filter).getRegistration()); + for (Map.Entry entry : filters.entrySet()) { + registrations.put(entry.getKey(), entry.getValue().getRegistration()); } + return registrations; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java index af7ba7a70..3b0188157 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainHolder.java @@ -12,6 +12,7 @@ */ package com.amazonaws.serverless.proxy.internal.servlet; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,6 +66,7 @@ public class FilterChainHolder implements FilterChain { // Implementation - FilterChain //------------------------------------------------------------- + @SuppressFBWarnings("CRLF_INJECTION_LOGS") @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException, ServletException { currentFilter++; @@ -78,11 +80,11 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo if (!holder.isFilterInitialized()) { holder.init(); } - log.debug("Starting {} {} : filter {}-{} {}", servletRequest.getDispatcherType(), ((HttpServletRequest) servletRequest).getRequestURI(), - currentFilter, holder.getFilterName(), holder.getFilter()); + log.debug("Starting {}: filter {}-{}", servletRequest.getDispatcherType(), + currentFilter, holder.getFilterName()); holder.getFilter().doFilter(servletRequest, servletResponse, this); - log.debug("Executed {} {} : filter {}-{} {}", servletRequest.getDispatcherType(), ((HttpServletRequest) servletRequest).getRequestURI(), - currentFilter, holder.getFilterName(), holder.getFilter()); + log.debug("Executed {}: filter {}-{}", servletRequest.getDispatcherType(), + currentFilter, holder.getFilterName()); } // if for some reason the response wasn't flushed yet, we force it here. diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainManager.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainManager.java index 21bdd2393..bd7087fa2 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainManager.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterChainManager.java @@ -12,6 +12,8 @@ */ package com.amazonaws.serverless.proxy.internal.servlet; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -27,6 +29,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -110,8 +113,8 @@ FilterChainHolder getFilterChain(final HttpServletRequest request, Servlet servl } return chainHolder; } - for (String name : registrations.keySet()) { - FilterHolder holder = registrations.get(name); + for (Map.Entry entry : registrations.entrySet()) { + FilterHolder holder = entry.getValue(); // we only check the dispatcher type if it's not empty. Otherwise we assume it's a REQUEST as per section 6.2.5 // of servlet specs if (holder.getRegistration().getDispatcherTypes().size() > 0 && !holder.getRegistration().getDispatcherTypes().contains(type)) { @@ -198,7 +201,7 @@ private void putFilterChainCache(final DispatcherType type, final String targetP */ boolean pathMatches(final String target, final String mapping) { // easiest case, they are exactly the same - if (target.toLowerCase().equals(mapping.toLowerCase())) { + if (target.toLowerCase(Locale.ENGLISH).equals(mapping.toLowerCase(Locale.ENGLISH))) { return true; } @@ -300,7 +303,14 @@ public int hashCode() { @Override public boolean equals(Object key) { - return key.getClass().isAssignableFrom(TargetCacheKey.class) && hashCode() == key.hashCode(); + if (key == null) { + return false; + } + if (key.getClass().equals(this.getClass())) { + return this.hashCode() == key.hashCode(); + } + + return false; } @@ -318,6 +328,7 @@ void setDispatcherType(DispatcherType dispatcherType) { } } + @SuppressFBWarnings("URF_UNREAD_FIELD") private class ServletExecutionFilter implements Filter { private FilterConfig config; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolder.java index a8446b141..609789697 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/FilterHolder.java @@ -12,6 +12,8 @@ */ package com.amazonaws.serverless.proxy.internal.servlet; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; @@ -213,6 +215,7 @@ private WebFilter getAnnotation() { * Registration class for the filter. This object stores the servlet names and the url patterns the filter is * associated with. */ + @SuppressFBWarnings("URF_UNREAD_FIELD") protected class Registration implements FilterRegistration.Dynamic { //------------------------------------------------------------- @@ -350,11 +353,11 @@ public String getInitParameter(String s) { @Override public Set setInitParameters(Map map) { Set conflicts = new LinkedHashSet<>(); - for (String newParamKey : map.keySet()) { - if (initParameters.get(newParamKey) != null) { - conflicts.add(newParamKey); + for (Map.Entry entry : map.entrySet()) { + if (initParameters.get(entry.getKey()) != null) { + conflicts.add(entry.getKey()); } else { - initParameters.put(newParamKey, map.get(newParamKey)); + initParameters.put(entry.getKey(), entry.getValue()); } } return conflicts; diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java index f9523158d..e4e389141 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/filters/UrlPathValidator.java @@ -20,6 +20,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.regex.Pattern; /** @@ -34,7 +36,7 @@ public class UrlPathValidator implements Filter { //------------------------------------------------------------- public static final int DEFAULT_ERROR_CODE = 404; - public static final Pattern PATH_PATTERN = Pattern.compile("^(/[-\\w:@&?=+,.!/~*'%$_;]*)?$"); + //public static final Pattern PATH_PATTERN = Pattern.compile("^(/[-\\w:@&?=+,.!/~*'%$_;]*)?$"); public static final String PARAM_INVALID_STATUS_CODE = "invalid_status_code"; @@ -67,20 +69,29 @@ public void init(FilterConfig filterConfig) throws ServletException { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - // the getServletPath method of the AwsProxyHttpServletRequest returns the request path - String path = ((HttpServletRequest)servletRequest).getServletPath(); + // the getPathInfo method of the AwsProxyHttpServletRequest returns the request path with the correct base path stripped + String path = ((HttpServletRequest)servletRequest).getPathInfo(); if (path == null) { setErrorResponse(servletResponse); return; } - if (!PATH_PATTERN.matcher(path).matches()) { + // switching to this mechanism to avoid ReDOS attacks on the path pattern regex + try { + URI uriPath = new URI(path); + } catch (URISyntaxException e) { + log.error("Invalid uri path in doFilter", e); setErrorResponse(servletResponse); return; } + //if (!PATH_PATTERN.matcher(path).matches()) { + // setErrorResponse(servletResponse); + // return; + //} // Logic taken from the Apache UrlValidator. I opted not to include Apache lib as a dependency to save space // in the final Lambda function package + // https://github.com/apache/commons-validator/blob/trunk/src/main/java/org/apache/commons/validator/UrlValidator.java int slashCount = countStrings("/", path); int dot2Count = countStrings("..", path); int slash2Count = countStrings("//", path); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java index 54bd0ebe3..b9c839f1d 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.io.IOUtils; import javax.ws.rs.core.HttpHeaders; @@ -241,6 +242,7 @@ public AwsProxyRequestBuilder fromJsonString(String jsonContent) return this; } + @SuppressFBWarnings("PATH_TRAVERSAL_IN") public AwsProxyRequestBuilder fromJsonPath(String filePath) throws IOException { request = LambdaContainerHandler.getObjectMapper().readValue(new File(filePath), AwsProxyRequest.class); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/Timer.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/Timer.java new file mode 100644 index 000000000..b8c332aab --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/Timer.java @@ -0,0 +1,80 @@ +package com.amazonaws.serverless.proxy.internal.testutils; + + +import java.util.LinkedHashMap; +import java.util.Map; + + +public final class Timer { + private volatile static Map timers = new LinkedHashMap<>(); + private volatile static boolean enabled = false; + + public static void start(String timerName) { + if (!enabled) { + return; + } + + timers.put(timerName, new TimerInfo(System.currentTimeMillis())); + } + + public static long stop(String timerName) { + if (!enabled) { + return 0L; + } + + TimerInfo info = timers.get(timerName); + if (info == null) { + throw new IllegalArgumentException("Could not find timer " + timerName); + } + + long stopTime = System.currentTimeMillis(); + info.stop(stopTime); + + return stopTime; + } + + + public static Map getTimers() { + return timers; + } + + public static TimerInfo getTimer(String timerName) { + return timers.get(timerName); + } + + public static void enable() { + enabled = true; + } + + public static void disable() { + enabled = false; + } + + private static class TimerInfo { + private long startTime; + private long stopTime; + private long duration; + + public TimerInfo(long start) { + startTime = start; + } + + public void stop(long stop) { + stopTime = stop; + duration = stopTime - startTime; + } + + public long getStartTime() { + return startTime; + } + + public long getStopTime() { + return stopTime; + } + + + public long getDuration() { + return duration; + } + } +} diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/ContainerConfig.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/ContainerConfig.java index 668f25e01..04344d3df 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/ContainerConfig.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/ContainerConfig.java @@ -3,12 +3,16 @@ import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import java.util.ArrayList; +import java.util.List; + /** * Configuration parameters for the framework */ public class ContainerConfig { public static final String DEFAULT_URI_ENCODING = "UTF-8"; + private static final List DEFAULT_FILE_PATHS = new ArrayList() {{ add("/tmp"); add("/var/task"); }}; public static ContainerConfig defaultConfig() { ContainerConfig configuration = new ContainerConfig(); @@ -16,6 +20,7 @@ public static ContainerConfig defaultConfig() { configuration.setUriEncoding(DEFAULT_URI_ENCODING); configuration.setConsolidateSetCookieHeaders(true); configuration.setUseStageAsServletContext(false); + configuration.setValidFilePaths(DEFAULT_FILE_PATHS); return configuration; } @@ -29,6 +34,11 @@ public static ContainerConfig defaultConfig() { private String uriEncoding; private boolean consolidateSetCookieHeaders; private boolean useStageAsServletContext; + private List validFilePaths; + + public ContainerConfig() { + validFilePaths = new ArrayList<>(); + } //------------------------------------------------------------- @@ -131,4 +141,31 @@ public boolean isUseStageAsServletContext() { public void setUseStageAsServletContext(boolean useStageAsServletContext) { this.useStageAsServletContext = useStageAsServletContext; } + + + /** + * Returns the list of file paths that the servlet accepts read/write requests to + * @return A List of file paths. By default this is set to /tmp and /var/task + */ + public List getValidFilePaths() { + return validFilePaths; + } + + + /** + * Sets a list of valid file paths for the servlet to read/write from. + * @param validFilePaths A populated list of base paths + */ + public void setValidFilePaths(List validFilePaths) { + this.validFilePaths = validFilePaths; + } + + + /** + * Adds a new base path to the list of allowed paths. + * @param filePath The base path + */ + public void addValidFilePath(String filePath) { + validFilePaths.add(filePath); + } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContextTest.java index da9f11685..5f4f28579 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContextTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/jaxrs/AwsProxySecurityContextTest.java @@ -19,15 +19,15 @@ public class AwsProxySecurityContextTest { @Test public void localVars_constructor_nullValues() { AwsProxySecurityContext context = new AwsProxySecurityContext(null, null); - assertNull(context.event); - assertNull(context.lambdaContext); + assertNull(context.getEvent()); + assertNull(context.getLambdaContext()); } @Test public void localVars_constructor_ValidRequest() { AwsProxySecurityContext context = new AwsProxySecurityContext(null, REQUEST_NO_AUTH); - assertEquals(REQUEST_NO_AUTH, context.event); - assertNull(context.lambdaContext); + assertEquals(REQUEST_NO_AUTH, context.getEvent()); + assertNull(context.getLambdaContext()); } @Test diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml index 9bd904e8b..920c8d408 100644 --- a/aws-serverless-java-container-jersey/pom.xml +++ b/aws-serverless-java-container-jersey/pom.xml @@ -70,4 +70,78 @@ + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.5 + + + Max + + Low + + true + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.5 + + + Max + + Low + + true + + ${project.build.directory}/findbugs + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + analyze-compile + compile + + check + + + + + + + diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyRequestReader.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyRequestReader.java deleted file mode 100644 index e90902b6f..000000000 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyRequestReader.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.proxy.jersey; - - -import com.amazonaws.serverless.exceptions.InvalidRequestEventException; -import com.amazonaws.serverless.proxy.RequestReader; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.ContainerConfig; -import com.amazonaws.services.lambda.runtime.Context; -import org.glassfish.jersey.internal.MapPropertiesDelegate; -import org.glassfish.jersey.internal.PropertiesDelegate; -import org.glassfish.jersey.server.ContainerRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.ws.rs.core.SecurityContext; -import javax.ws.rs.core.UriBuilder; - -import java.io.ByteArrayInputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Base64; - - -/** - * Default implementation of the RequestReader object. This object reads an incoming AwsProxyRequest - * event and transform it into a Jersey ContainerRequest object. The object sets three custom properties in the - * request's PropertiesDelegate object: The API Gateway request context, the Map of stage variables, and the - * Lambda context object. - * - * The useStageAsBasePath configuration variable lets you set whether the stage name should be included in the - * request path passed to the Jersey application handler. - */ -public class JerseyAwsProxyRequestReader extends RequestReader { - - //------------------------------------------------------------- - // Variables - Private - Static - //------------------------------------------------------------- - - private static AwsProxyRequest currentRequest; - private static Context currentLambdaContext; - - private Logger log = LoggerFactory.getLogger(JerseyAwsProxyRequestReader.class); - - - //------------------------------------------------------------- - // Methods - Implementation - //------------------------------------------------------------- - - /** - * Reads an request object generated by an AWS_PROXY integration in API Gateway and transforms it into a Jersey - * ContainerRequest object. - * - * @param request The incoming request object - * @param securityContext A jax-rs SecurityContext object (@see com.amazonaws.serverless.proxy.SecurityContextWriter) - * @param lambdaContext The AWS Lambda context for the request - * @param config The container config object, this is passed in by the LambdaContainerHandler - * @return A populated ContainerRequest object - * @throws InvalidRequestEventException When the method fails to parse the incoming request - */ - @Override - public ContainerRequest readRequest(AwsProxyRequest request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config) - throws InvalidRequestEventException { - currentRequest = request; - currentLambdaContext = lambdaContext; - - request.setPath(stripBasePath(request.getPath(), config)); - - URI basePathUri; - URI requestPathUri; - String basePath = "/"; - - try { - basePathUri = new URI(basePath); - } catch (URISyntaxException e) { - log.error("Could not read base path URI", e); - throw new InvalidRequestEventException("Error while generating base path URI: " + basePath, e); - } - - - UriBuilder uriBuilder = UriBuilder.fromPath(request.getPath()); - - if (request.getQueryStringParameters() != null) { - for (String paramKey : request.getQueryStringParameters().keySet()) { - uriBuilder = uriBuilder.queryParam(paramKey, request.getQueryStringParameters().get(paramKey)); - } - } - - requestPathUri = uriBuilder.build(); - - PropertiesDelegate apiGatewayProperties = new MapPropertiesDelegate(); - apiGatewayProperties.setProperty(API_GATEWAY_CONTEXT_PROPERTY, request.getRequestContext()); - apiGatewayProperties.setProperty(API_GATEWAY_STAGE_VARS_PROPERTY, request.getStageVariables()); - apiGatewayProperties.setProperty(LAMBDA_CONTEXT_PROPERTY, lambdaContext); - - ContainerRequest requestContext = new ContainerRequest(basePathUri, requestPathUri, request.getHttpMethod(), securityContext, apiGatewayProperties); - - if (request.getBody() != null) { - if (request.isBase64Encoded()) { - requestContext.setEntityStream(new ByteArrayInputStream(Base64.getDecoder().decode(request.getBody()))); - } else { - requestContext.setEntityStream(new ByteArrayInputStream(request.getBody().getBytes())); - } - } - - if (request.getHeaders() != null) { - for (final String headerName : request.getHeaders().keySet()) { - requestContext.headers(headerName, request.getHeaders().get(headerName)); - } - } - - return requestContext; - } - - //------------------------------------------------------------- - // Methods - Protected - //------------------------------------------------------------- - - @Override - protected Class getRequestClass() { - return AwsProxyRequest.class; - } - - - //------------------------------------------------------------- - // Methods - Package - //------------------------------------------------------------- - - public static AwsProxyRequest getCurrentRequest() { - return currentRequest; - } - - - public static Context getCurrentLambdaContext() { - return currentLambdaContext; - } -} diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java deleted file mode 100644 index cbf9f18a4..000000000 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.proxy.jersey; - - -import com.amazonaws.serverless.exceptions.InvalidResponseObjectException; -import com.amazonaws.serverless.proxy.ResponseWriter; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.services.lambda.runtime.Context; - -import java.util.Base64; - - -/** - * Transforms the data from a JerseyResponseWriter object into a valid AwsProxyResponse object. - * - * @see com.amazonaws.serverless.proxy.jersey.JerseyResponseWriter - * @see AwsProxyResponse - */ -public class JerseyAwsProxyResponseWriter extends ResponseWriter { - - //------------------------------------------------------------- - // Methods - Implementation - //------------------------------------------------------------- - - /** - * Reads the data from the JerseyResponseWriter object and creates an AwsProxyResponse object - * - * @param containerResponse The container response or response reader object - * @param lambdaContext The context for the Lambda function execution - * @return An initialized AwsProxyResponse object - * @throws InvalidResponseObjectException When the library fails to read the JerseyResponseWriter object - */ - @Override - public AwsProxyResponse writeResponse(JerseyResponseWriter containerResponse, Context lambdaContext) - throws InvalidResponseObjectException { - try { - AwsProxyResponse response = new AwsProxyResponse(); - response.setStatusCode(containerResponse.getStatusCode()); - - if (containerResponse.getHeaders() != null && containerResponse.getHeaders().size() > 0) { - response.setHeaders(containerResponse.getHeaders()); - } - - if (containerResponse.getResponseBody() != null) { - String responseString; - - if (isValidUtf8(containerResponse.getResponseBody().toByteArray())) { - responseString = new String(containerResponse.getResponseBody().toByteArray()); - } else { - responseString = Base64.getMimeEncoder().encodeToString(containerResponse.getResponseBody().toByteArray()); - response.setBase64Encoded(true); - } - - response.setBody(responseString); - } - - return response; - } catch (Exception ex) { - throw new InvalidResponseObjectException(ex.getMessage(), ex); - } - } -} diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyHandlerFilter.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyHandlerFilter.java new file mode 100644 index 000000000..e36a044fe --- /dev/null +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyHandlerFilter.java @@ -0,0 +1,206 @@ +package com.amazonaws.serverless.proxy.jersey; + + +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.internal.PropertiesDelegate; +import org.glassfish.jersey.server.ApplicationHandler; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.spi.Container; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.UriBuilder; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Enumeration; +import java.util.Locale; +import java.util.concurrent.CountDownLatch; + +import static com.amazonaws.serverless.proxy.RequestReader.API_GATEWAY_CONTEXT_PROPERTY; +import static com.amazonaws.serverless.proxy.RequestReader.API_GATEWAY_STAGE_VARS_PROPERTY; +import static com.amazonaws.serverless.proxy.RequestReader.JAX_SECURITY_CONTEXT_PROPERTY; +import static com.amazonaws.serverless.proxy.RequestReader.LAMBDA_CONTEXT_PROPERTY; + + +public class JerseyHandlerFilter implements Filter, Container { + public static final String JERSEY_SERVLET_REQUEST_PROPERTY = "com.amazonaws.serverless.jersey.servletRequest"; + public static final String JERSEY_SERVLET_RESPONSE_PROPERTY = "com.amazonaws.serverless.jersey.servletResponse"; + + private ApplicationHandler jersey; + private Application app; + private Logger log = LoggerFactory.getLogger(JerseyHandlerFilter.class); + + JerseyHandlerFilter(Application jaxApplication) { + Timer.start("JERSEY_FILTER_CONSTRUCTOR"); + app = jaxApplication; + + jersey = new ApplicationHandler(app); + jersey.onStartup(this); + Timer.stop("JERSEY_FILTER_CONSTRUCTOR"); + } + + @Override + public void init(FilterConfig filterConfig) { + log.info("Initialize Jersey application handler"); + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + Timer.start("JERSEY_FILTER_DOFILTER"); + // we use a latch to make the processing inside Jersey synchronous + CountDownLatch jerseyLatch = new CountDownLatch(1); + + ContainerRequest req = servletRequestToContainerRequest(servletRequest); + req.setWriter(new JerseyServletResponseWriter(servletResponse, jerseyLatch)); + + req.setProperty(JERSEY_SERVLET_RESPONSE_PROPERTY, servletResponse); + + jersey.handle(req); + try { + jerseyLatch.await(); + } catch (InterruptedException e) { + log.error("Interrupted while processing request", e); + throw new InternalServerErrorException(e); + } + Timer.stop("JERSEY_FILTER_DOFILTER"); + filterChain.doFilter(servletRequest, servletResponse); + } + + @Override + public void destroy() { + log.info("Jersey filter destroy"); + jersey.onShutdown(this); + } + + // suppressing warnings because I expect headers and query strings to be checked by the underlying + // servlet implementation + @SuppressFBWarnings({ "SERVLET_HEADER", "SERVLET_QUERY_STRING" }) + private ContainerRequest servletRequestToContainerRequest(ServletRequest request) { + Timer.start("JERSEY_SERVLET_REQUEST_TO_CONTAINER"); + URI basePathUri; + URI requestPathUri; + String basePath = "/"; + HttpServletRequest servletRequest = (HttpServletRequest)request; + + try { + if (servletRequest.getContextPath().equals("")) { + basePathUri = URI.create(basePath); + } else { + basePathUri = new URI(servletRequest.getContextPath()); + } + } catch (URISyntaxException e) { + log.error("Could not read base path URI", e); + basePathUri = URI.create(basePath); + } + + UriBuilder uriBuilder = UriBuilder.fromPath(servletRequest.getPathInfo()); + uriBuilder.replaceQuery(AwsProxyHttpServletRequest.decodeValueIfEncoded(servletRequest.getQueryString())); + + requestPathUri = uriBuilder.build(); + + PropertiesDelegate apiGatewayProperties = new MapPropertiesDelegate(); + apiGatewayProperties.setProperty(API_GATEWAY_CONTEXT_PROPERTY, servletRequest.getAttribute(API_GATEWAY_CONTEXT_PROPERTY)); + apiGatewayProperties.setProperty(API_GATEWAY_STAGE_VARS_PROPERTY, servletRequest.getAttribute(API_GATEWAY_STAGE_VARS_PROPERTY)); + apiGatewayProperties.setProperty(LAMBDA_CONTEXT_PROPERTY, servletRequest.getAttribute(LAMBDA_CONTEXT_PROPERTY)); + apiGatewayProperties.setProperty(JERSEY_SERVLET_REQUEST_PROPERTY, servletRequest); + + ContainerRequest requestContext = new ContainerRequest( + basePathUri, + requestPathUri, + servletRequest.getMethod().toUpperCase(Locale.ENGLISH), + (SecurityContext)servletRequest.getAttribute(JAX_SECURITY_CONTEXT_PROPERTY), + apiGatewayProperties); + + InputStream requestInputStream; + try { + requestInputStream = servletRequest.getInputStream(); + if (requestInputStream != null) { + requestContext.setEntityStream(requestInputStream); + } + } catch (IOException e) { + log.error("Could not read input stream from request", e); + } + + Enumeration headerNames = servletRequest.getHeaderNames(); + + while (headerNames.hasMoreElements()) { + String headerKey = headerNames.nextElement(); + requestContext.header(headerKey, servletRequest.getHeader(headerKey)); + } + Timer.stop("JERSEY_SERVLET_REQUEST_TO_CONTAINER"); + return requestContext; + } + + //------------------------------------------------------------- + // Implementation - Container + //------------------------------------------------------------- + + + @Override + public ResourceConfig getConfiguration() { + return jersey.getConfiguration(); + } + + + @Override + public ApplicationHandler getApplicationHandler() { + return jersey; + } + + + /** + * Shuts down and restarts the application handler in the current container. The ApplicationHandler + * object is re-initialized with the Application object initially set in the LambdaContainer.getInstance() + * call. + */ + @Override + public void reload() { + Timer.start("JERSEY_RELOAD_DEFAULT"); + jersey.onShutdown(this); + + jersey = new ApplicationHandler(app); + + jersey.onReload(this); + jersey.onStartup(this); + Timer.stop("JERSEY_RELOAD_DEFAULT"); + } + + + /** + * Restarts the application handler and configures a different Application object. The new application + * resets the one currently configured in the container. + * @param resourceConfig An initialized Application + */ + @Override + public void reload(ResourceConfig resourceConfig) { + Timer.start("JERSEY_RELOAD_CONFIG"); + jersey.onShutdown(this); + + app = resourceConfig; + jersey = new ApplicationHandler(resourceConfig); + + jersey.onReload(this); + jersey.onStartup(this); + Timer.stop("JERSEY_RELOAD_CONFIG"); + } +} diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java index 297a59620..ec34f7d0b 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java @@ -20,6 +20,12 @@ import com.amazonaws.serverless.proxy.RequestReader; import com.amazonaws.serverless.proxy.ResponseWriter; import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; @@ -29,8 +35,11 @@ import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.spi.Container; +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; import javax.ws.rs.core.Application; +import java.util.EnumSet; import java.util.concurrent.CountDownLatch; @@ -58,8 +67,7 @@ * @param The type for the incoming Lambda event * @param The type for Lambda's return value */ -public class JerseyLambdaContainerHandler extends LambdaContainerHandler - implements Container { +public class JerseyLambdaContainerHandler extends AwsLambdaServletContainerHandler { //------------------------------------------------------------- // Variables - Private @@ -67,8 +75,10 @@ public class JerseyLambdaContainerHandler extends Lam // The Jersey application object private Application jaxRsApplication; - // The Jersey app handler to route requests - private ApplicationHandler applicationHandler; + + private JerseyHandlerFilter jerseyFilter; + // tracker for the first request + private boolean initialized; //------------------------------------------------------------- @@ -85,8 +95,8 @@ public class JerseyLambdaContainerHandler extends Lam * @return A JerseyLambdaContainerHandler object */ public static JerseyLambdaContainerHandler getAwsProxyHandler(Application jaxRsApplication) { - return new JerseyLambdaContainerHandler<>(new JerseyAwsProxyRequestReader(), - new JerseyAwsProxyResponseWriter(), + return new JerseyLambdaContainerHandler<>(new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler(), jaxRsApplication); @@ -106,89 +116,55 @@ public static JerseyLambdaContainerHandler ge * @param exceptionHandler An exception handler * @param jaxRsApplication The JaxRs application */ - public JerseyLambdaContainerHandler(RequestReader requestReader, - ResponseWriter responseWriter, + public JerseyLambdaContainerHandler(RequestReader requestReader, + ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, Application jaxRsApplication) { - super(requestReader, responseWriter, securityContextWriter, exceptionHandler); + super(requestReader, responseWriter, securityContextWriter, exceptionHandler); + Timer.start("JERSEY_CONTAINER_CONSTRUCTOR"); this.jaxRsApplication = jaxRsApplication; - this.applicationHandler = new ApplicationHandler(jaxRsApplication); - - applicationHandler.onStartup(this); + this.initialized = false; + this.jerseyFilter = new JerseyHandlerFilter(this.jaxRsApplication); + Timer.stop("JERSEY_CONTAINER_CONSTRUCTOR"); } - //------------------------------------------------------------- - // Implementation - Container + // Methods - Implementation //------------------------------------------------------------- - /** - * Gets the configuration currently set in the internal ApplicationHandler - * - * @return The Jersey's ResourceConfig object currently running in the container - */ - public ResourceConfig getConfiguration() { - return applicationHandler.getConfiguration(); - } - - - /** - * The instantiated ApplicationHandler object used by this container - * - * @return Jersey's ApplicationHander object - */ - public ApplicationHandler getApplicationHandler() { - return applicationHandler; - } - - - /** - * Shuts down and restarts the application handler in the current container. The ApplicationHandler - * object is re-initialized with the Application object initially set in the LambdaContainer.getInstance() - * call. - */ - public void reload() { - applicationHandler.onShutdown(this); - - this.applicationHandler = new ApplicationHandler(jaxRsApplication); - - applicationHandler.onReload(this); - applicationHandler.onStartup(this); + @Override + protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); } + @Override + protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) + throws Exception { - /** - * Restarts the application handler and configures a different Application object. The new application - * resets the one currently configured in the container. - * @param resourceConfig An initialized Application - */ - public void reload(ResourceConfig resourceConfig) { - applicationHandler.onShutdown(this); - - this.jaxRsApplication = resourceConfig; - this.applicationHandler = new ApplicationHandler(resourceConfig); - - applicationHandler.onReload(this); - applicationHandler.onStartup(this); - } + Timer.start("JERSEY_HANDLE_REQUEST"); + // this method of the AwsLambdaServletContainerHandler sets the request context + super.handleRequest(httpServletRequest, httpServletResponse, lambdaContext); + if (!initialized) { + Timer.start("JERSEY_COLD_START_INIT"); + // call the onStartup event if set to give developers a chance to set filters in the context + if (startupHandler != null) { + startupHandler.onStartup(getServletContext()); + } - //------------------------------------------------------------- - // Methods - Implementation - //------------------------------------------------------------- + // manually add the spark filter to the chain. This should the last one and match all uris + FilterRegistration.Dynamic jerseyFilterReg = getServletContext().addFilter("JerseyFilter", jerseyFilter); + jerseyFilterReg.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); - @Override - protected JerseyResponseWriter getContainerResponse(ContainerRequest request, CountDownLatch latch) { - return new JerseyResponseWriter(latch); - } + initialized = true; + Timer.stop("JERSEY_COLD_START_INIT"); + } + httpServletRequest.setServletContext(getServletContext()); - @Override - protected void handleRequest(ContainerRequest containerRequest, JerseyResponseWriter jerseyResponseWriter, Context lambdaContext) { - containerRequest.setWriter(jerseyResponseWriter); - - applicationHandler.handle(containerRequest); + doFilter(httpServletRequest, httpServletResponse, null); + Timer.stop("JERSEY_HANDLE_REQUEST"); } } diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyResponseWriter.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java similarity index 57% rename from aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyResponseWriter.java rename to aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java index 89a038b1d..8e15112e2 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyResponseWriter.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyServletResponseWriter.java @@ -13,17 +13,22 @@ package com.amazonaws.serverless.proxy.jersey; -import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.SecurityUtils; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.glassfish.jersey.server.ContainerException; import org.glassfish.jersey.server.ContainerResponse; import org.glassfish.jersey.server.spi.ContainerResponseWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import javax.ws.rs.core.HttpHeaders; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.InternalServerErrorException; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.OutputStream; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -35,18 +40,16 @@ * AwsProxyResponse object. The response object is passed in the constructor alongside an ExceptionHandler * instance. */ -class JerseyResponseWriter +class JerseyServletResponseWriter implements ContainerResponseWriter { //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- - private CountDownLatch responseMutex; - private Map headers; - private int statusCode; - private ByteArrayOutputStream responseBody; - + private HttpServletResponse servletResponse; + private Logger log = LoggerFactory.getLogger(JerseyServletResponseWriter.class); + private CountDownLatch jerseyLatch; //------------------------------------------------------------- // Constructors @@ -54,11 +57,11 @@ class JerseyResponseWriter /** * Creates a new response writer. - * @param latch The latch object is used to synchronize the response request handling and response generation for - * AWS Lambda + * @param resp The current ServletResponse from the container */ - JerseyResponseWriter(CountDownLatch latch) { - this.responseMutex = latch; + public JerseyServletResponseWriter(ServletResponse resp, CountDownLatch latch) { + servletResponse = (HttpServletResponse)resp; + jerseyLatch = latch; } @@ -73,76 +76,64 @@ class JerseyResponseWriter * @return An OutputStream for Jersey to write the response body to * @throws ContainerException default Jersey declaration */ + @SuppressFBWarnings("HTTP_RESPONSE_SPLITTING") // suppress this because headers are sanitized in the setHeader method of the servlet response public OutputStream writeResponseStatusAndHeaders(long contentLength, ContainerResponse containerResponse) throws ContainerException { - statusCode = containerResponse.getStatusInfo().getStatusCode(); - - if (headers == null) { - headers = new HashMap<>(); - } - + Timer.start("JERSEY_WRITE_RESPONSE"); + servletResponse.setStatus(containerResponse.getStatusInfo().getStatusCode()); for (final Map.Entry> e : containerResponse.getStringHeaders().entrySet()) { for (final String value : e.getValue()) { - // special case for set cookies - // RFC 2109 allows for a comma separated list of cookies in one Set-Cookie header: https://tools.ietf.org/html/rfc2109 - if (e.getKey().equals(HttpHeaders.SET_COOKIE)) { - if (headers.containsKey(e.getKey()) && LambdaContainerHandler.getContainerConfig().isConsolidateSetCookieHeaders()) { - headers.put(e.getKey(), headers.get(e.getKey()) + ", " + value); - } else { - headers.put(e.getKey(), containerResponse.getStringHeaders().getFirst(e.getKey())); - break; - } - } else { - headers.put(e.getKey(), value); - } + servletResponse.setHeader(e.getKey(), value); } } + try { + Timer.stop("JERSEY_WRITE_RESPONSE"); + return servletResponse.getOutputStream(); + } catch (IOException e) { + log.error("Could not get servlet response output stream", e); + Timer.stop("JERSEY_WRITE_RESPONSE"); + throw new InternalServerErrorException("Could not get servlet response output stream", e); + } - responseBody = new ByteArrayOutputStream(); - - return responseBody; } public boolean suspend(long l, TimeUnit timeUnit, TimeoutHandler timeoutHandler) { + log.debug("Suspend"); return false; } public void setSuspendTimeout(long l, TimeUnit timeUnit) throws IllegalStateException { + log.debug("SuspectTimeout"); } public void commit() { - responseMutex.countDown(); + try { + log.debug("commit"); + jerseyLatch.countDown(); + servletResponse.flushBuffer(); + } catch (IOException e) { + log.error("Could not commit response", e); + throw new InternalServerErrorException(e); + } } public void failure(Throwable throwable) { - responseMutex.countDown(); + try { + log.error("failure", throwable); + jerseyLatch.countDown(); + servletResponse.flushBuffer(); + } catch (IOException e) { + log.error("Could not fail response", e); + throw new InternalServerErrorException(e); + } } public boolean enableResponseBuffering() { return false; } - - - //------------------------------------------------------------- - // Methods - Package - //------------------------------------------------------------- - - Map getHeaders() { - return headers; - } - - - int getStatusCode() { - return statusCode; - } - - - ByteArrayOutputStream getResponseBody() { - return responseBody; - } } diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletContextFactory.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletContextFactory.java index 4144d8db6..126e72e86 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletContextFactory.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletContextFactory.java @@ -14,8 +14,14 @@ import org.glassfish.hk2.api.Factory; +import org.glassfish.jersey.server.ContainerRequest; import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.core.Context; + +import static com.amazonaws.serverless.proxy.jersey.JerseyHandlerFilter.JERSEY_SERVLET_REQUEST_PROPERTY; /** @@ -37,9 +43,18 @@ * */ public class AwsProxyServletContextFactory implements Factory { + @Context ContainerRequest currentRequest; + @Override public ServletContext provide() { - return AwsProxyServletRequestFactory.getRequest().getServletContext(); + HttpServletRequest req = (HttpServletRequest)currentRequest.getProperty(JERSEY_SERVLET_REQUEST_PROPERTY); + + if (req == null) { + throw new InternalServerErrorException("Could not find servlet request in context"); + } + + ServletContext ctx = req.getServletContext(); + return ctx; } diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletRequestFactory.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletRequestFactory.java index 3b353871f..6dc38d0fb 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletRequestFactory.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletRequestFactory.java @@ -13,19 +13,16 @@ package com.amazonaws.serverless.proxy.jersey.factory; -import com.amazonaws.serverless.exceptions.InvalidRequestEventException; -import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; -import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; -import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext; -import com.amazonaws.serverless.proxy.jersey.JerseyAwsProxyRequestReader; -import com.amazonaws.serverless.proxy.jersey.JerseyLambdaContainerHandler; - import org.glassfish.hk2.api.Factory; +import org.glassfish.jersey.server.ContainerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.Context; + +import static com.amazonaws.serverless.proxy.jersey.JerseyHandlerFilter.JERSEY_SERVLET_REQUEST_PROPERTY; + /** * Implementation of Jersey's Factory object for HttpServletRequest objects. This can be used @@ -48,7 +45,7 @@ public class AwsProxyServletRequestFactory implements Factory { - private static AwsProxyHttpServletRequestReader requestReader = new AwsProxyHttpServletRequestReader(); + @Context ContainerRequest currentRequest; private static Logger log = LoggerFactory.getLogger(AwsProxyServletRequestFactory.class); //------------------------------------------------------------- @@ -57,26 +54,11 @@ public class AwsProxyServletRequestFactory @Override public HttpServletRequest provide() { - return getRequest(); + return (HttpServletRequest)currentRequest.getProperty(JERSEY_SERVLET_REQUEST_PROPERTY); } @Override public void dispose(HttpServletRequest httpServletRequest) { } - - public static HttpServletRequest getRequest() { - try { - AwsProxyHttpServletRequest req = requestReader.readRequest(JerseyAwsProxyRequestReader.getCurrentRequest(), - AwsProxySecurityContextWriter.getCurrentContext(), - JerseyAwsProxyRequestReader.getCurrentLambdaContext(), - JerseyLambdaContainerHandler.getContainerConfig()); - req.setServletContext(new AwsServletContext(null)); - return req; - } catch (InvalidRequestEventException e) { - log.error("Invalid request event", e); - return null; - } - - } } diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletResponseFactory.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletResponseFactory.java new file mode 100644 index 000000000..1b9de98c4 --- /dev/null +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/factory/AwsProxyServletResponseFactory.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.jersey.factory; + + +import org.glassfish.hk2.api.Factory; +import org.glassfish.jersey.server.ContainerRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Context; + +import static com.amazonaws.serverless.proxy.jersey.JerseyHandlerFilter.JERSEY_SERVLET_REQUEST_PROPERTY; +import static com.amazonaws.serverless.proxy.jersey.JerseyHandlerFilter.JERSEY_SERVLET_RESPONSE_PROPERTY; + + +/** + * Implementation of Jersey's Factory object for HttpServletResponse objects. This can be used + * to write data directly to the servlet response for the method, without using Jersey's ContainerResponse + * + *
+ * 
+ *     ResourceConfig app = new ResourceConfig().packages("my.app.package")
+ *         .register(new AbstractBinder() {
+ *             {@literal @}Override
+ *             protected void configure() {
+ *                 bindFactory(AwsProxyServletResponseFactory.class)
+ *                     .to(HttpServletResponse.class)
+ *                     .in(RequestScoped.class);
+ *            }
+ *       });
+ * 
+ * 
+ */ +public class AwsProxyServletResponseFactory + implements Factory { + + @Context ContainerRequest currentRequest; + private static Logger log = LoggerFactory.getLogger(AwsProxyServletResponseFactory.class); + + //------------------------------------------------------------- + // Implementation - Factory + //------------------------------------------------------------- + + @Override + public HttpServletResponse provide() { + return (HttpServletResponse)currentRequest.getProperty(JERSEY_SERVLET_RESPONSE_PROPERTY); + } + + + @Override + public void dispose(HttpServletResponse httpServletRequest) { + } +} diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java index 58f437990..86bac4f35 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java @@ -19,6 +19,7 @@ import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.*; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.Context; @@ -33,7 +34,7 @@ */ @Path("/echo") public class EchoJerseyResource { - + public static final String SERVLET_RESP_HEADER_KEY = "X-HttpServletResponse"; public static final String EXCEPTION_MESSAGE = "Fake exception"; @Path("/headers") @GET @@ -120,6 +121,15 @@ public Response echoCustomStatusCode(@QueryParam("status") int statusCode ) { return Response.status(statusCode).entity(output).build(); } + @Path("/servlet-response") @GET + @Produces(MediaType.APPLICATION_JSON) + public Response echoCustomStatusCode(@Context HttpServletResponse resp) { + SingleValueModel output = new SingleValueModel(); + output.setValue("Custom header in resp"); + resp.setHeader(SERVLET_RESP_HEADER_KEY, "1"); + return Response.ok().entity(output).build(); + } + @Path("/binary") @GET @Produces("application/octet-stream") public Response echoBinaryData() { diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java index 71c58bd78..bbb28853c 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java @@ -15,6 +15,7 @@ import com.amazonaws.serverless.proxy.jersey.factory.AwsProxyServletContextFactory; import com.amazonaws.serverless.proxy.jersey.factory.AwsProxyServletRequestFactory; +import com.amazonaws.serverless.proxy.jersey.factory.AwsProxyServletResponseFactory; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext; @@ -35,6 +36,7 @@ import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.Response; import java.io.IOException; @@ -66,6 +68,9 @@ protected void configure() { bindFactory(AwsProxyServletContextFactory.class) .to(ServletContext.class) .in(RequestScoped.class); + bindFactory(AwsProxyServletResponseFactory.class) + .to(HttpServletResponse.class) + .in(RequestScoped.class); } }) .property(LoggingFeature.LOGGING_FEATURE_VERBOSITY_SERVER, LoggingFeature.Verbosity.PAYLOAD_ANY); @@ -101,10 +106,24 @@ public void headers_servletRequest_echo() { validateMapResponseModel(output); } + @Test + public void context_servletResponse_setCustomHeader() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/servlet-response", "GET") + .json() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertTrue(output.getHeaders().containsKey(EchoJerseyResource.SERVLET_RESP_HEADER_KEY)); + } + @Test public void context_serverInfo_correctContext() { AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/servlet-context", "GET").build(); AwsProxyResponse output = handler.proxy(request, lambdaContext); + for (String header : output.getHeaders().keySet()) { + System.out.println(header + ": " + output.getHeaders().get(header)); + } assertEquals(200, output.getStatusCode()); assertEquals("application/json", output.getHeaders().get("Content-Type")); diff --git a/aws-serverless-java-container-spark/pom.xml b/aws-serverless-java-container-spark/pom.xml index 9fae1699d..bc1a1adfc 100644 --- a/aws-serverless-java-container-spark/pom.xml +++ b/aws-serverless-java-container-spark/pom.xml @@ -41,6 +41,36 @@ + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.5 + + + Max + + Low + + true + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + @@ -52,6 +82,45 @@ always + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.5 + + + Max + + Low + + true + + ${project.build.directory}/findbugs + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + analyze-compile + compile + + check + + + + diff --git a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java index 87a999468..300dc57eb 100644 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java +++ b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java @@ -12,6 +12,7 @@ */ package com.amazonaws.serverless.proxy.spark; + import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; @@ -19,6 +20,7 @@ import com.amazonaws.serverless.proxy.RequestReader; import com.amazonaws.serverless.proxy.ResponseWriter; import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.internal.servlet.*; @@ -31,7 +33,6 @@ import org.slf4j.LoggerFactory; import spark.Service; import spark.Spark; -import spark.embeddedserver.EmbeddedServerFactory; import spark.embeddedserver.EmbeddedServers; import javax.servlet.DispatcherType; @@ -40,18 +41,22 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.EnumSet; import java.util.concurrent.CountDownLatch; + /** * Implementation of the LambdaContainerHandler object that supports the Spark framework: http://sparkjava.com/ - * + *

* Because of the way this container is implemented, using reflection to change accessibility of methods in the Spark * framework and inserting itself as the default embedded container, it is important that you initialize the Handler * before declaring your spark routes. - * + *

* This implementation uses the default AwsProxyHttpServletRequest and Response implementations. - * + *

*

  * {@code
  *     // always initialize the handler first
@@ -64,10 +69,12 @@
  *     });
  * }
  * 
+ * * @param The request object used by the RequestReader implementation passed to the constructor * @param The response object produced by the ResponseWriter implementation in the constructor */ -public class SparkLambdaContainerHandler extends AwsLambdaServletContainerHandler { +public class SparkLambdaContainerHandler + extends AwsLambdaServletContainerHandler { //------------------------------------------------------------- // Constants @@ -75,26 +82,27 @@ public class SparkLambdaContainerHandler extends AwsL private static final String LAMBDA_EMBEDDED_SERVER_CODE = "AWS_LAMBDA"; - //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- private LambdaEmbeddedServer embeddedServer; + private LambdaEmbeddedServerFactory lambdaServerFactory; private Logger log = LoggerFactory.getLogger(SparkLambdaContainerHandler.class); - //------------------------------------------------------------- // Methods - Public - Static //------------------------------------------------------------- + /** * Returns a new instance of an SparkLambdaContainerHandler initialized to work with AwsProxyRequest * and AwsProxyResponse objects. * * @return a new instance of SparkLambdaContainerHandler + * * @throws ContainerInitializationException Throws this exception if we fail to initialize the Spark container. - * This could be caused by the introspection used to insert the library as the default embedded container + * This could be caused by the introspection used to insert the library as the default embedded container */ public static SparkLambdaContainerHandler getAwsProxyHandler() throws ContainerInitializationException { @@ -105,51 +113,59 @@ public static SparkLambdaContainerHandler get new LambdaEmbeddedServerFactory()); } - //------------------------------------------------------------- // Constructors //------------------------------------------------------------- + public SparkLambdaContainerHandler(RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler, - EmbeddedServerFactory embeddedServerFactory) + LambdaEmbeddedServerFactory embeddedServerFactory) throws ContainerInitializationException { super(requestReader, responseWriter, securityContextWriter, exceptionHandler); + Timer.start("SPARK_CONTAINER_HANDLER_CONSTRUCTOR"); EmbeddedServers.add(LAMBDA_EMBEDDED_SERVER_CODE, embeddedServerFactory); + this.lambdaServerFactory = embeddedServerFactory; // TODO: This is pretty bad but we are not given access to the embeddedServerIdentifier property of the // Service object try { - log.debug("Changing visibility of getInstance method and embeddedServerIdentifier properties"); - Method serviceInstanceMethod = Spark.class.getDeclaredMethod("getInstance"); - serviceInstanceMethod.setAccessible(true); - Service sparkService = (Service) serviceInstanceMethod.invoke(null); - Field serverIdentifierField = Service.class.getDeclaredField("embeddedServerIdentifier"); - serverIdentifierField.setAccessible(true); - serverIdentifierField.set(sparkService, LAMBDA_EMBEDDED_SERVER_CODE); - } catch (NoSuchFieldException e) { - log.error("Could not fine embeddedServerIdentifier field in Service class", e); - throw new ContainerInitializationException("Cannot find embeddedServerIdentifier field in Service class", e); - } catch (NoSuchMethodException e) { - log.error("Could not find getInstance method in Spark class", e); - throw new ContainerInitializationException("Cannot find getInstance method in Spark class", e); - } catch (IllegalAccessException e) { - log.error("Could not access getInstance method in Spark class", e); - throw new ContainerInitializationException("Cannot access getInstance method in Spark class", e); - } catch (InvocationTargetException e) { - log.error("Could not invoke getInstance method in Spark class", e); - throw new ContainerInitializationException("Cannot invoke getInstance method in Spark class", e); + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + log.debug("Changing visibility of getInstance method and embeddedServerIdentifier properties"); + Method serviceInstanceMethod = Spark.class.getDeclaredMethod("getInstance"); + serviceInstanceMethod.setAccessible(true); + Service sparkService = (Service) serviceInstanceMethod.invoke(null); + Field serverIdentifierField = Service.class.getDeclaredField("embeddedServerIdentifier"); + serverIdentifierField.setAccessible(true); + serverIdentifierField.set(sparkService, LAMBDA_EMBEDDED_SERVER_CODE); + return null; + }); + } catch (PrivilegedActionException e) { + if (e.getException() instanceof NoSuchFieldException) { + log.error("Could not fine embeddedServerIdentifier field in Service class", e.getException()); + } else if (e.getException() instanceof NoSuchMethodException) { + log.error("Could not find getInstance method in Spark class", e.getException()); + } else if (e.getException() instanceof IllegalAccessException) { + log.error("Could not access getInstance method in Spark class", e.getException()); + } else if (e.getException() instanceof InvocationTargetException) { + log.error("Could not invoke getInstance method in Spark class", e.getException()); + } else { + log.error("Unknown exception while modifying Spark class", e.getException()); + } + Timer.stop("SPARK_CONTAINER_HANDLER_CONSTRUCTOR"); + throw new ContainerInitializationException("Could not initialize Spark server", e.getException()); } + Timer.stop("SPARK_CONTAINER_HANDLER_CONSTRUCTOR"); } - //------------------------------------------------------------- // Methods - Implementation //------------------------------------------------------------- + @Override protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { return new AwsHttpServletResponse(request, latch); @@ -159,11 +175,12 @@ protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest @Override protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) throws Exception { - + Timer.start("SPARK_HANDLE_REQUEST"); // this method of the AwsLambdaServletContainerHandler sets the request context super.handleRequest(httpServletRequest, httpServletResponse, lambdaContext); if (embeddedServer == null) { + Timer.start("SPARK_COLD_START"); log.debug("First request, getting new server instance"); // trying to call init in case the embedded server had not been initialized. @@ -173,7 +190,7 @@ protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsH // condition and solve GitHub issue #71. Spark.awaitInitialization(); - embeddedServer = LambdaEmbeddedServerFactory.getServerInstance(); + embeddedServer = lambdaServerFactory.getServerInstance(); // call the onStartup event if set to give developers a chance to set filters in the context if (startupHandler != null) { @@ -183,8 +200,12 @@ protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsH // manually add the spark filter to the chain. This should the last one and match all uris FilterRegistration.Dynamic sparkRegistration = getServletContext().addFilter("SparkFilter", embeddedServer.getSparkFilter()); sparkRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); + Timer.stop("SPARK_COLD_START"); } + httpServletRequest.setServletContext(getServletContext()); + doFilter(httpServletRequest, httpServletResponse, null); + Timer.stop("SPARK_HANDLE_REQUEST"); } } diff --git a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java index 6885cc52d..1db167ae5 100644 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java +++ b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServer.java @@ -1,5 +1,8 @@ package com.amazonaws.serverless.proxy.spark.embeddedserver; +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import spark.embeddedserver.EmbeddedServer; @@ -10,10 +13,6 @@ import spark.staticfiles.StaticFilesConfiguration; import javax.servlet.Filter; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; import java.util.Map; import java.util.Optional; @@ -36,12 +35,14 @@ public class LambdaEmbeddedServer //------------------------------------------------------------- LambdaEmbeddedServer(Routes routes, StaticFilesConfiguration filesConfig, boolean multipleHandlers) { + Timer.start("SPARK_EMBEDDED_SERVER_CONSTRUCTOR"); applicationRoutes = routes; staticFilesConfiguration = filesConfig; hasMultipleHandler = multipleHandlers; // try to initialize the filter here. sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, true, hasMultipleHandler); + Timer.stop("SPARK_EMBEDDED_SERVER_CONSTRUCTOR"); } @@ -50,14 +51,15 @@ public class LambdaEmbeddedServer //------------------------------------------------------------- @Override public int ignite(String s, int i, SslStores sslStores, int i1, int i2, int i3) - throws Exception { + throws ContainerInitializationException { + Timer.start("SPARK_EMBEDDED_IGNITE"); log.info("Starting Spark server, ignoring port and host"); // if not initialized yet if (sparkFilter == null) { sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, true, hasMultipleHandler); } sparkFilter.init(null); - + Timer.stop("SPARK_EMBEDDED_IGNITE"); return i; } @@ -71,8 +73,7 @@ public void configureWebSockets(Map webSocketHa @Override - public void join() - throws InterruptedException { + public void join() { log.info("Called join method, nothing to do here since Lambda only runs a single event per container"); } @@ -93,12 +94,6 @@ public int activeThreadCount() { // Methods - Public //------------------------------------------------------------- - public void handle(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - sparkFilter.doFilter(request, response, null); - } - - /** * Returns the initialized instance of the main Spark filter. * @return The spark filter instance. diff --git a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java index 3bf02a717..255e442a2 100644 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java +++ b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/embeddedserver/LambdaEmbeddedServerFactory.java @@ -1,5 +1,8 @@ package com.amazonaws.serverless.proxy.spark.embeddedserver; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import spark.embeddedserver.EmbeddedServer; import spark.embeddedserver.EmbeddedServerFactory; import spark.route.Routes; @@ -11,7 +14,7 @@ public class LambdaEmbeddedServerFactory implements EmbeddedServerFactory { // Variables - Private - Static //------------------------------------------------------------- - private static LambdaEmbeddedServer embeddedServer; + private static volatile LambdaEmbeddedServer embeddedServer; /** @@ -24,8 +27,9 @@ public LambdaEmbeddedServerFactory() {} * Constructor used in unit tests to inject a mocked embedded server * @param server The mocked server */ + @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD") // suppressing the warining as this constructor is only used for unit tests public LambdaEmbeddedServerFactory(LambdaEmbeddedServer server) { - embeddedServer = server; + LambdaEmbeddedServerFactory.embeddedServer = server; } @@ -36,10 +40,11 @@ public LambdaEmbeddedServerFactory(LambdaEmbeddedServer server) { @Override public EmbeddedServer create(Routes routes, StaticFilesConfiguration staticFilesConfiguration, boolean multipleHandlers) { + Timer.start("SPARK_SERVER_FACTORY_CREATE"); if (embeddedServer == null) { - embeddedServer = new LambdaEmbeddedServer(routes, staticFilesConfiguration, multipleHandlers); + LambdaEmbeddedServerFactory.embeddedServer = new LambdaEmbeddedServer(routes, staticFilesConfiguration, multipleHandlers); } - + Timer.stop("SPARK_SERVER_FACTORY_CREATE"); return embeddedServer; } @@ -48,7 +53,7 @@ public EmbeddedServer create(Routes routes, StaticFilesConfiguration staticFiles // Methods - Getter/Setter //------------------------------------------------------------- - public static LambdaEmbeddedServer getServerInstance() { + public LambdaEmbeddedServer getServerInstance() { return embeddedServer; } } diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 1b31f4b51..b12554d3a 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -79,6 +79,36 @@ + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.5 + + + Max + + Low + + true + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + @@ -90,6 +120,45 @@ always + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.5 + + + Max + + Low + + true + + ${project.build.directory}/findbugs + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + analyze-compile + compile + + check + + + + diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java index e44147533..26914ae0c 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java @@ -12,6 +12,9 @@ */ package com.amazonaws.serverless.proxy.spring; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; @@ -43,25 +46,34 @@ * `currentResponse` private property to the value of the new `HttpServletResponse` object. This is used to intercept * Spring notifications for the `ServletRequestHandledEvent` and call the flush method to release the latch. */ +@SuppressFBWarnings("MTIA_SUSPECT_SERVLET_INSTANCE_FIELD") // we assume we are running in a "single threaded" environment - AWS Lambda public class LambdaSpringApplicationInitializer extends HttpServlet implements WebApplicationInitializer { public static final String ERROR_NO_CONTEXT = "No application context or configuration classes provided"; private static final String DEFAULT_SERVLET_NAME = "aws-servless-java-container"; + private static final long serialVersionUID = 42L; + + // all of these variables are declared as volatile for correctness, technically this class is an implementation of + // HttpServlet and could live in a multi-threaded environment. Because the library runs inside AWS Lambda, we can + // assume we will be in a single-threaded environment. + // Configuration variables that can be passed in - private ConfigurableWebApplicationContext applicationContext; - private boolean refreshContext = true; - private List contextListeners; - private List springProfiles; + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private transient volatile ConfigurableWebApplicationContext applicationContext; + private volatile boolean refreshContext = true; + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private transient volatile List contextListeners; + private volatile ArrayList springProfiles; // Dynamically instantiated properties - private ServletConfig dispatcherConfig; - private DispatcherServlet dispatcherServlet; + private volatile DispatcherServlet dispatcherServlet; // The current response is used to release the latch when Spring emits the request handled event - private HttpServletResponse currentResponse; + private transient volatile HttpServletResponse currentResponse; - private Logger log = LoggerFactory.getLogger(LambdaSpringApplicationInitializer.class); + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private transient Logger log = LoggerFactory.getLogger(LambdaSpringApplicationInitializer.class); /** * Creates a new instance of the WebApplicationInitializer @@ -99,7 +111,6 @@ public void dispatch(HttpServletRequest request, HttpServletResponse response) dispatcherServlet.service(request, response); } - /** * Gets the initialized Spring dispatcher servlet instance. * @return The Spring dispatcher servlet @@ -118,12 +129,14 @@ public void setSpringProfiles(List springProfiles) { @Override public void onStartup(ServletContext servletContext) throws ServletException { + Timer.start("SPRING_INITIALIZER_ONSTARTUP"); if (springProfiles != null) { applicationContext.getEnvironment().setActiveProfiles(springProfiles.toArray(new String[0])); } applicationContext.setServletContext(servletContext); - dispatcherConfig = new DefaultDispatcherConfig(servletContext); + + DefaultDispatcherConfig dispatcherConfig = new DefaultDispatcherConfig(servletContext); applicationContext.setServletConfig(dispatcherConfig); // Configure the listener for the request handled events. All we do here is release the latch @@ -153,6 +166,7 @@ public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledE dispatcherServlet.init(dispatcherConfig); notifyStartListeners(servletContext); + Timer.stop("SPRING_INITIALIZER_ONSTARTUP"); } private void notifyStartListeners(ServletContext context) { @@ -170,7 +184,18 @@ private void notifyStartListeners(ServletContext context) { @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + Timer.start("SPRING_INITIALIZER_SERVICE"); + Timer.start("SPRING_INITIALIZER_SERVICE_CAST"); + if (!(req instanceof HttpServletRequest)) { + throw new ServletException("Cannot service request that is not instance of HttpServletRequest"); + } + + if (!(res instanceof HttpServletResponse)) { + throw new ServletException("Cannot work with response that is not instance of HttpServletResponse"); + } + Timer.stop("SPRING_INITIALIZER_SERVICE_CAST"); dispatch((HttpServletRequest)req, (HttpServletResponse)res); + Timer.stop("SPRING_INITIALIZER_SERVICE"); } /** diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java index 164e8e1d3..fd961ee39 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -19,6 +19,7 @@ import com.amazonaws.serverless.proxy.RequestReader; import com.amazonaws.serverless.proxy.ResponseWriter; import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.internal.servlet.*; @@ -90,7 +91,9 @@ public SpringBootLambdaContainerHandler(RequestReader springBootInitializer) throws ContainerInitializationException { super(requestReader, responseWriter, securityContextWriter, exceptionHandler); + Timer.start("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); this.springBootInitializer = springBootInitializer; + Timer.stop("SPRINGBOOT_CONTAINER_HANDLER_CONSTRUCTOR"); } public void activateSpringProfiles(String... profiles) { @@ -107,12 +110,14 @@ protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest @Override protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { // this method of the AwsLambdaServletContainerHandler sets the servlet context + Timer.start("SPRINGBOOT_HANDLE_REQUEST"); if (getServletContext() == null) { setServletContext(new SpringBootAwsServletContext()); } // wire up the application context on the first invocation if (!initialized) { + Timer.start("SPRINGBOOT_COLD_START"); if (springProfiles != null && springProfiles.length > 0) { System.setProperty("spring.profiles.active", String.join(",", springProfiles)); } @@ -128,6 +133,7 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt } initialized = true; + Timer.stop("SPRINGBOOT_COLD_START"); } containerRequest.setServletContext(getServletContext()); @@ -137,6 +143,7 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt DispatcherServlet dispatcherServlet = applicationContext.getBean("dispatcherServlet", DispatcherServlet.class); // process filters & invoke servlet doFilter(containerRequest, containerResponse, dispatcherServlet); + Timer.stop("SPRINGBOOT_HANDLE_REQUEST"); } private class SpringBootAwsServletContext extends AwsServletContext { diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java index 76df8bbc6..108cb3843 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java @@ -19,6 +19,7 @@ import com.amazonaws.serverless.proxy.RequestReader; import com.amazonaws.serverless.proxy.ResponseWriter; import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.internal.servlet.*; @@ -95,7 +96,9 @@ public SpringLambdaContainerHandler(RequestReader1.10.19 test + + + + com.google.code.findbugs + annotations + 3.0.1 + provided + diff --git a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/LambdaHandler.java b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/LambdaHandler.java deleted file mode 100644 index b680718a1..000000000 --- a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/LambdaHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.sample.jersey; - -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.jersey.JerseyLambdaContainerHandler; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import org.glassfish.jersey.jackson.JacksonFeature; -import org.glassfish.jersey.server.ResourceConfig; - -public class LambdaHandler implements RequestHandler { - private final ResourceConfig jerseyApplication = new ResourceConfig() - .packages("com.amazonaws.serverless.sample.jersey") - .register(JacksonFeature.class); - private final JerseyLambdaContainerHandler handler - = JerseyLambdaContainerHandler.getAwsProxyHandler(jerseyApplication); - - @Override - public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { - return handler.proxy(awsProxyRequest, context); - } -} \ No newline at end of file diff --git a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/StreamLambdaHandler.java b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/StreamLambdaHandler.java index 6195e6539..34567ce7f 100644 --- a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/StreamLambdaHandler.java +++ b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/StreamLambdaHandler.java @@ -2,6 +2,7 @@ import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.jersey.JerseyLambdaContainerHandler; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; @@ -23,6 +24,11 @@ public class StreamLambdaHandler implements RequestStreamHandler { private final JerseyLambdaContainerHandler handler = JerseyLambdaContainerHandler.getAwsProxyHandler(jerseyApplication); + public StreamLambdaHandler() { + // we enable the timer for debugging. This SHOULD NOT be enabled in production. + Timer.enable(); + } + @Override public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { @@ -32,6 +38,9 @@ public void handleRequest(InputStream inputStream, OutputStream outputStream, Co AwsProxyResponse resp = handler.proxy(request, context); LambdaContainerHandler.getObjectMapper().writeValue(outputStream, resp); + + System.err.println(LambdaContainerHandler.getObjectMapper().writeValueAsString(Timer.getTimers())); + // just in case it wasn't closed by the mapper outputStream.close(); } diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/LambdaHandler.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/LambdaHandler.java deleted file mode 100644 index bfc795c8f..000000000 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/LambdaHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.sample.spark; - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.spark.SparkLambdaContainerHandler; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spark.Spark; - -public class LambdaHandler implements RequestHandler { - private boolean isInitialized = false; - private SparkLambdaContainerHandler handler; - private Logger log = LoggerFactory.getLogger(LambdaHandler.class); - - @Override - public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { - if (!isInitialized) { - isInitialized = true; - try { - handler = SparkLambdaContainerHandler.getAwsProxyHandler(); - SparkResources.defineResources(); - Spark.awaitInitialization(); - } catch (ContainerInitializationException e) { - log.error("Cannot initialize Spark application", e); - return null; - } - } - return handler.proxy(awsProxyRequest, context); - } -} \ No newline at end of file diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java index c7c1b3bdb..c30c895e7 100644 --- a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java +++ b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/StreamLambdaHandler.java @@ -3,6 +3,7 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spark.SparkLambdaContainerHandler; @@ -23,6 +24,11 @@ public class StreamLambdaHandler implements RequestStreamHandler { private SparkLambdaContainerHandler handler; private Logger log = LoggerFactory.getLogger(StreamLambdaHandler.class); + public StreamLambdaHandler() { + // we enable the timer for debugging. This SHOULD NOT be enabled in production. + Timer.enable(); + } + @Override public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { @@ -43,6 +49,9 @@ public void handleRequest(InputStream inputStream, OutputStream outputStream, Co AwsProxyResponse resp = handler.proxy(request, context); LambdaContainerHandler.getObjectMapper().writeValue(outputStream, resp); + + System.err.println(LambdaContainerHandler.getObjectMapper().writeValueAsString(Timer.getTimers())); + // just in case it wasn't closed by the mapper outputStream.close(); } diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java deleted file mode 100644 index 0aa891264..000000000 --- a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ -package com.amazonaws.serverless.sample.spring; - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -public class LambdaHandler implements RequestHandler { - private SpringLambdaContainerHandler handler; - private Logger log = LoggerFactory.getLogger(LambdaHandler.class); - - public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { - if (handler == null) { - try { - handler = SpringLambdaContainerHandler.getAwsProxyHandler(PetStoreSpringAppConfig.class); - } catch (ContainerInitializationException e) { - log.error("Cannot initialize spring handler", e); - return null; - } - } - return handler.proxy(awsProxyRequest, context); - } -} diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java index 8e08830ac..790fc13cf 100644 --- a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/StreamLambdaHandler.java @@ -3,6 +3,7 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; @@ -21,6 +22,11 @@ public class StreamLambdaHandler implements RequestStreamHandler { private SpringLambdaContainerHandler handler; private Logger log = LoggerFactory.getLogger(StreamLambdaHandler.class); + public StreamLambdaHandler() { + // we enable the timer for debugging. This SHOULD NOT be enabled in production. + Timer.enable(); + } + @Override public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { @@ -39,6 +45,9 @@ public void handleRequest(InputStream inputStream, OutputStream outputStream, Co AwsProxyResponse resp = handler.proxy(request, context); LambdaContainerHandler.getObjectMapper().writeValue(outputStream, resp); + + System.err.println(LambdaContainerHandler.getObjectMapper().writeValueAsString(Timer.getTimers())); + // just in case it wasn't closed by the mapper outputStream.close(); } diff --git a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/LambdaHandler.java b/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/LambdaHandler.java deleted file mode 100644 index 4e272ab7a..000000000 --- a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/LambdaHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.amazonaws.serverless.sample.springboot; - - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - - -public class LambdaHandler implements RequestStreamHandler { - private SpringBootLambdaContainerHandler handler; - private static ObjectMapper mapper = new ObjectMapper(); - private Logger log = LoggerFactory.getLogger(LambdaHandler.class); - - @Override - public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) - throws IOException { - if (handler == null) { - try { - handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); - - } catch (ContainerInitializationException e) { - e.printStackTrace(); - outputStream.close(); - return; - } - } - - AwsProxyRequest request = mapper.readValue(inputStream, AwsProxyRequest.class); - - AwsProxyResponse resp = handler.proxy(request, context); - mapper.writeValue(outputStream, resp); - // just in case it wasn't closed by the mapper - outputStream.close(); - } -} diff --git a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/StreamLambdaHandler.java b/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/StreamLambdaHandler.java index 1cf57452c..abc03895c 100644 --- a/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/StreamLambdaHandler.java +++ b/samples/springboot/pet-store/src/main/java/com/amazonaws/serverless/sample/springboot/StreamLambdaHandler.java @@ -3,6 +3,7 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; @@ -21,6 +22,11 @@ public class StreamLambdaHandler implements RequestStreamHandler { private SpringBootLambdaContainerHandler handler; private Logger log = LoggerFactory.getLogger(StreamLambdaHandler.class); + public StreamLambdaHandler() { + // we enable the timer for debugging. This SHOULD NOT be enabled in production. + Timer.enable(); + } + @Override public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { @@ -39,6 +45,9 @@ public void handleRequest(InputStream inputStream, OutputStream outputStream, Co AwsProxyResponse resp = handler.proxy(request, context); LambdaContainerHandler.getObjectMapper().writeValue(outputStream, resp); + + System.err.println(LambdaContainerHandler.getObjectMapper().writeValueAsString(Timer.getTimers())); + // just in case it wasn't closed by the mapper outputStream.close(); }