From e7e808916bc375d721d76722dc36cd55a39a5c04 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 23 Mar 2023 10:38:42 +0100 Subject: [PATCH] Add integration with AWS API latch --- .../pom.xml | 6 +- .../web/ProxyFilterRegistration.java | 93 ++++++++++++++ .../function/serverless/web/ProxyMvc.java | 115 +++++++++++------ .../serverless/web/ProxyServletContext.java | 29 ++++- .../web/ProxyServletRegistration.java | 120 ++++++++++++++++++ 5 files changed, 318 insertions(+), 45 deletions(-) create mode 100644 spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyFilterRegistration.java create mode 100644 spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletRegistration.java diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml index d8837b163..1b1c85370 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml @@ -27,8 +27,10 @@ spring-webmvc - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api + + org.springframework.boot diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyFilterRegistration.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyFilterRegistration.java new file mode 100644 index 000000000..4def16599 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyFilterRegistration.java @@ -0,0 +1,93 @@ +package org.springframework.cloud.function.serverless.web; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; + +public class ProxyFilterRegistration implements FilterRegistration, FilterRegistration.Dynamic { + + public Filter getFilter() { + return filter; + } + + private final String name; + + private final Filter filter; + + public ProxyFilterRegistration(String name, Filter filter) { + this.name = name; + this.filter = filter; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getClassName() { + return this.filter.getClass().getName(); + } + + @Override + public boolean setInitParameter(String name, String value) { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getInitParameter(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Set setInitParameters(Map initParameters) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map getInitParameters() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) { + // TODO Auto-generated method stub + + } + + @Override + public void addMappingForServletNames(EnumSet dispatcherTypes, boolean isMatchAfter, + String... servletNames) { + // TODO Auto-generated method stub + + } + + @Override + public Collection getServletNameMappings() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void addMappingForUrlPatterns(EnumSet dispatcherTypes, boolean isMatchAfter, + String... urlPatterns) { + // TODO Auto-generated method stub + + } + + @Override + public Collection getUrlPatternMappings() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java index 8a30e8557..46422927b 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.List; +import java.util.concurrent.CountDownLatch; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -32,14 +33,14 @@ import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; @@ -65,41 +66,49 @@ public class ProxyMvc { static final String MVC_RESULT_ATTRIBUTE = ProxyMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE"); - private final DispatcherServlet servlet; - - private final Filter[] filters; + private final DispatcherServlet dispatcher; private final ConfigurableWebApplicationContext applicationContext; - public static ProxyMvc INSTANCE(Class... componentClasses) { - Assert.notEmpty(componentClasses, "'componentClasses' must not be null or empty"); + private ServletContext servletContext; + private volatile boolean initialized; + + public ConfigurableWebApplicationContext getApplicationContext() { + return this.applicationContext; + } + + public ServletContext getServletContext() { + return this.servletContext; + } + + public static ProxyMvc INSTANCE(ConfigurableWebApplicationContext applpicationContext) { ProxyServletContext servletContext = new ProxyServletContext(); - GenericWebApplicationContext applpicationContext = new GenericWebApplicationContext(servletContext); + applpicationContext.setServletContext(servletContext); + DispatcherServlet dispatcher = new DispatcherServlet(applpicationContext); + ServletRegistration.Dynamic reg = servletContext.addServlet("dispatcherServlet", dispatcher); + reg.setLoadOnStartup(1); + + ProxyMvc mvc = new ProxyMvc(dispatcher, applpicationContext); + mvc.servletContext = servletContext; + return mvc; + } + + public static ProxyMvc INSTANCE(Class... componentClasses) { + GenericWebApplicationContext applpicationContext = new GenericWebApplicationContext(); AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(applpicationContext); - reader.register(componentClasses); - - reader.register(ProxyErrorController.class); - - try { - DispatcherServlet servlet = new DispatcherServlet(applpicationContext); - servlet.init(new ProxyServletConfig(servletContext)); - applpicationContext.registerBean(DispatcherServlet.class, servlet); - - return new ProxyMvc(servlet, applpicationContext); - } - catch (Exception e) { - throw new IllegalStateException("Failed to create MVC Proxy", e); + if (!ObjectUtils.isEmpty(componentClasses)) { + reader.register(componentClasses); } + return INSTANCE(applpicationContext); } /** * Private constructor, not for direct instantiation. */ - ProxyMvc(DispatcherServlet servlet, ConfigurableWebApplicationContext applicationContext) { + ProxyMvc(DispatcherServlet dispatcher, ConfigurableWebApplicationContext applicationContext) { this.applicationContext = applicationContext; - this.servlet = servlet; - this.filters = applicationContext.getBeansOfType(Filter.class).values().toArray(new Filter[0]); + this.dispatcher = dispatcher; } public void stop() { @@ -118,8 +127,23 @@ public class ProxyMvc { * @see org.springframework.test.web.servlet.result.MockMvcResultMatchers */ public void service(HttpServletRequest request, HttpServletResponse response) throws Exception { - ProxyFilterChain filterChain = new ProxyFilterChain(this.servlet, this.filters); + this.service(request, response, (CountDownLatch) null); + } + + public void service(HttpServletRequest request, HttpServletResponse response, CountDownLatch latch) throws Exception { + synchronized (this) { + if (!this.initialized) { + this.dispatcher.init(new ProxyServletConfig(this.servletContext)); + this.initialized = true; + } + } + + ProxyFilterChain filterChain = new ProxyFilterChain(this.dispatcher); filterChain.doFilter(request, response); + + if (latch != null) { + latch.countDown(); + } } private static class ProxyFilterChain implements FilterChain { @@ -143,10 +167,12 @@ public class ProxyMvc { * @param filters the {@link Filter}'s to invoke in this {@link FilterChain} * @since 3.2 */ - ProxyFilterChain(Servlet servlet, Filter... filters) { + ProxyFilterChain(DispatcherServlet servlet) { + List filters = new ArrayList<>(); + servlet.getServletContext().getFilterRegistrations().values().forEach(fr -> filters.add(((ProxyFilterRegistration)fr).getFilter())); Assert.notNull(filters, "filters cannot be null"); Assert.noNullElements(filters, "filters cannot contain null values"); - this.filters = initFilterList(servlet, filters); + this.filters = initFilterList(servlet, filters.toArray(new Filter[] {})); } private static List initFilterList(Servlet servlet, Filter... filters) { @@ -210,24 +236,37 @@ public class ProxyMvc { throws IOException, ServletException { try { - this.delegateServlet.service(request, response); - if (((HttpServletResponse) response).getStatus() != HttpStatus.OK.value()) { - ((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, ((HttpServletResponse) response).getStatus()); + if (((HttpServletResponse) response).getStatus() != HttpStatus.OK.value() && request instanceof ProxyHttpServletRequest) { + ((HttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, ((HttpServletResponse) response).getStatus()); this.setErrorMessageAttribute((ProxyHttpServletRequest) request, (ProxyHttpServletResponse) response, null); - ((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_REQUEST_URI, ((ProxyHttpServletRequest) request).getRequestURI()); + ((HttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_REQUEST_URI, ((HttpServletRequest) request).getRequestURI()); ((ProxyHttpServletRequest) request).setRequestURI("/error"); this.delegateServlet.service(request, response); } + else { + this.delegateServlet.service(request, response); + if (((HttpServletResponse) response).getStatus() != HttpStatus.OK.value() && request instanceof ProxyHttpServletRequest) { + ((HttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, ((HttpServletResponse) response).getStatus()); + this.setErrorMessageAttribute((ProxyHttpServletRequest) request, (ProxyHttpServletResponse) response, null); + ((HttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_REQUEST_URI, ((HttpServletRequest) request).getRequestURI()); + + ((ProxyHttpServletRequest) request).setRequestURI("/error"); + this.delegateServlet.service(request, response); + } + } } catch (Exception e) { - ((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.INTERNAL_SERVER_ERROR); - this.setErrorMessageAttribute((ProxyHttpServletRequest) request, (ProxyHttpServletResponse) response, e); - ((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, e); - ((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_REQUEST_URI, ((ProxyHttpServletRequest) request).getRequestURI()); + if (request instanceof ProxyHttpServletRequest) { + ((HttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.INTERNAL_SERVER_ERROR); + this.setErrorMessageAttribute((ProxyHttpServletRequest) request, (ProxyHttpServletResponse) response, e); + ((HttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, e); + ((HttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_REQUEST_URI, ((HttpServletRequest) request).getRequestURI()); + ((ProxyHttpServletRequest) request).setRequestURI("/error"); + } + + LOG.error("Failed processing the request to: " + ((HttpServletRequest) request).getRequestURI(), e); - LOG.error("Failed processing the request to: " + ((ProxyHttpServletRequest) request).getRequestURI(), e); - ((ProxyHttpServletRequest) request).setRequestURI("/error"); this.delegateServlet.service(request, response); } } @@ -270,7 +309,7 @@ public class ProxyMvc { @Override public String getServletName() { - return "serverless-proxy"; + return "spring-serverless-proxy"; } @Override diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletContext.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletContext.java index e9fa8e4e4..d8e694862 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletContext.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletContext.java @@ -20,12 +20,17 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.Enumeration; import java.util.EventListener; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; +import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterRegistration; import javax.servlet.RequestDispatcher; @@ -200,9 +205,14 @@ public class ProxyServletContext implements ServletContext { throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); } + Map registrations = new HashMap<>(); + @Override public Dynamic addServlet(String servletName, Servlet servlet) { - throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); + + ProxyServletRegistration registration = new ProxyServletRegistration(servletName, servlet, this); + this.registrations.put(servletName, registration); + return registration; } @Override @@ -222,7 +232,7 @@ public class ProxyServletContext implements ServletContext { @Override public ServletRegistration getServletRegistration(String servletName) { - throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); + return this.registrations.get(servletName); } @Override @@ -240,9 +250,18 @@ public class ProxyServletContext implements ServletContext { throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); } + Map filterRegistrations = new HashMap<>(); + @Override public javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, Class filterClass) { - throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); + try { + Filter filter = filterClass.getDeclaredConstructor().newInstance(); + ProxyFilterRegistration registration = new ProxyFilterRegistration(filterName, filter); + filterRegistrations.put(filterName, registration); + return registration; + } catch (Exception e) { + throw new IllegalStateException(e); + } } @Override @@ -252,12 +271,12 @@ public class ProxyServletContext implements ServletContext { @Override public FilterRegistration getFilterRegistration(String filterName) { - throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); + return this.filterRegistrations.get(filterName); } @Override public Map getFilterRegistrations() { - throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); + return this.filterRegistrations; } @Override diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletRegistration.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletRegistration.java new file mode 100644 index 000000000..a7e52669a --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletRegistration.java @@ -0,0 +1,120 @@ +package org.springframework.cloud.function.serverless.web; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletSecurityElement; + +public class ProxyServletRegistration implements ServletRegistration, ServletRegistration.Dynamic, Comparable { + + private final String servletName; + + private final Servlet servlet; + + private final ServletContext servletContext; + + private int loadOnStartup; + + public ProxyServletRegistration(String servletName, Servlet servlet, ServletContext servletContext) { + this.servlet = servlet; + this.servletName = servletName; + this.servletContext = servletContext; + } + + @Override + public String getName() { + return this.servletName; + } + + @Override + public String getClassName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean setInitParameter(String name, String value) { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getInitParameter(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Set setInitParameters(Map initParameters) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map getInitParameters() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) { + // TODO Auto-generated method stub + + } + + @Override + public void setLoadOnStartup(int loadOnStartup) { + this.loadOnStartup = loadOnStartup; + } + + public int getLoadOnStartup() { + return this.loadOnStartup; + } + + @Override + public Set setServletSecurity(ServletSecurityElement constraint) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setMultipartConfig(MultipartConfigElement multipartConfig) { + // TODO Auto-generated method stub + + } + + @Override + public void setRunAsRole(String roleName) { + // TODO Auto-generated method stub + + } + + @Override + public Set addMapping(String... urlPatterns) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Collection getMappings() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getRunAsRole() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int compareTo(ProxyServletRegistration o) { + return Integer.compare(this.loadOnStartup, o.getLoadOnStartup()); + } + +}