Add integration with AWS API latch

This commit is contained in:
Oleg Zhurakousky
2023-03-23 10:38:42 +01:00
parent 04baab62b7
commit e7e808916b
5 changed files with 318 additions and 45 deletions

View File

@@ -27,8 +27,10 @@
<artifactId>spring-webmvc</artifactId> <artifactId>spring-webmvc</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>jakarta.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
<!-- <version>6.0.0</version>-->
<!-- <scope>provided</scope>-->
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@@ -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<String> setInitParameters(Map<String, String> initParameters) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<String, String> getInitParameters() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setAsyncSupported(boolean isAsyncSupported) {
// TODO Auto-generated method stub
}
@Override
public void addMappingForServletNames(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
String... servletNames) {
// TODO Auto-generated method stub
}
@Override
public Collection<String> getServletNameMappings() {
// TODO Auto-generated method stub
return null;
}
@Override
public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
String... urlPatterns) {
// TODO Auto-generated method stub
}
@Override
public Collection<String> getUrlPatternMappings() {
// TODO Auto-generated method stub
return null;
}
}

View File

@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@@ -32,14 +33,14 @@ import javax.servlet.Servlet;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable; 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"); static final String MVC_RESULT_ATTRIBUTE = ProxyMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE");
private final DispatcherServlet servlet; private final DispatcherServlet dispatcher;
private final Filter[] filters;
private final ConfigurableWebApplicationContext applicationContext; private final ConfigurableWebApplicationContext applicationContext;
public static ProxyMvc INSTANCE(Class<?>... componentClasses) { private ServletContext servletContext;
Assert.notEmpty(componentClasses, "'componentClasses' must not be null or empty");
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(); 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); AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(applpicationContext);
reader.register(componentClasses); if (!ObjectUtils.isEmpty(componentClasses)) {
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);
} }
return INSTANCE(applpicationContext);
} }
/** /**
* Private constructor, not for direct instantiation. * Private constructor, not for direct instantiation.
*/ */
ProxyMvc(DispatcherServlet servlet, ConfigurableWebApplicationContext applicationContext) { ProxyMvc(DispatcherServlet dispatcher, ConfigurableWebApplicationContext applicationContext) {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.servlet = servlet; this.dispatcher = dispatcher;
this.filters = applicationContext.getBeansOfType(Filter.class).values().toArray(new Filter[0]);
} }
public void stop() { public void stop() {
@@ -118,8 +127,23 @@ public class ProxyMvc {
* @see org.springframework.test.web.servlet.result.MockMvcResultMatchers * @see org.springframework.test.web.servlet.result.MockMvcResultMatchers
*/ */
public void service(HttpServletRequest request, HttpServletResponse response) throws Exception { 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); filterChain.doFilter(request, response);
if (latch != null) {
latch.countDown();
}
} }
private static class ProxyFilterChain implements FilterChain { 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} * @param filters the {@link Filter}'s to invoke in this {@link FilterChain}
* @since 3.2 * @since 3.2
*/ */
ProxyFilterChain(Servlet servlet, Filter... filters) { ProxyFilterChain(DispatcherServlet servlet) {
List<Filter> filters = new ArrayList<>();
servlet.getServletContext().getFilterRegistrations().values().forEach(fr -> filters.add(((ProxyFilterRegistration)fr).getFilter()));
Assert.notNull(filters, "filters cannot be null"); Assert.notNull(filters, "filters cannot be null");
Assert.noNullElements(filters, "filters cannot contain null values"); Assert.noNullElements(filters, "filters cannot contain null values");
this.filters = initFilterList(servlet, filters); this.filters = initFilterList(servlet, filters.toArray(new Filter[] {}));
} }
private static List<Filter> initFilterList(Servlet servlet, Filter... filters) { private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
@@ -210,24 +236,37 @@ public class ProxyMvc {
throws IOException, ServletException { throws IOException, ServletException {
try { try {
this.delegateServlet.service(request, response); if (((HttpServletResponse) response).getStatus() != HttpStatus.OK.value() && request instanceof ProxyHttpServletRequest) {
if (((HttpServletResponse) response).getStatus() != HttpStatus.OK.value()) { ((HttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, ((HttpServletResponse) response).getStatus());
((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, ((HttpServletResponse) response).getStatus());
this.setErrorMessageAttribute((ProxyHttpServletRequest) request, (ProxyHttpServletResponse) response, null); 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"); ((ProxyHttpServletRequest) request).setRequestURI("/error");
this.delegateServlet.service(request, response); 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) { catch (Exception e) {
((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.INTERNAL_SERVER_ERROR); if (request instanceof ProxyHttpServletRequest) {
this.setErrorMessageAttribute((ProxyHttpServletRequest) request, (ProxyHttpServletResponse) response, e); ((HttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.INTERNAL_SERVER_ERROR);
((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, e); this.setErrorMessageAttribute((ProxyHttpServletRequest) request, (ProxyHttpServletResponse) response, e);
((ProxyHttpServletRequest) request).setAttribute(RequestDispatcher.ERROR_REQUEST_URI, ((ProxyHttpServletRequest) request).getRequestURI()); ((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); this.delegateServlet.service(request, response);
} }
} }
@@ -270,7 +309,7 @@ public class ProxyMvc {
@Override @Override
public String getServletName() { public String getServletName() {
return "serverless-proxy"; return "spring-serverless-proxy";
} }
@Override @Override

View File

@@ -20,12 +20,17 @@ import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.EventListener; import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.DispatcherType;
import javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterRegistration; import javax.servlet.FilterRegistration;
import javax.servlet.RequestDispatcher; 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"); throw new UnsupportedOperationException("This ServletContext does not represent a running web container");
} }
Map<String, ServletRegistration> registrations = new HashMap<>();
@Override @Override
public Dynamic addServlet(String servletName, Servlet servlet) { 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 @Override
@@ -222,7 +232,7 @@ public class ProxyServletContext implements ServletContext {
@Override @Override
public ServletRegistration getServletRegistration(String servletName) { public ServletRegistration getServletRegistration(String servletName) {
throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); return this.registrations.get(servletName);
} }
@Override @Override
@@ -240,9 +250,18 @@ public class ProxyServletContext implements ServletContext {
throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); throw new UnsupportedOperationException("This ServletContext does not represent a running web container");
} }
Map<String, FilterRegistration> filterRegistrations = new HashMap<>();
@Override @Override
public javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass) { public javax.servlet.FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> 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 @Override
@@ -252,12 +271,12 @@ public class ProxyServletContext implements ServletContext {
@Override @Override
public FilterRegistration getFilterRegistration(String filterName) { public FilterRegistration getFilterRegistration(String filterName) {
throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); return this.filterRegistrations.get(filterName);
} }
@Override @Override
public Map<String, ? extends FilterRegistration> getFilterRegistrations() { public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
throw new UnsupportedOperationException("This ServletContext does not represent a running web container"); return this.filterRegistrations;
} }
@Override @Override

View File

@@ -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<ProxyServletRegistration> {
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<String> setInitParameters(Map<String, String> initParameters) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<String, String> 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<String> 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<String> addMapping(String... urlPatterns) {
// TODO Auto-generated method stub
return null;
}
@Override
public Collection<String> 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());
}
}