From 97c935023d24bd1b5869979d20d54a0d85b815e7 Mon Sep 17 00:00:00 2001 From: Hendy Irawan Date: Sat, 2 Sep 2017 06:17:58 +0700 Subject: [PATCH 01/15] Proof-of-concept of Spring Boot, Servlet API, and Spring MVC support for AWS Lambda See https://github.com/spring-projects/spring-boot/issues/10136 --- .../proxy/internal/model/AwsProxyRequest.java | 9 +- .../servlet/AwsHttpServletRequest.java | 12 +- .../servlet/AwsHttpServletResponse.java | 10 +- .../internal/servlet/AwsHttpSession.java | 111 ++++++++ .../AwsLambdaServletContainerHandler.java | 7 +- .../servlet/AwsProxyHttpServletRequest.java | 8 +- .../internal/servlet/AwsServletContext.java | 15 +- .../internal/servlet/FilterChainHolder.java | 40 ++- .../internal/servlet/FilterChainManager.java | 15 +- .../proxy/internal/servlet/FilterHolder.java | 2 +- .../testutils/AwsProxyRequestBuilder.java | 2 + .../servlet/AwsFilterChainManagerTest.java | 18 +- .../spark/SparkLambdaContainerHandler.java | 2 +- .../SpringBootLambdaContainerHandler.java | 254 ++++++++++++++++++ .../spring/SpringLambdaContainerHandler.java | 3 +- 15 files changed, 468 insertions(+), 40 deletions(-) create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java create mode 100644 aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyRequest.java index e9b141ec1..d8c594826 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyRequest.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.HashMap; import java.util.Map; /** @@ -31,7 +32,7 @@ public class AwsProxyRequest { private String resource; private ApiGatewayRequestContext requestContext; private Map queryStringParameters; - private Map headers; + private Map headers = new HashMap<>(); // avoid NPE private Map pathParameters; private String httpMethod; private Map stageVariables; @@ -105,7 +106,11 @@ public Map getHeaders() { public void setHeaders(Map headers) { - this.headers = headers; + if (null != headers) { + this.headers = headers; + } else { + this.headers.clear(); + } } 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 15ad74dac..c4d85dd2d 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 @@ -12,6 +12,8 @@ */ package com.amazonaws.serverless.proxy.internal.servlet; +import com.amazonaws.serverless.proxy.internal.RequestReader; +import com.amazonaws.serverless.proxy.internal.model.ApiGatewayRequestContext; import com.amazonaws.serverless.proxy.internal.model.ContainerConfig; import com.amazonaws.services.lambda.runtime.Context; @@ -24,6 +26,7 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionContext; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; @@ -68,6 +71,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { private Context lambdaContext; private Map attributes; private ServletContext servletContext; + private AwsHttpSession session; protected DispatcherType dispatcherType; @@ -101,13 +105,17 @@ public String getRequestedSessionId() { @Override public HttpSession getSession(boolean b) { - return null; + if (b && null == this.session) { + ApiGatewayRequestContext requestContext = (ApiGatewayRequestContext) getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + this.session = new AwsHttpSession(requestContext.getRequestId()); + } + return this.session; } @Override public HttpSession getSession() { - return null; + return this.session; } 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 4ebfd8938..52b6b80a3 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 @@ -53,6 +53,7 @@ public class AwsHttpServletResponse private int statusCode; private String statusMessage; private String responseBody; + private PrintWriter writer; private ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream(); private CountDownLatch writersCountDownLatch; private AwsHttpServletRequest request; @@ -316,7 +317,10 @@ public void close() @Override public PrintWriter getWriter() throws IOException { - return new PrintWriter(bodyOutputStream); + if (null == writer) { + writer = new PrintWriter(bodyOutputStream); + } + return writer; } @@ -358,7 +362,11 @@ public int getBufferSize() { @Override public void flushBuffer() throws IOException { + if (null != writer) { + writer.flush(); + } responseBody = new String(bodyOutputStream.toByteArray()); + log.debug("Response buffer flushed with {} bytes, latch={}", responseBody.length(), writersCountDownLatch.getCount()); isCommitted = true; writersCountDownLatch.countDown(); } 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 new file mode 100644 index 000000000..6aca5947c --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpSession.java @@ -0,0 +1,111 @@ +package com.amazonaws.serverless.proxy.internal.servlet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionContext; +import java.util.Enumeration; + +public class AwsHttpSession implements HttpSession { + + private static final Logger log = LoggerFactory.getLogger(AwsHttpSession.class); + private String id; + + /** + * @param id API gateway request ID. + */ + 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; + } + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public String getId() { + return id; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public void setMaxInactiveInterval(int interval) { + + } + + @Override + public int getMaxInactiveInterval() { + return 0; + } + + @Override + public HttpSessionContext getSessionContext() { + return null; + } + + @Override + public Object getAttribute(String name) { + return null; + } + + @Override + public Object getValue(String name) { + return null; + } + + @Override + public Enumeration getAttributeNames() { + return null; + } + + @Override + public String[] getValueNames() { + return new String[0]; + } + + @Override + public void setAttribute(String name, Object value) { + + } + + @Override + public void putValue(String name, Object value) { + + } + + @Override + public void removeAttribute(String name) { + + } + + @Override + public void removeValue(String name) { + + } + + @Override + public void invalidate() { + + } + + @Override + public boolean isNew() { + return false; + } +} 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 39d7806ed..cc6697cf0 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 @@ -22,6 +22,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -176,11 +177,13 @@ protected void setServletContext(final ServletContext context) { * Applies the filter chain in the request lifecycle * @param request The Request object. This must be an implementation of HttpServletRequest * @param response The response object. This must be an implementation of HttpServletResponse + * @param servlet Servlet at the end of the chain (optional). * @throws IOException * @throws ServletException */ - protected void doFilter(ContainerRequestType request, ContainerResponseType response) throws IOException, ServletException { - FilterChainHolder chain = filterChainManager.getFilterChain(request); + protected void doFilter(ContainerRequestType request, ContainerResponseType response, Servlet servlet) throws IOException, ServletException { + FilterChainHolder chain = filterChainManager.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 5f836ef11..7245522ee 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 @@ -196,9 +196,13 @@ public String getPathTranslated() { } + /** + * In AWS API Gateway, stage is never given as part of the path. + * @return + */ @Override public String getContextPath() { - return request.getRequestContext().getStage(); + return ""; } @@ -228,7 +232,7 @@ public Principal getUserPrincipal() { @Override public String getRequestURI() { - return request.getPath(); + return (getContextPath().isEmpty() ? "" : "/" + getContextPath()) + request.getPath(); } 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 fcdbcb5ea..957af0f01 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 @@ -34,6 +34,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; @@ -141,9 +142,16 @@ public int getEffectiveMinorVersion() { @Override public String getMimeType(String s) { try { - return Files.probeContentType(Paths.get(s)); + + if (s.startsWith("file:")) { // Support paths such as file:/D:/something/hello.txt + return Files.probeContentType(Paths.get(URI.create(s))); + } else if (s.startsWith("/")) { // Support paths such as file:/D:/something/hello.txt + return Files.probeContentType(Paths.get(URI.create("file://" + s))); + } else { + return Files.probeContentType(Paths.get(s)); + } } catch (IOException e) { - log.warn("Could not find content type for filter", e); + log.warn("Could not find content type for file {}", s, e); return null; } } @@ -364,6 +372,8 @@ public FilterRegistration.Dynamic addFilter(String name, Filter filter) { // filter already exists, we do nothing if (filters.containsKey(name)) { return null; + } else { + log.debug("Adding filter '{}' from {}", name, filter); } FilterHolder newFilter = new FilterHolder(name, filter, this); @@ -376,6 +386,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()); Filter newFilter = createFilter(filterClass); return addFilter(name, newFilter); } catch (ServletException e) { 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 47c5ebe6e..eb42cff77 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 @@ -15,10 +15,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.ArrayList; @@ -30,6 +28,7 @@ * during a request lifecycle */ public class FilterChainHolder implements FilterChain { + private final Servlet servlet; //------------------------------------------------------------- // Variables - Private @@ -47,19 +46,22 @@ public class FilterChainHolder implements FilterChain { /** * Creates a new empty FilterChainHolder + * @param servlet */ - FilterChainHolder() { - this(new ArrayList<>()); + FilterChainHolder(Servlet servlet) { + this(new ArrayList<>(), servlet); } /** * Creates a new instance of a filter chain holder * @param allFilters A populated list of FilterHolder objects + * @param servlet */ - FilterChainHolder(List allFilters) { + FilterChainHolder(List allFilters, Servlet servlet) { filters = allFilters; resetHolder(); + this.servlet = servlet; } @@ -70,9 +72,19 @@ public class FilterChainHolder implements FilterChain { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException, ServletException { currentFilter++; - if (filters == null || filters.size() == 0 || currentFilter > filters.size() - 1) { + if (filters == null || filters.size() == 0 ) { log.debug("Could not find filters to execute, returning"); return; + } else if (currentFilter > filters.size() - 1) { + if (null != servlet) { + log.debug("Starting servlet {}", servlet); + servlet.service(servletRequest, servletResponse); + log.debug("Executed servlet {}", servlet); + return; + } else { + log.debug("No more filters"); + return; + } } // TODO: We do not check for async filters here @@ -82,9 +94,12 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo if (!holder.isFilterInitialized()) { holder.init(); } - log.debug("Starting filter " + holder.getFilterName()); + log.debug("Starting {} {} : filter {}-{} {}", servletRequest.getDispatcherType(), ((HttpServletRequest) servletRequest).getRequestURI(), + currentFilter, holder.getFilterName(), holder.getFilter()); holder.getFilter().doFilter(servletRequest, servletResponse, this); - log.debug("Executed filter " + holder.getFilterName()); + log.debug("Executed {} {} : filter {}-{} {}", servletRequest.getDispatcherType(), ((HttpServletRequest) servletRequest).getRequestURI(), + currentFilter, holder.getFilterName(), holder.getFilter()); + currentFilter--; } @@ -144,4 +159,9 @@ public List getFilters() { private void resetHolder() { currentFilter = -1; } + + @Override + public String toString() { + return "filters=" + filters + ", servlet=" + servlet; + } } 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 79bbbc75d..6ddfeadfb 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 @@ -13,6 +13,7 @@ package com.amazonaws.serverless.proxy.internal.servlet; import javax.servlet.DispatcherType; +import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -81,18 +82,19 @@ public abstract class FilterChainManagerFilterChainHolder object that can be used to apply the filters to the request */ - FilterChainHolder getFilterChain(final HttpServletRequest request) { + FilterChainHolder getFilterChain(final HttpServletRequest request, Servlet servlet) { String targetPath = request.getServletPath(); DispatcherType type = request.getDispatcherType(); // only return the cached result if the filter list hasn't changed in the meanwhile - if (getFilterHolders().size() == filtersSize && getFilterChainCache(type, targetPath) != null) { - return getFilterChainCache(type, targetPath); + if (getFilterHolders().size() == filtersSize && getFilterChainCache(type, targetPath, servlet) != null) { + return getFilterChainCache(type, targetPath, servlet); } - FilterChainHolder chainHolder = new FilterChainHolder(); + FilterChainHolder chainHolder = new FilterChainHolder(servlet); Map registrations = getFilterHolders(); if (registrations == null || registrations.size() == 0) { @@ -134,9 +136,10 @@ FilterChainHolder getFilterChain(final HttpServletRequest request) { * initialized with the cached list of {@link FilterHolder} objects * @param type The dispatcher type for the incoming request * @param targetPath The request path - this is extracted with the getPath method of the request object + * @param servlet Servlet to put at the end of the chain (optional). * @return A populated FilterChainHolder */ - private FilterChainHolder getFilterChainCache(final DispatcherType type, final String targetPath) { + private FilterChainHolder getFilterChainCache(final DispatcherType type, final String targetPath, Servlet servlet) { TargetCacheKey key = new TargetCacheKey(); key.setDispatcherType(type); key.setTargetPath(targetPath); @@ -145,7 +148,7 @@ private FilterChainHolder getFilterChainCache(final DispatcherType type, final S return null; } - return new FilterChainHolder(filterCache.get(key)); + return new FilterChainHolder(filterCache.get(key), servlet); } 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 daa318285..a8446b141 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 @@ -29,7 +29,7 @@ public class FilterHolder { //------------------------------------------------------------- private Filter filter; - private FilterConfig filterConfig; + private FilterConfig filterConfig = new Config(); private Registration registration; private String filterName; private Map initParameters; 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 bdfd90fbd..1c9b4d36c 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 @@ -62,10 +62,12 @@ public AwsProxyRequestBuilder(String path, String httpMethod) { this.mapper = new ObjectMapper(); this.request = new AwsProxyRequest(); + this.request.setHeaders(new HashMap<>()); // avoid NPE this.request.setHttpMethod(httpMethod); this.request.setPath(path); this.request.setQueryStringParameters(new HashMap<>()); this.request.setRequestContext(new ApiGatewayRequestContext()); + this.request.getRequestContext().setRequestId("test-invoke-request"); this.request.getRequestContext().setStage("test"); ApiGatewayRequestIdentity identity = new ApiGatewayRequestIdentity(); identity.setSourceIp("127.0.0.1"); diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java index 84f320b96..c5ed8f248 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsFilterChainManagerTest.java @@ -115,21 +115,21 @@ public void filterChain_getFilterChain_subsetOfFilters() { new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req.setServletContext(servletContext); - FilterChainHolder fcHolder = chainManager.getFilterChain(req); + FilterChainHolder fcHolder = chainManager.getFilterChain(req, null); assertEquals(1, fcHolder.filterCount()); assertEquals("Filter1", fcHolder.getFilter(0).getFilterName()); req = new AwsProxyHttpServletRequest( new AwsProxyRequestBuilder("/second/mime", "GET").build(), lambdaContext, null ); - fcHolder = chainManager.getFilterChain(req); + fcHolder = chainManager.getFilterChain(req, null); assertEquals(1, fcHolder.filterCount()); assertEquals("Filter2", fcHolder.getFilter(0).getFilterName()); req = new AwsProxyHttpServletRequest( new AwsProxyRequestBuilder("/second/mime/third", "GET").build(), lambdaContext, null ); - fcHolder = chainManager.getFilterChain(req); + fcHolder = chainManager.getFilterChain(req, null); assertEquals(1, fcHolder.filterCount()); assertEquals("Filter2", fcHolder.getFilter(0).getFilterName()); } @@ -140,7 +140,7 @@ public void filterChain_matchMultipleTimes_expectSameMatch() { new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req.setServletContext(servletContext); - FilterChainHolder fcHolder = chainManager.getFilterChain(req); + FilterChainHolder fcHolder = chainManager.getFilterChain(req, null); assertEquals(1, fcHolder.filterCount()); assertEquals("Filter1", fcHolder.getFilter(0).getFilterName()); @@ -148,7 +148,7 @@ public void filterChain_matchMultipleTimes_expectSameMatch() { new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req.setServletContext(servletContext); - FilterChainHolder fcHolder2 = chainManager.getFilterChain(req2); + FilterChainHolder fcHolder2 = chainManager.getFilterChain(req2, null); assertEquals(1, fcHolder2.filterCount()); assertEquals("Filter1", fcHolder2.getFilter(0).getFilterName()); } @@ -159,7 +159,7 @@ public void filerChain_executeMultipleFilters_expectRunEachTime() { new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req.setServletContext(servletContext); - FilterChainHolder fcHolder = chainManager.getFilterChain(req); + FilterChainHolder fcHolder = chainManager.getFilterChain(req, null); assertEquals(1, fcHolder.filterCount()); assertEquals("Filter1", fcHolder.getFilter(0).getFilterName()); AwsHttpServletResponse resp = new AwsHttpServletResponse(req, new CountDownLatch(1)); @@ -183,7 +183,7 @@ public void filerChain_executeMultipleFilters_expectRunEachTime() { new AwsProxyRequestBuilder("/first/second", "GET").build(), lambdaContext, null ); req2.setServletContext(servletContext); - FilterChainHolder fcHolder2 = chainManager.getFilterChain(req2); + FilterChainHolder fcHolder2 = chainManager.getFilterChain(req2, null); assertEquals(1, fcHolder2.filterCount()); assertEquals("Filter1", fcHolder2.getFilter(0).getFilterName()); assertEquals(-1, fcHolder2.currentFilter); @@ -212,14 +212,14 @@ public void filterChain_getFilterChain_multipleFilters() { req.setServletContext(servletContext); FilterRegistration.Dynamic reg = req.getServletContext().addFilter("Filter4", new MockFilter()); reg.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/second/*"); - FilterChainHolder fcHolder = chainManager.getFilterChain(req); + FilterChainHolder fcHolder = chainManager.getFilterChain(req, null); assertEquals(2, fcHolder.filterCount()); assertEquals("Filter2", fcHolder.getFilter(0).getFilterName()); assertEquals("Filter4", fcHolder.getFilter(1).getFilterName()); reg = req.getServletContext().addFilter("Filter5", new MockFilter()); reg.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/second/*"); - fcHolder = chainManager.getFilterChain(req); + fcHolder = chainManager.getFilterChain(req, null); assertEquals(3, fcHolder.filterCount()); assertEquals("Filter2", fcHolder.getFilter(0).getFilterName()); assertEquals("Filter4", fcHolder.getFilter(1).getFilterName()); 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 b4a059d30..6c7aa1a49 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 @@ -164,7 +164,7 @@ protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsH } } - doFilter(httpServletRequest, httpServletResponse); + doFilter(httpServletRequest, httpServletResponse, null); embeddedServer.handle(httpServletRequest, httpServletResponse); } 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 new file mode 100644 index 000000000..de7ce509b --- /dev/null +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringBootLambdaContainerHandler.java @@ -0,0 +1,254 @@ +/* + * 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.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.*; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.internal.servlet.*; +import com.amazonaws.services.lambda.runtime.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.SpringServletContainerInitializer; +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.servlet.DispatcherServlet; + +import javax.servlet.*; +import javax.servlet.http.HttpServletResponse; +import java.util.*; +import java.util.concurrent.CountDownLatch; + +/** + * Spring implementation of the `LambdaContainerHandler` abstract class. This class uses the `LambdaSpringApplicationInitializer` + * object behind the scenes to proxy requests. The default implementation leverages the `AwsProxyHttpServletRequest` and + * `AwsHttpServletResponse` implemented in the `aws-serverless-java-container-core` package. + * + * Important: Make sure to add {@link LambdaFlushResponseListener} in your SpringBootServletInitializer subclass configure(). + * + * @param The incoming event type + * @param The expected return type + */ +public class SpringBootLambdaContainerHandler extends AwsLambdaServletContainerHandler { + static ThreadLocal currentResponse = new ThreadLocal<>(); + private final Class springBootInitializer; + private static final Logger log = LoggerFactory.getLogger(SpringBootLambdaContainerHandler.class); + + // State vars + private boolean initialized; + + /** + * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects + * @param springBootInitializer {@code SpringBootServletInitializer} class + * @return An initialized instance of the `SpringLambdaContainerHandler` + * @throws ContainerInitializationException + */ + public static SpringBootLambdaContainerHandler getAwsProxyHandler(Class springBootInitializer) + throws ContainerInitializationException { + return new SpringBootLambdaContainerHandler<>( + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + springBootInitializer + ); + } + + /** + * Creates a new container handler with the given reader and writer objects + * + * @param requestReader An implementation of `RequestReader` + * @param responseWriter An implementation of `ResponseWriter` + * @param securityContextWriter An implementation of `SecurityContextWriter` + * @param exceptionHandler An implementation of `ExceptionHandler` + * @throws ContainerInitializationException + */ + public SpringBootLambdaContainerHandler(RequestReader requestReader, + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler, + Class springBootInitializer) + throws ContainerInitializationException { + super(requestReader, responseWriter, securityContextWriter, exceptionHandler); + this.springBootInitializer = springBootInitializer; + } + + @Override + protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); + } + + @Override + protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + // this method of the AwsLambdaServletContainerHandler sets the servlet context + if (getServletContext() == null) { + setServletContext(new SpringBootAwsServletContext()); + } + + // wire up the application context on the first invocation + if (!initialized) { + SpringServletContainerInitializer springServletContainerInitializer = new SpringServletContainerInitializer(); + LinkedHashSet> webAppInitializers = new LinkedHashSet<>(); + webAppInitializers.add(springBootInitializer); + springServletContainerInitializer.onStartup(webAppInitializers, getServletContext()); + initialized = true; + } + + containerRequest.setServletContext(getServletContext()); + + currentResponse.set(containerResponse); + try { + WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); + DispatcherServlet dispatcherServlet = applicationContext.getBean("dispatcherServlet", DispatcherServlet.class); + // process filters & invoke servlet + log.debug("Process filters & invoke servlet: {}", dispatcherServlet); + doFilter(containerRequest, containerResponse, dispatcherServlet); + } finally { + // call the flush method to release the latch + SpringBootLambdaContainerHandler.currentResponse.remove(); + currentResponse.remove(); + } + } + + private class SpringBootAwsServletContext extends AwsServletContext { + public SpringBootAwsServletContext() { + super(SpringBootLambdaContainerHandler.this); + } + + @Override + public ServletRegistration.Dynamic addServlet(String s, String s1) { + throw new UnsupportedOperationException("Only dispatcherServlet is supported"); + } + + @Override + public ServletRegistration.Dynamic addServlet(String s, Class aClass) { + throw new UnsupportedOperationException("Only dispatcherServlet is supported"); + } + + @Override + public ServletRegistration.Dynamic addServlet(String s, Servlet servlet) { + if ("dispatcherServlet".equals(s)) { + try { + servlet.init(new ServletConfig() { + @Override + public String getServletName() { + return s; + } + + @Override + public ServletContext getServletContext() { + return SpringBootAwsServletContext.this; + } + + @Override + public String getInitParameter(String name) { + return null; + } + + @Override + public Enumeration getInitParameterNames() { + return new Enumeration() { + @Override + public boolean hasMoreElements() { + return false; + } + + @Override + public String nextElement() { + return null; + } + }; + } + }); + } catch (ServletException e) { + throw new RuntimeException("Cannot add servlet " + servlet, e); + } + return new ServletRegistration.Dynamic() { + @Override + public String getName() { + return s; + } + + @Override + public String getClassName() { + return null; + } + + @Override + public boolean setInitParameter(String name, String value) { + return false; + } + + @Override + public String getInitParameter(String name) { + return null; + } + + @Override + public Set setInitParameters(Map initParameters) { + return null; + } + + @Override + public Map getInitParameters() { + return null; + } + + @Override + public Set addMapping(String... urlPatterns) { + return null; + } + + @Override + public Collection getMappings() { + return null; + } + + @Override + public String getRunAsRole() { + return null; + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) { + + } + + @Override + public void setLoadOnStartup(int loadOnStartup) { + + } + + @Override + public Set setServletSecurity(ServletSecurityElement constraint) { + return null; + } + + @Override + public void setMultipartConfig(MultipartConfigElement multipartConfig) { + + } + + @Override + public void setRunAsRole(String roleName) { + + } + }; + } else { + throw new UnsupportedOperationException("Only dispatcherServlet is supported"); + } + } + } +} 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 f3a932b0b..9c4950ba4 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 @@ -21,7 +21,6 @@ import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import javax.servlet.ServletContext; import java.util.Arrays; import java.util.concurrent.CountDownLatch; @@ -136,7 +135,7 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt containerRequest.setServletContext(getServletContext()); // process filters - doFilter(containerRequest, containerResponse); + doFilter(containerRequest, containerResponse, null); // invoke servlet initializer.dispatch(containerRequest, containerResponse); } From d237c38f35024229306756bc467c7047bebbb2d5 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 12 Oct 2017 08:40:16 -0700 Subject: [PATCH 02/15] Fixed tests that were failing because of pull request #65 --- .../serverless/proxy/spring/SpringAwsProxyTest.java | 6 +++--- .../serverless/proxy/spring/echoapp/EchoResource.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java index 9d6ee9e11..b94e60b03 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -209,16 +209,16 @@ public void request_requestURI() { @Test public void request_requestURL() { - AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/request-Url", "GET") + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/request-url", "GET") .scheme("https") .serverName("api.myserver.com") .stage("prod") .build(); - + handler.stripBasePath(""); AwsProxyResponse output = handler.proxy(request, lambdaContext); assertEquals(200, output.getStatusCode()); - validateSingleValueModel(output, "https://api.myserver.com/prod/echo/request-Url"); + validateSingleValueModel(output, "https://api.myserver.com/echo/request-url"); } @Test diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java index 55deb7edb..af6ae2fb5 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java @@ -102,7 +102,7 @@ public SingleValueModel echoRequestURI(HttpServletRequest request) { return valueModel; } - @RequestMapping(path = "/request-Url", method = RequestMethod.GET) + @RequestMapping(path = "/request-url", method = RequestMethod.GET) public SingleValueModel echoRequestURL(HttpServletRequest request) { SingleValueModel valueModel = new SingleValueModel(); valueModel.setValue(request.getRequestURL().toString()); From 3cd6184557593a51ea1deb9c118a0f819d517784 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 12 Oct 2017 08:54:02 -0700 Subject: [PATCH 03/15] Minor change to embedded server for Spark - running into a build issue I cannot replicate in local --- .../proxy/spark/embeddedserver/LambdaEmbeddedServer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e6c8541f2..ee51c44f4 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 @@ -49,11 +49,11 @@ public int ignite(String s, int i, SslStores sslStores, int i1, int i2, int i3) throws Exception { log.info("Starting Spark server, ignoring port and host"); sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, false, hasMultipleHandler); - sparkFilter.init(null); + //sparkFilter.init(null); //countDownLatch.countDown(); - return 0; + return i; } From fb8d656989ce4fee1ccf44ca31da77ff64fe2336 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 12 Oct 2017 09:06:02 -0700 Subject: [PATCH 04/15] More minor changes to address build failres for spark. Still cannot replicate in local --- .../proxy/spark/embeddedserver/LambdaEmbeddedServer.java | 3 --- .../proxy/spark/SparkLambdaContainerHandlerTest.java | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) 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 ee51c44f4..b3bfe2756 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 @@ -49,9 +49,6 @@ public int ignite(String s, int i, SslStores sslStores, int i1, int i2, int i3) throws Exception { log.info("Starting Spark server, ignoring port and host"); sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, false, hasMultipleHandler); - //sparkFilter.init(null); - - //countDownLatch.countDown(); return i; } diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java index ecf09226e..101baddd5 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java @@ -9,6 +9,7 @@ import com.amazonaws.serverless.proxy.spark.filter.CustomHeaderFilter; import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import spark.Spark; @@ -34,7 +35,7 @@ public void filters_onStartupMethod_executeFilters() { e.printStackTrace(); fail(); } - + handler.onStartup(c -> { if (c == null) { System.out.println("Null servlet context"); @@ -64,7 +65,7 @@ public static void stopSpark() { Spark.stop(); } - private void configureRoutes() { + private static void configureRoutes() { get("/header-filter", (req, res) -> { res.status(200); return RESPONSE_BODY_TEXT; From 0433b6b778560e1da5fb07a691d963b5e1f4641a Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 12 Oct 2017 09:15:03 -0700 Subject: [PATCH 05/15] Still flying blind, move Spark filter initialization to constructor of the embedded server object --- .../proxy/spark/embeddedserver/LambdaEmbeddedServer.java | 8 +++++++- .../proxy/spark/SparkLambdaContainerHandlerTest.java | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) 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 b3bfe2756..b82282554 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 @@ -38,6 +38,9 @@ public class LambdaEmbeddedServer applicationRoutes = routes; staticFilesConfiguration = filesConfig; hasMultipleHandler = multipleHandlers; + + // try to initialize the filter here. + sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, true, hasMultipleHandler); } @@ -48,7 +51,10 @@ public class LambdaEmbeddedServer public int ignite(String s, int i, SslStores sslStores, int i1, int i2, int i3) throws Exception { log.info("Starting Spark server, ignoring port and host"); - sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, false, hasMultipleHandler); + // if not initialized yet + if (sparkFilter == null) { + sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, true, hasMultipleHandler); + } return i; } diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java index 101baddd5..4790358cb 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java @@ -35,7 +35,7 @@ public void filters_onStartupMethod_executeFilters() { e.printStackTrace(); fail(); } - + handler.onStartup(c -> { if (c == null) { System.out.println("Null servlet context"); From c677a2014855f517ff51b11bc65f73242c171397 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 12 Oct 2017 13:07:13 -0700 Subject: [PATCH 06/15] Moved execution of servlet to the filter chain to allow filters to halt execution. The servlet is injected in the chain by the FilterChainManager. This should fix the last issues with #65 and fix #66 --- .../AwsLambdaServletContainerHandler.java | 8 ++- .../internal/servlet/FilterChainHolder.java | 53 ++++++++----------- .../internal/servlet/FilterChainManager.java | 48 ++++++++++++++++- .../spark/SparkLambdaContainerHandler.java | 10 +++- .../embeddedserver/LambdaEmbeddedServer.java | 10 ++++ .../SparkLambdaContainerHandlerTest.java | 50 +++++++++++++++++ .../spark/filter/UnauthenticatedFilter.java | 45 ++++++++++++++++ .../LambdaSpringApplicationInitializer.java | 24 ++++++++- .../spring/SpringLambdaContainerHandler.java | 4 +- .../proxy/spring/SpringAwsProxyTest.java | 13 +++++ .../spring/echoapp/EchoSpringAppConfig.java | 11 ++++ .../spring/echoapp/UnauthenticatedFilter.java | 45 ++++++++++++++++ 12 files changed, 279 insertions(+), 42 deletions(-) create mode 100644 aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java 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 cc6697cf0..ade16b26b 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 @@ -22,6 +22,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.servlet.FilterChain; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -168,6 +169,10 @@ protected void setServletContext(final ServletContext context) { filterChainManager = new AwsFilterChainManager((AwsServletContext)context); } + protected FilterChain getFilterChain(ContainerRequestType req, Servlet servlet) { + return filterChainManager.getFilterChain(req, servlet); + } + //------------------------------------------------------------- // Methods - Protected @@ -182,12 +187,11 @@ protected void setServletContext(final ServletContext context) { * @throws ServletException */ protected void doFilter(ContainerRequestType request, ContainerResponseType response, Servlet servlet) throws IOException, ServletException { - FilterChainHolder chain = filterChainManager.getFilterChain(request, servlet); + FilterChain chain = getFilterChain(request, servlet); log.debug("FilterChainHolder.doFilter {}", chain); chain.doFilter(request, response); } - //------------------------------------------------------------- // Inner Class - //------------------------------------------------------------- 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 eb42cff77..af7ba7a70 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 @@ -28,7 +28,6 @@ * during a request lifecycle */ public class FilterChainHolder implements FilterChain { - private final Servlet servlet; //------------------------------------------------------------- // Variables - Private @@ -46,22 +45,19 @@ public class FilterChainHolder implements FilterChain { /** * Creates a new empty FilterChainHolder - * @param servlet */ - FilterChainHolder(Servlet servlet) { - this(new ArrayList<>(), servlet); + FilterChainHolder() { + this(new ArrayList<>()); } /** * Creates a new instance of a filter chain holder * @param allFilters A populated list of FilterHolder objects - * @param servlet */ - FilterChainHolder(List allFilters, Servlet servlet) { + FilterChainHolder(List allFilters) { filters = allFilters; resetHolder(); - this.servlet = servlet; } @@ -72,34 +68,27 @@ public class FilterChainHolder implements FilterChain { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException, ServletException { currentFilter++; - if (filters == null || filters.size() == 0 ) { - log.debug("Could not find filters to execute, returning"); - return; - } else if (currentFilter > filters.size() - 1) { - if (null != servlet) { - log.debug("Starting servlet {}", servlet); - servlet.service(servletRequest, servletResponse); - log.debug("Executed servlet {}", servlet); - return; - } else { - log.debug("No more filters"); - return; - } - } // TODO: We do not check for async filters here - FilterHolder holder = filters.get(currentFilter); + // if we still have filters, keep running through the chain + if (currentFilter <= filters.size() - 1) { + FilterHolder holder = filters.get(currentFilter); + + // lazily initialize filters when they are needed + if (!holder.isFilterInitialized()) { + holder.init(); + } + log.debug("Starting {} {} : filter {}-{} {}", servletRequest.getDispatcherType(), ((HttpServletRequest) servletRequest).getRequestURI(), + currentFilter, holder.getFilterName(), holder.getFilter()); + holder.getFilter().doFilter(servletRequest, servletResponse, this); + log.debug("Executed {} {} : filter {}-{} {}", servletRequest.getDispatcherType(), ((HttpServletRequest) servletRequest).getRequestURI(), + currentFilter, holder.getFilterName(), holder.getFilter()); + } - // lazily initialize filters when they are needed - if (!holder.isFilterInitialized()) { - holder.init(); + // if for some reason the response wasn't flushed yet, we force it here. + if (!servletResponse.isCommitted()) { + servletResponse.flushBuffer(); } - log.debug("Starting {} {} : filter {}-{} {}", servletRequest.getDispatcherType(), ((HttpServletRequest) servletRequest).getRequestURI(), - currentFilter, holder.getFilterName(), holder.getFilter()); - holder.getFilter().doFilter(servletRequest, servletResponse, this); - log.debug("Executed {} {} : filter {}-{} {}", servletRequest.getDispatcherType(), ((HttpServletRequest) servletRequest).getRequestURI(), - currentFilter, holder.getFilterName(), holder.getFilter()); - currentFilter--; } @@ -162,6 +151,6 @@ private void resetHolder() { @Override public String toString() { - return "filters=" + filters + ", servlet=" + servlet; + return "filters=" + filters; } } 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 6ddfeadfb..bd3233496 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 @@ -13,10 +13,17 @@ package com.amazonaws.serverless.proxy.internal.servlet; import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; import javax.servlet.Servlet; import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -94,10 +101,13 @@ FilterChainHolder getFilterChain(final HttpServletRequest request, Servlet servl return getFilterChainCache(type, targetPath, servlet); } - FilterChainHolder chainHolder = new FilterChainHolder(servlet); + FilterChainHolder chainHolder = new FilterChainHolder(); Map registrations = getFilterHolders(); if (registrations == null || registrations.size() == 0) { + if (servlet != null) { + chainHolder.addFilter(new FilterHolder(new ServletExecutionFilter(servlet), servletContext)); + } return chainHolder; } for (String name : registrations.keySet()) { @@ -117,6 +127,10 @@ FilterChainHolder getFilterChain(final HttpServletRequest request, Servlet servl // we assume we only ever have one servlet. } + if (servlet != null) { + chainHolder.addFilter(new FilterHolder(new ServletExecutionFilter(servlet), servletContext)); + } + putFilterChainCache(type, targetPath, chainHolder); // update total filter size if (filtersSize != registrations.size()) { @@ -148,7 +162,7 @@ private FilterChainHolder getFilterChainCache(final DispatcherType type, final S return null; } - return new FilterChainHolder(filterCache.get(key), servlet); + return new FilterChainHolder(filterCache.get(key)); } @@ -303,4 +317,34 @@ void setDispatcherType(DispatcherType dispatcherType) { this.dispatcherType = dispatcherType; } } + + private class ServletExecutionFilter implements Filter { + + private FilterConfig config; + private Servlet handlerServlet; + + public ServletExecutionFilter(Servlet handler) { + handlerServlet = handler; + } + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + config = filterConfig; + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + handlerServlet.service(servletRequest, servletResponse); + filterChain.doFilter(servletRequest, servletResponse); + } + + + @Override + public void destroy() { + + } + } } 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 6c7aa1a49..7f6098555 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 @@ -29,9 +29,13 @@ import spark.embeddedserver.EmbeddedServerFactory; import spark.embeddedserver.EmbeddedServers; +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; + import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.EnumSet; import java.util.concurrent.CountDownLatch; /** @@ -162,10 +166,12 @@ protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsH if (startupHandler != null) { startupHandler.onStartup(getServletContext()); } + + // 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, "/*"); } doFilter(httpServletRequest, httpServletResponse, null); - - embeddedServer.handle(httpServletRequest, httpServletResponse); } } 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 b82282554..1025b0a0c 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 @@ -9,6 +9,7 @@ import spark.ssl.SslStores; import spark.staticfiles.StaticFilesConfiguration; +import javax.servlet.Filter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -95,4 +96,13 @@ 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. + */ + public Filter getSparkFilter() { + return sparkFilter; + } } diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java index 4790358cb..154485e1c 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java @@ -7,6 +7,7 @@ import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.spark.filter.CustomHeaderFilter; +import com.amazonaws.serverless.proxy.spark.filter.UnauthenticatedFilter; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -60,6 +61,50 @@ public void filters_onStartupMethod_executeFilters() { } + @Test + public void filters_unauthenticatedFilter_stopRequestProcessing() { + + SparkLambdaContainerHandler handler = null; + try { + handler = SparkLambdaContainerHandler.getAwsProxyHandler(); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + fail(); + } + + handler.onStartup(c -> { + if (c == null) { + System.out.println("Null servlet context"); + fail(); + } + FilterRegistration.Dynamic registration = c.addFilter("UnauthenticatedFilter", UnauthenticatedFilter.class); + // update the registration to map to a path + registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/unauth"); + // servlet name mappings are disabled and will throw an exception + }); + + configureRoutes(); + + // first we test without the custom header, we expect request processing to complete + // successfully + AwsProxyRequest req = new AwsProxyRequestBuilder().method("GET").path("/unauth").build(); + AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertEquals(RESPONSE_BODY_TEXT, response.getBody()); + + // now we test with the custom header, this should stop request processing in the + // filter and return an unauthenticated response + AwsProxyRequest unauthReq = new AwsProxyRequestBuilder().method("GET").path("/unauth") + .header(UnauthenticatedFilter.HEADER_NAME, "1").build(); + AwsProxyResponse unauthResp = handler.proxy(unauthReq, new MockLambdaContext()); + + assertNotNull(unauthResp); + assertEquals(UnauthenticatedFilter.RESPONSE_STATUS, unauthResp.getStatusCode()); + assertEquals("", unauthResp.getBody()); + } + @AfterClass public static void stopSpark() { Spark.stop(); @@ -70,5 +115,10 @@ private static void configureRoutes() { res.status(200); return RESPONSE_BODY_TEXT; }); + + get("/unauth", (req, res) -> { + res.status(200); + return RESPONSE_BODY_TEXT; + }); } } diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java new file mode 100644 index 000000000..60e8eb429 --- /dev/null +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/filter/UnauthenticatedFilter.java @@ -0,0 +1,45 @@ +package com.amazonaws.serverless.proxy.spark.filter; + + +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.servlet.http.HttpServletResponse; + +import java.io.IOException; + + +public class UnauthenticatedFilter implements Filter { + public static final String HEADER_NAME = "X-Unauthenticated-Response"; + public static final int RESPONSE_STATUS = 401; + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + System.out.println("Running unauth filter"); + if (((HttpServletRequest)servletRequest).getHeader(HEADER_NAME) != null) { + ((HttpServletResponse) servletResponse).setStatus(401); + System.out.println("Returning 401"); + return; + } + System.out.println("Continue chain"); + filterChain.doFilter(servletRequest, servletResponse); + } + + + @Override + public void destroy() { + + } +} 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 45a9e1e0f..28e38a4a8 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 @@ -27,6 +27,7 @@ import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.*; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -42,7 +43,7 @@ * `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. */ -public class LambdaSpringApplicationInitializer implements WebApplicationInitializer { +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"; @@ -98,6 +99,15 @@ public void dispatch(HttpServletRequest request, HttpServletResponse response) dispatcherServlet.service(request, response); } + + /** + * Gets the initialized Spring dispatcher servlet instance. + * @return + */ + public Servlet getDispatcherServlet() { + return dispatcherServlet; + } + public List getSpringProfiles() { return Collections.unmodifiableList(springProfiles); } @@ -151,6 +161,18 @@ private void notifyStartListeners(ServletContext context) { } } + + /////////////////////////////////////////////////////////////// + // HttpServlet implementation // + // This is used to pass the initializer to the filter chain // + // to handle requests // + /////////////////////////////////////////////////////////////// + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + dispatch((HttpServletRequest)req, (HttpServletResponse)res); + } + /** * Default configuration class for the DispatcherServlet. This just mocks the behaviour of a default * ServletConfig object with no init parameters 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 9c4950ba4..9ac03516e 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 @@ -135,8 +135,6 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt containerRequest.setServletContext(getServletContext()); // process filters - doFilter(containerRequest, containerResponse, null); - // invoke servlet - initializer.dispatch(containerRequest, containerResponse); + doFilter(containerRequest, containerResponse, initializer); } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java index b94e60b03..ac3420ef1 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -6,6 +6,7 @@ import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig; +import com.amazonaws.serverless.proxy.spring.echoapp.UnauthenticatedFilter; import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel; import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel; import com.fasterxml.jackson.core.JsonProcessingException; @@ -139,6 +140,18 @@ public void error_statusCode_methodNotAllowed() { assertEquals(405, output.getStatusCode()); } + @Test + public void error_unauthenticatedCall_filterStepsRequest() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/status-code", "GET") + .header(UnauthenticatedFilter.HEADER_NAME, "1") + .json() + .queryString("status", "201") + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(401, output.getStatusCode()); + } + @Test public void responseBody_responseWriter_validBody() throws JsonProcessingException { SingleValueModel singleValueModel = new SingleValueModel(); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java index 93134c1bf..1f068f8fe 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java @@ -13,6 +13,11 @@ import org.springframework.context.annotation.PropertySource; import org.springframework.web.context.ConfigurableWebApplicationContext; +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; + +import java.util.EnumSet; + @Configuration @ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp") @@ -26,6 +31,12 @@ public class EchoSpringAppConfig { public SpringLambdaContainerHandler springLambdaContainerHandler() throws ContainerInitializationException { SpringLambdaContainerHandler handler = SpringLambdaContainerHandler.getAwsProxyHandler(applicationContext); handler.setRefreshContext(false); + handler.onStartup(c -> { + FilterRegistration.Dynamic registration = c.addFilter("UnauthenticatedFilter", UnauthenticatedFilter.class); + // update the registration to map to a path + registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/echo/*"); + // servlet name mappings are disabled and will throw an exception + }); return handler; } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java new file mode 100644 index 000000000..b63401572 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/UnauthenticatedFilter.java @@ -0,0 +1,45 @@ +package com.amazonaws.serverless.proxy.spring.echoapp; + + +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.servlet.http.HttpServletResponse; + +import java.io.IOException; + + +public class UnauthenticatedFilter implements Filter { + public static final String HEADER_NAME = "X-Unauthenticated-Response"; + public static final int RESPONSE_STATUS = 401; + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + System.out.println("Running unauth filter"); + if (((HttpServletRequest)servletRequest).getHeader(HEADER_NAME) != null) { + ((HttpServletResponse) servletResponse).setStatus(401); + System.out.println("Returning 401"); + return; + } + System.out.println("Continue chain"); + filterChain.doFilter(servletRequest, servletResponse); + } + + + @Override + public void destroy() { + + } +} From a0e140429b741c2e04ac64f9304d7e04881b878c Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 12 Oct 2017 14:03:28 -0700 Subject: [PATCH 07/15] Added check to avoid NPE when mapping null request body. Addresses #70 --- .../servlet/AwsProxyHttpServletRequest.java | 3 ++ .../proxy/spring/SpringAwsProxyTest.java | 28 +++++++++++++++++++ .../proxy/spring/echoapp/EchoResource.java | 11 ++++++++ 3 files changed, 42 insertions(+) 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 7245522ee..0792098dc 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 @@ -386,6 +386,9 @@ public String getContentType() { @Override public ServletInputStream getInputStream() throws IOException { + if (request.getBody() == null) { + return null; + } byte[] bodyBytes = request.getBody().getBytes(); if (request.isBase64Encoded()) { bodyBytes = Base64.getMimeDecoder().decode(request.getBody()); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java index ac3420ef1..1cbeaef4e 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -189,6 +189,34 @@ public void base64_binaryResponse_base64Encoding() { assertTrue(Base64.isBase64(response.getBody())); } + @Test + public void injectBody_populatedResponse_noException() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/request-body", "POST") + .body("This is a populated body") + .build(); + + AwsProxyResponse response = handler.proxy(request, lambdaContext); + assertNotNull(response.getBody()); + try { + SingleValueModel output = objectMapper.readValue(response.getBody(), SingleValueModel.class); + assertEquals("true", output.getValue()); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + + AwsProxyRequest emptyReq = new AwsProxyRequestBuilder("/echo/request-body", "POST") + .build(); + AwsProxyResponse emptyResp = handler.proxy(emptyReq, lambdaContext); + try { + SingleValueModel output = objectMapper.readValue(emptyResp.getBody(), SingleValueModel.class); + assertEquals(null, output.getValue()); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + } + @Test public void servletRequestEncoding_acceptEncoding_okStatusCode() { SingleValueModel singleValueModel = new SingleValueModel(); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java index af6ae2fb5..185bc7bdc 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java @@ -110,6 +110,17 @@ public SingleValueModel echoRequestURL(HttpServletRequest request) { return valueModel; } + @RequestMapping(path = "/request-body", method = RequestMethod.POST) + public SingleValueModel helloForPopulatedBody(@RequestBody(required = false) String input) { + SingleValueModel valueModel = new SingleValueModel(); + System.out.println("Input: \"" + input + "\""); + if (input != null && !"null".equals(input)) { + valueModel.setValue("true"); + } + + return valueModel; + } + @RequestMapping(path = "/encoded-request-uri/{encoded-var}", method = RequestMethod.GET) public SingleValueModel echoEncodedRequestUri(@PathVariable("encoded-var") String encodedVar) { SingleValueModel valueModel = new SingleValueModel(); From 3104192fe4127dbb193527391fc6b59d1b33395b Mon Sep 17 00:00:00 2001 From: sapessi Date: Mon, 20 Nov 2017 19:31:20 -0800 Subject: [PATCH 08/15] Fixed issue with encoded form parameters and added unit test for it --- .../servlet/AwsProxyHttpServletRequest.java | 22 +++++++++++-------- .../AwsProxyHttpServletRequestFormTest.java | 22 +++++++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) 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 0792098dc..dc2a95504 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 @@ -707,13 +707,8 @@ private Map> getFormUrlEncodedParametersMap() { if (!contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED) || !getMethod().toLowerCase().equals("post")) { return new HashMap<>(); } - String rawBodyContent; - try { - rawBodyContent = URLDecoder.decode(request.getBody(), DEFAULT_CHARACTER_ENCODING); - } catch (UnsupportedEncodingException e) { - log.warn("Could not decode body content - proceeding as if it was already decoded", e); - rawBodyContent = request.getBody(); - } + + String rawBodyContent = request.getBody(); Map> output = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (String parameter : rawBodyContent.split(FORM_DATA_SEPARATOR)) { @@ -725,10 +720,19 @@ private Map> getFormUrlEncodedParametersMap() { if (output.containsKey(parameterKeyValue[0])) { values = output.get(parameterKeyValue[0]); } - values.add(parameterKeyValue[1]); - output.put(parameterKeyValue[0], values); + values.add(decodeValueIfEncoded(parameterKeyValue[1])); + output.put(decodeValueIfEncoded(parameterKeyValue[0]), values); } return output; } + + private String decodeValueIfEncoded(String value) { + try { + return URLDecoder.decode(value, DEFAULT_CHARACTER_ENCODING); + } catch (UnsupportedEncodingException e) { + log.warn("Could not decode body content - proceeding as if it was already decoded", e); + return value; + } + } } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java index f82ab9d93..62f3fe971 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java @@ -7,12 +7,14 @@ import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; import org.apache.http.client.entity.EntityBuilder; +import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.junit.Test; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; import java.io.IOException; import java.util.Random; @@ -29,6 +31,8 @@ public class AwsProxyHttpServletRequestFormTest { private static final String PART_VALUE_2 = "value2"; private static final String FILE_KEY = "file_upload_1"; + private static final String ENCODED_VALUE = "test123a%3D1%262@3"; + private static final HttpEntity MULTIPART_FORM_DATA = MultipartEntityBuilder.create() .addTextBody(PART_KEY_1, PART_VALUE_1) .addTextBody(PART_KEY_2, PART_VALUE_2) @@ -43,6 +47,24 @@ public class AwsProxyHttpServletRequestFormTest { .addTextBody(PART_KEY_2, PART_VALUE_2) .addBinaryBody(FILE_KEY, FILE_BYTES) .build(); + private static final String ENCODED_FORM_ENTITY = PART_KEY_1 + "=" + ENCODED_VALUE + "&" + PART_KEY_2 + "=" + PART_VALUE_2; + + @Test + public void postForm_getParam_getEncodedFullValue() { + try { + AwsProxyRequest proxyRequest = new AwsProxyRequestBuilder("/form", "POST") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED) + .body(ENCODED_FORM_ENTITY) + .build(); + + HttpServletRequest request = new AwsProxyHttpServletRequest(proxyRequest, null, null); + assertNotNull(request.getParts()); + assertEquals("test123a=1&2@3", request.getParameter(PART_KEY_1)); + } catch (IOException | ServletException e) { + fail(e.getMessage()); + } + } + @Test public void postForm_getParts_parsing() { try { From 6c1d15966a86131908ccc0a4214e26a4086568e6 Mon Sep 17 00:00:00 2001 From: sapessi Date: Mon, 20 Nov 2017 19:42:23 -0800 Subject: [PATCH 09/15] Upgraded to latest version of spark to try and address #71 --- aws-serverless-java-container-spark/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-serverless-java-container-spark/pom.xml b/aws-serverless-java-container-spark/pom.xml index 5901f1fa3..4fb1c3e03 100644 --- a/aws-serverless-java-container-spark/pom.xml +++ b/aws-serverless-java-container-spark/pom.xml @@ -15,7 +15,7 @@ - 2.6.0 + 2.7.1 From 1f9e957101bb0293fce42c9a273268e660835c7d Mon Sep 17 00:00:00 2001 From: sapessi Date: Tue, 21 Nov 2017 07:45:41 -0800 Subject: [PATCH 10/15] Added init method call for filter, still cannot replicate #71 in local --- .../proxy/spark/embeddedserver/LambdaEmbeddedServer.java | 1 + 1 file changed, 1 insertion(+) 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 1025b0a0c..6885cc52d 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 @@ -56,6 +56,7 @@ public int ignite(String s, int i, SslStores sslStores, int i1, int i2, int i3) if (sparkFilter == null) { sparkFilter = new MatcherFilter(applicationRoutes, staticFilesConfiguration, true, hasMultipleHandler); } + sparkFilter.init(null); return i; } From 558565c043239d04b78f0128812fd542dc6ec49c Mon Sep 17 00:00:00 2001 From: sapessi Date: Tue, 21 Nov 2017 10:58:13 -0800 Subject: [PATCH 11/15] Added the awaitInitialization() call as suggested in #71. --- .../serverless/proxy/spark/SparkLambdaContainerHandler.java | 4 ++++ 1 file changed, 4 insertions(+) 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 7f6098555..9c61f0812 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 @@ -170,6 +170,10 @@ 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, "/*"); + + // adding this call to make sure that the framework is fully initialized. This should address a race + // condition and solve GitHub issue #71. + Spark.awaitInitialization(); } doFilter(httpServletRequest, httpServletResponse, null); From ab7160b8f1b5077e5c6fa5a3947f6ef1d3563dda Mon Sep 17 00:00:00 2001 From: sapessi Date: Tue, 21 Nov 2017 11:44:25 -0800 Subject: [PATCH 12/15] Changes to address #55. Added a new CognitoUserPoolPrincipal object that exposes claims from the token. Also added support for custom claims. --- .../InvalidRequestEventException.java | 4 ++ .../jaxrs/AwsProxySecurityContext.java | 67 ++++++++++++++----- .../model/CognitoAuthorizerClaims.java | 16 +++++ .../testutils/AwsProxyRequestBuilder.java | 6 ++ .../jaxrs/AwsProxySecurityContextTest.java | 18 ++++- 5 files changed, 92 insertions(+), 19 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/exceptions/InvalidRequestEventException.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/exceptions/InvalidRequestEventException.java index f20a8e554..faf82b9c3 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/exceptions/InvalidRequestEventException.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/exceptions/InvalidRequestEventException.java @@ -22,4 +22,8 @@ public class InvalidRequestEventException extends Exception { public InvalidRequestEventException(String message, Exception e) { super(message, e); } + + public InvalidRequestEventException(String message) { + super(message); + } } 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 816cf8af2..46eb809cc 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 @@ -13,6 +13,7 @@ package com.amazonaws.serverless.proxy.internal.jaxrs; import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.CognitoAuthorizerClaims; import com.amazonaws.services.lambda.runtime.Context; import javax.ws.rs.core.SecurityContext; @@ -61,26 +62,33 @@ public AwsProxySecurityContext(final Context lambdaContext, final AwsProxyReques //------------------------------------------------------------- public Principal getUserPrincipal() { - return () -> { - if (getAuthenticationScheme() == null) { - return null; - } - - if (getAuthenticationScheme().equals(AUTH_SCHEME_CUSTOM)) { - return event.getRequestContext().getAuthorizer().getPrincipalId(); - } else if (getAuthenticationScheme().equals(AUTH_SCHEME_AWS_IAM)) { - // if we received credentials from Cognito Federated Identities then we return the identity id - if (event.getRequestContext().getIdentity().getCognitoIdentityId() != null) { - return event.getRequestContext().getIdentity().getCognitoIdentityId(); - } else { // otherwise the user arn from the credentials - return event.getRequestContext().getIdentity().getUserArn(); + if (getAuthenticationScheme() == null) { + return () -> null; + } + + if (getAuthenticationScheme().equals(AUTH_SCHEME_CUSTOM) || getAuthenticationScheme().equals(AUTH_SCHEME_AWS_IAM)) { + return () -> { + if (getAuthenticationScheme().equals(AUTH_SCHEME_CUSTOM)) { + return event.getRequestContext().getAuthorizer().getPrincipalId(); + } else if (getAuthenticationScheme().equals(AUTH_SCHEME_AWS_IAM)) { + // if we received credentials from Cognito Federated Identities then we return the identity id + if (event.getRequestContext().getIdentity().getCognitoIdentityId() != null) { + return event.getRequestContext().getIdentity().getCognitoIdentityId(); + } else { // otherwise the user arn from the credentials + return event.getRequestContext().getIdentity().getUserArn(); + } } - } else if (getAuthenticationScheme().equals(AUTH_SCHEME_COGNITO_POOL)) { - return event.getRequestContext().getAuthorizer().getClaims().getSubject(); - } - return null; - }; + // return null if we couldn't find a valid scheme + return null; + }; + } + + if (getAuthenticationScheme().equals(AUTH_SCHEME_COGNITO_POOL)) { + return new CognitoUserPoolPrincipal(event.getRequestContext().getAuthorizer().getClaims()); + } + + throw new RuntimeException("Cannot recognize authorization scheme in event"); } @@ -105,4 +113,27 @@ public String getAuthenticationScheme() { return null; } } + + + /** + * 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 { + + private CognitoAuthorizerClaims claims; + + CognitoUserPoolPrincipal(CognitoAuthorizerClaims c) { + claims = c; + } + + @Override + public String getName() { + return claims.getSubject(); + } + + public CognitoAuthorizerClaims getClaims() { + return claims; + } + } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java index 697bce4e8..6c3702862 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/CognitoAuthorizerClaims.java @@ -13,10 +13,14 @@ package com.amazonaws.serverless.proxy.internal.model; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; /** @@ -44,6 +48,8 @@ public class CognitoAuthorizerClaims { // Variables - Private //------------------------------------------------------------- + private Map claims = new HashMap<>(); + @JsonProperty(value = "sub") private String subject; @JsonProperty(value = "aud") @@ -69,6 +75,16 @@ public class CognitoAuthorizerClaims { // Methods - Getter/Setter //------------------------------------------------------------- + @JsonAnyGetter + public String getClaim(String claim) { + return claims.get(claim); + } + + @JsonAnySetter + public void setClaim(String claim, String value) { + claims.put(claim, value); + } + public String getSubject() { return this.subject; } 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 1c9b4d36c..5ffd29413 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 @@ -189,6 +189,12 @@ public AwsProxyRequestBuilder cognitoUserPool(String identityId) { return this; } + public AwsProxyRequestBuilder claim(String claim, String value) { + this.request.getRequestContext().getAuthorizer().getClaims().setClaim(claim, value); + + return this; + } + public AwsProxyRequestBuilder cognitoIdentity(String identityId, String identityPoolId) { this.request.getRequestContext().getIdentity().setCognitoAuthenticationType("IDENTITY"); 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 c5d631588..255f2a0da 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 @@ -4,13 +4,17 @@ import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import org.junit.Test; +import java.security.Principal; + import static org.junit.Assert.*; public class AwsProxySecurityContextTest { + private static final String CLAIM_KEY = "custom:claim"; + private static final String CLAIM_VALUE = "customClaimant"; private static final String COGNITO_IDENTITY_ID = "us-east-2:123123123123"; private static final AwsProxyRequest REQUEST_NO_AUTH = new AwsProxyRequestBuilder("/hello", "GET").build(); private static final AwsProxyRequest REQUEST_COGNITO_USER_POOL = new AwsProxyRequestBuilder("/hello", "GET") - .cognitoUserPool(COGNITO_IDENTITY_ID).build(); + .cognitoUserPool(COGNITO_IDENTITY_ID).claim(CLAIM_KEY, CLAIM_VALUE).build(); @Test public void localVars_constructor_nullValues() { @@ -39,4 +43,16 @@ public void authScheme_getPrincipal_userPool() { assertEquals("COGNITO_USER_POOL", context.getAuthenticationScheme()); assertEquals(COGNITO_IDENTITY_ID, context.getUserPrincipal().getName()); } + + @Test + public void userPool_getClaims_retrieveCustomClaim() { + AwsProxySecurityContext context = new AwsProxySecurityContext(null, REQUEST_COGNITO_USER_POOL); + Principal userPrincipal = context.getUserPrincipal(); + assertNotNull(userPrincipal.getName()); + assertEquals(COGNITO_IDENTITY_ID, userPrincipal.getName()); + + assertTrue(userPrincipal instanceof AwsProxySecurityContext.CognitoUserPoolPrincipal); + assertNotNull(((AwsProxySecurityContext.CognitoUserPoolPrincipal)userPrincipal).getClaims().getClaim(CLAIM_KEY)); + assertEquals(CLAIM_VALUE, ((AwsProxySecurityContext.CognitoUserPoolPrincipal)userPrincipal).getClaims().getClaim(CLAIM_KEY)); + } } From de731558897499ab034bd7d19c28ccc6a5e18279 Mon Sep 17 00:00:00 2001 From: sapessi Date: Tue, 21 Nov 2017 11:51:17 -0800 Subject: [PATCH 13/15] Another commit for #71. Looks like the awaitInitialization method needs to be called after the resources are defined --- README.md | 3 +++ .../com/amazonaws/serverless/sample/spark/LambdaHandler.java | 2 ++ 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index b21c87013..210b948ac 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,9 @@ public class LambdaHandler implements RequestHandler Date: Tue, 21 Nov 2017 12:03:07 -0800 Subject: [PATCH 14/15] Bumped Spark version in sample application --- samples/spark/pet-store/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/spark/pet-store/pom.xml b/samples/spark/pet-store/pom.xml index 7cad08a18..a291acf72 100644 --- a/samples/spark/pet-store/pom.xml +++ b/samples/spark/pet-store/pom.xml @@ -27,7 +27,7 @@ 1.8 1.8 2.8.5 - 2.6.0 + 2.7.1 From a1b0798839b2674427987b78835b8e716d6bb1ba Mon Sep 17 00:00:00 2001 From: sapessi Date: Tue, 21 Nov 2017 14:32:07 -0800 Subject: [PATCH 15/15] Added awaitInitialization call in all spark unit tests --- .../amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java | 2 +- .../serverless/proxy/spark/InitExceptionHandlerTest.java | 2 +- .../proxy/spark/SparkLambdaContainerHandlerTest.java | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java index d75595e26..3975a4218 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/HelloWorldSparkTest.java @@ -37,7 +37,7 @@ public static void initializeServer() { handler = SparkLambdaContainerHandler.getAwsProxyHandler(); configureRoutes(); - + Spark.awaitInitialization(); } catch (RuntimeException | ContainerInitializationException e) { e.printStackTrace(); fail(); diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java index a0c976d51..a95497c26 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/InitExceptionHandlerTest.java @@ -44,7 +44,7 @@ public void initException_mockException_expectHandlerToRun() { serverFactory); configureRoutes(); - + Spark.awaitInitialization(); } catch (Exception e) { e.printStackTrace(); fail("Error while mocking server"); diff --git a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java index 154485e1c..f1884537a 100644 --- a/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java +++ b/aws-serverless-java-container-spark/src/test/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandlerTest.java @@ -50,6 +50,8 @@ public void filters_onStartupMethod_executeFilters() { configureRoutes(); + Spark.awaitInitialization(); + AwsProxyRequest req = new AwsProxyRequestBuilder().method("GET").path("/header-filter").build(); AwsProxyResponse response = handler.proxy(req, new MockLambdaContext()); @@ -84,6 +86,7 @@ public void filters_unauthenticatedFilter_stopRequestProcessing() { }); configureRoutes(); + Spark.awaitInitialization(); // first we test without the custom header, we expect request processing to complete // successfully