From 3502d6bbe03436d377ed095a7251c1f14a5fba83 Mon Sep 17 00:00:00 2001 From: Kai Moritz Date: Tue, 21 Jun 2016 12:44:25 +0200 Subject: [PATCH] WIP --- pom.xml | 92 +--- .../ImportVariablesAttrProcessor.java | 145 +----- .../java/de/juplo/thymeleaf/JuploDialect.java | 38 -- .../java/de/juplo/thymeproxy/Application.java | 162 ------- .../thymeproxy/DefaultExceptionHandler.java | 38 -- .../ExceptionResolverErrorController.java | 363 --------------- .../thymeproxy/ProxyHttpRequestHandler.java | 413 ------------------ .../de/juplo/thymeproxy/RegexPathMatcher.java | 124 ------ .../thymeproxy/RegexUrlHandlerMapping.java | 23 - .../RequestToProxyViewNameTranslator.java | 28 -- .../thymeproxy/UrlProxyViewController.java | 28 -- src/main/resources/application.properties | 9 - 12 files changed, 28 insertions(+), 1435 deletions(-) delete mode 100644 src/main/java/de/juplo/thymeleaf/JuploDialect.java delete mode 100644 src/main/java/de/juplo/thymeproxy/Application.java delete mode 100644 src/main/java/de/juplo/thymeproxy/DefaultExceptionHandler.java delete mode 100644 src/main/java/de/juplo/thymeproxy/ExceptionResolverErrorController.java delete mode 100644 src/main/java/de/juplo/thymeproxy/ProxyHttpRequestHandler.java delete mode 100644 src/main/java/de/juplo/thymeproxy/RegexPathMatcher.java delete mode 100644 src/main/java/de/juplo/thymeproxy/RegexUrlHandlerMapping.java delete mode 100644 src/main/java/de/juplo/thymeproxy/RequestToProxyViewNameTranslator.java delete mode 100644 src/main/java/de/juplo/thymeproxy/UrlProxyViewController.java delete mode 100644 src/main/resources/application.properties diff --git a/pom.xml b/pom.xml index 4a571c0..e4725e1 100644 --- a/pom.xml +++ b/pom.xml @@ -2,14 +2,8 @@ 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 1.3.5.RELEASE - - - de.juplo - thymeproxy-starter + de.juplo.jackson + simple-mapping 1.0-SNAPSHOT @@ -23,55 +17,10 @@ 1.8 UTF-8 - - ${project.name} - http://localhost:8080 - 8888 - 300000 - true - ERROR - - - 1.0-SNAPSHOT - 1.0-SNAPSHOT - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - de.juplo - thymeproxy - ${thymeproxy.version} - - - de.juplo - httpclient-spring-boot-starter - ${httpclient-spring-boot-starter.version} - - - - - org.apache.httpcomponents - httpclient - - - - - net.sourceforge.nekohtml - nekohtml - ${nekohtml.version} - - - com.fasterxml.jackson.core jackson-annotations @@ -84,33 +33,32 @@ com.fasterxml.jackson.core jackson-databind + + org.slf4j + slf4j-api + + + ch.qos.logback logback-classic - runtime + test - - - + + + + org.springframework.boot - spring-boot-maven-plugin - - - false - - - - org.springframework - springloaded - 1.2.4.RELEASE - - - - - + spring-boot-dependencies + 1.3.5.RELEASE + pom + import + + + diff --git a/src/main/java/de/juplo/thymeleaf/ImportVariablesAttrProcessor.java b/src/main/java/de/juplo/thymeleaf/ImportVariablesAttrProcessor.java index 63963fa..fe2725e 100644 --- a/src/main/java/de/juplo/thymeleaf/ImportVariablesAttrProcessor.java +++ b/src/main/java/de/juplo/thymeleaf/ImportVariablesAttrProcessor.java @@ -12,24 +12,9 @@ import java.time.format.DateTimeFormatter; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Configurable; -import org.springframework.context.MessageSource; -import org.thymeleaf.Arguments; -import org.thymeleaf.Configuration; -import org.thymeleaf.TemplateProcessingParameters; -import org.thymeleaf.context.IContext; -import org.thymeleaf.context.VariablesMap; -import org.thymeleaf.dom.Element; -import org.thymeleaf.processor.ProcessorResult; -import org.thymeleaf.processor.attr.AbstractAttrProcessor; -import org.thymeleaf.resourceresolver.IResourceResolver; -import org.thymeleaf.templateresolver.ITemplateResolver; -import org.thymeleaf.templateresolver.TemplateResolution; @@ -37,16 +22,8 @@ import org.thymeleaf.templateresolver.TemplateResolution; * * @author kai */ -@Configurable -public class ImportVariablesAttrProcessor extends AbstractAttrProcessor +public class ImportVariablesAttrProcessor { - public static final int ATTR_PRECEDENCE = 200; - public static final String ATTR_VAR_NAME = - JuploDialect.DIALECT_PREFIX + ":var"; - public static final String ATTR_LOCALE_NAME = - JuploDialect.DIALECT_PREFIX + ":locale"; - public static final String DEFAULT_VAR_NAME = "crumb"; - private static final Logger LOG = LoggerFactory.getLogger(ImportVariablesAttrProcessor.class); private static final DateTimeFormatter FORMATTER = @@ -55,65 +32,8 @@ public class ImportVariablesAttrProcessor extends AbstractAttrProcessor private static final JsonFactory FACTORY = new JsonFactory(); - @Autowired - MessageSource messageSource; - @Autowired - Locale defaultLocale; - - - public ImportVariablesAttrProcessor() + public Object processAttribute(InputStream is) { - super("variables"); - } - - - @Override - public final ProcessorResult processAttribute( - final Arguments arguments, - final Element element, - final String name - ) - { - Configuration config = arguments.getConfiguration(); - String templateName = element.getAttributeValue(name); - - TemplateProcessingParameters params = - new TemplateProcessingParameters( - config, - templateName, - new IContext() // << We will not execute the template, hence we need no context - { - @Override - public VariablesMap getVariables() - { - return new VariablesMap<>(); - } - - @Override - public Locale getLocale() - { - return Locale.getDefault(); - } - - @Override - public void addContextExecutionInfo(String templateName) - { - } - }); - - for (ITemplateResolver t_resolver : config.getTemplateResolvers()) - { - TemplateResolution resolution = t_resolver.resolveTemplate(params); - if (resolution == null) - continue; - if (!"JSON".equals(resolution.getTemplateMode())) - continue; - IResourceResolver r_resolver = resolution.getResourceResolver(); - InputStream is = - r_resolver.getResourceAsStream(params, resolution.getResourceName()); - if (is == null) - continue; - try { /** Read the JSON and create the variables */ @@ -123,71 +43,22 @@ public class ImportVariablesAttrProcessor extends AbstractAttrProcessor if (token == null) { - LOG.warn("found empty content for {}", templateName); - break; + LOG.warn("empty input-stream"); + return null; } - if (!JsonToken.START_OBJECT.equals(token)) - { - LOG.error("{} must contain an object as root-element", templateName); - throw new RuntimeException( - "The root-element of " + - templateName + - " has to be an object, that contains the variable-definitions!" - ); - } - - token = parser.nextToken(); - if (token == null) - fail(parser, "unexpected EOF"); - if (JsonToken.END_OBJECT.equals(token)) - { - LOG.warn("found empty object for {}", templateName); - break; - } - - do - { - if (!JsonToken.FIELD_NAME.equals(token)) - fail(parser, "expected a field-name"); - - String var_name = parser.getText(); - parser.nextToken(); - Object var_value = convert(parser); - - LOG.debug( - "defining variable {} of type {}", - var_name, - var_value == null ? "NULL" : var_value.getClass().getSimpleName() - ); - element.setNodeLocalVariable(var_name, var_value); - - token = parser.nextToken(); - if (token == null) - fail(parser, "unexpected EOF"); - } - while (!JsonToken.END_OBJECT.equals(token)); + Object result = convert(parser); if (parser.nextToken() != null) fail(parser, "unexpected data after parsed variables"); + + return result; } catch (IOException e) { - LOG.error("cannot parse {} as JSON: {}", templateName, e.getMessage()); + LOG.error("cannot parse input-stream as JSON: {}", e.getMessage()); throw new RuntimeException(e); } - } - - element.removeAttribute(name); - - return ProcessorResult.OK; - } - - - @Override - public int getPrecedence() - { - return ATTR_PRECEDENCE; } diff --git a/src/main/java/de/juplo/thymeleaf/JuploDialect.java b/src/main/java/de/juplo/thymeleaf/JuploDialect.java deleted file mode 100644 index 6b127f0..0000000 --- a/src/main/java/de/juplo/thymeleaf/JuploDialect.java +++ /dev/null @@ -1,38 +0,0 @@ -package de.juplo.thymeleaf; - - -import java.util.HashSet; -import java.util.Set; -import org.thymeleaf.dialect.AbstractDialect; -import org.thymeleaf.processor.IProcessor; - - -/** - * - * @author kai - */ -public class JuploDialect extends AbstractDialect -{ - public static final String DIALECT_PREFIX = "juplo"; - - - public JuploDialect() - { - super(); - } - - - @Override - public String getPrefix() - { - return DIALECT_PREFIX; - } - - @Override - public Set getProcessors() - { - final Set processors = new HashSet<>(); - processors.add(new ImportVariablesAttrProcessor()); - return processors; - } -} diff --git a/src/main/java/de/juplo/thymeproxy/Application.java b/src/main/java/de/juplo/thymeproxy/Application.java deleted file mode 100644 index 6a3d7da..0000000 --- a/src/main/java/de/juplo/thymeproxy/Application.java +++ /dev/null @@ -1,162 +0,0 @@ -package de.juplo.thymeproxy; - -import de.juplo.thymeleaf.JuploDialect; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import org.apache.http.impl.client.CloseableHttpClient; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.web.ErrorAttributes; -import org.springframework.boot.autoconfigure.web.ErrorController; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.core.Ordered; -import org.springframework.core.env.Environment; -import org.springframework.http.HttpStatus; -import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; -import org.thymeleaf.TemplateEngine; -import org.thymeleaf.resourceresolver.IResourceResolver; -import org.thymeleaf.resourceresolver.UrlResourceResolver; -import org.thymeleaf.templateresolver.TemplateResolver; - - -@SpringBootApplication -public class Application extends WebMvcConfigurerAdapter -{ - @Autowired - private ServerProperties properties; - - - @Bean - public RegexUrlHandlerMapping proxiedHandlerMapping( - ProxyHttpRequestHandler proxy, - UrlThymeleafViewController views - ) - { - RegexUrlHandlerMapping mapping = new RegexUrlHandlerMapping(); - mapping.setOrder(Ordered.HIGHEST_PRECEDENCE); - Map mappings = new HashMap<>(); - mappings.put("/img/.+", proxy); - mappings.put("/css/.+", proxy); - mappings.put("/js/.+", proxy); - mappings.put("/fonts/.+", proxy); - mappings.put("/.*\\.html", views); - mapping.setUrlMap(mappings); - return mapping; - } - - @Bean - public ProxyHttpRequestHandler proxyHttpRequestHandler( - CloseableHttpClient client, - Environment env - ) - { - ProxyHttpRequestHandler handler = new ProxyHttpRequestHandler(); - handler.setClient(client); - handler.setOrigin(env.getProperty("thymeproxy.origin")); - handler.setTtl(30000l); - return handler; - } - - @Bean - public UrlThymeleafViewController urlThymeleafViewController( - TemplateEngine engine - ) - { - return new UrlThymeleafViewController(engine); - } - - @Bean - public TemplateResolver defaultTemplateResolver( - IResourceResolver resources, - Environment env - ) - { - TemplateResolver resolver = new TemplateResolver(); - resolver.setResourceResolver(resources); - resolver.setPrefix("http://localhost:8080/thymeleaf/"); - resolver.setSuffix(".html"); - resolver.setTemplateMode("HTML5"); - resolver.setCharacterEncoding("UTF-8"); - resolver.setCacheable(Boolean.valueOf(env.getProperty("thymeproxy.cacheable"))); - return resolver; - } - - @Bean - public TemplateResolver jsonTemplateResolver( - IResourceResolver resources, - Environment env - ) - { - TemplateResolver resolver = new TemplateResolver(); - resolver.setResourceResolver(resources); - resolver.setPrefix("http://localhost:8080/thymeleaf/"); - resolver.setSuffix(".json"); - resolver.setTemplateMode("JSON"); - resolver.setCharacterEncoding("UTF-8"); - resolver.setCacheable(Boolean.valueOf(env.getProperty("thymeproxy.cacheable"))); - return resolver; - } - - @Bean - public UrlResourceResolver thymeleafResourceResolver() - { - return new UrlResourceResolver(); - } - - @Bean - public JuploDialect juploDialect() - { - return new JuploDialect(); - } - - @Bean - public SimpleMappingExceptionResolver simpleMappingExceptionResolver() - { - SimpleMappingExceptionResolver resolver = - new SimpleMappingExceptionResolver(); - - Properties mappings = new Properties(); - mappings.setProperty("TemplateInputException", "templates/404"); - - resolver.setExceptionMappings(mappings); - resolver.setDefaultErrorView("templates/error"); - resolver.setWarnLogCategory("exception"); - return resolver; - } - - @Bean - public ErrorController errorController( - ApplicationContext context, - ErrorAttributes errorAttributes - ) - { - ExceptionResolverErrorController controller = - new ExceptionResolverErrorController( - context, - errorAttributes, - properties.getError() - ); - controller.addErrorMapping(HttpStatus.NOT_FOUND, "templates/404"); - controller.setDefaultErrorView("templates/error"); - return controller; - } - - - @Override - public void addViewControllers(ViewControllerRegistry registry) - { - registry.addViewController("/").setViewName("forward:index.html"); - } - - - public static void main(String[] args) - { - SpringApplication.run(Application.class, args); - } -} \ No newline at end of file diff --git a/src/main/java/de/juplo/thymeproxy/DefaultExceptionHandler.java b/src/main/java/de/juplo/thymeproxy/DefaultExceptionHandler.java deleted file mode 100644 index 7aaf512..0000000 --- a/src/main/java/de/juplo/thymeproxy/DefaultExceptionHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -package de.juplo.thymeproxy; - - -import javax.servlet.http.HttpServletRequest; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.servlet.ModelAndView; -import org.thymeleaf.exceptions.TemplateInputException; - - - -/** - * - * @author Kai Moritz - */ -@ControllerAdvice -public class DefaultExceptionHandler -{ - public DefaultExceptionHandler() - { - } - - - @ResponseStatus(HttpStatus.NOT_FOUND) - @ExceptionHandler(value = TemplateInputException.class) - public ModelAndView templateInputExceptionHandler( - HttpServletRequest request, - TemplateInputException e - ) - { - ModelAndView mav = new ModelAndView("templates/404"); - mav.addObject("template", e.getTemplateName()); - mav.addObject("uri", request.getRequestURI()); - return mav; - } -} diff --git a/src/main/java/de/juplo/thymeproxy/ExceptionResolverErrorController.java b/src/main/java/de/juplo/thymeproxy/ExceptionResolverErrorController.java deleted file mode 100644 index 7339ddf..0000000 --- a/src/main/java/de/juplo/thymeproxy/ExceptionResolverErrorController.java +++ /dev/null @@ -1,363 +0,0 @@ -package de.juplo.thymeproxy; - - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.BeanInitializationException; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.boot.autoconfigure.web.BasicErrorController; -import org.springframework.boot.autoconfigure.web.ErrorAttributes; -import org.springframework.boot.autoconfigure.web.ErrorProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.support.PropertiesLoaderUtils; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.DispatcherServlet; -import static org.springframework.web.servlet.DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME; -import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.servlet.ModelAndView; - - - -/** - * - * @author kai - */ -public class ExceptionResolverErrorController extends BasicErrorController -{ - private final static Logger LOG = - LoggerFactory.getLogger(ExceptionResolverErrorController.class); - - public final static String EXCEPTION_ATTRIBUTE = - "javax.servlet.error.exception"; - - - /** - * Name of the class path resource (relative to the DispatcherServlet class) - * that defines DispatcherServlet's default strategy names. - */ - private static final String DEFAULT_STRATEGIES_PATH = - "DispatcherServlet.properties"; - - private static final Properties DEFAULT_STRATEGIES; - static - { - // Load default strategy implementations from properties file. - // This is currently strictly internal and not meant to be customized - // by application developers. - try - { - ClassPathResource resource = - new ClassPathResource( - DEFAULT_STRATEGIES_PATH, - DispatcherServlet.class - ); - DEFAULT_STRATEGIES = PropertiesLoaderUtils.loadProperties(resource); - } - catch (IOException ex) - { - throw new IllegalStateException( - "Could not load 'DispatcherServlet.properties': " + - ex.getMessage() - ); - } - } - - - /** Name of the default-error-view */ - private String defaultErrorView = "error"; - - /** Mapping from HTTP-status-codes to specialized error-pages */ - private Map errorMappings = new HashMap<>(); - - /** List of HandlerExceptionResolvers used by this servlet */ - private List handlerExceptionResolvers; - - /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */ - private boolean detectAllHandlerExceptionResolvers = true; - - - ExceptionResolverErrorController( - ApplicationContext context, - ErrorAttributes errorAttributes, - ErrorProperties errorProperties - ) - { - super(errorAttributes, errorProperties); - initHandlerExceptionResolvers(context); - } - - - @Override - public ModelAndView errorHtml( - HttpServletRequest request, - HttpServletResponse response - ) - { - Map model = - getErrorAttributes( - request, - isIncludeStackTrace(request, MediaType.TEXT_HTML) - ); - - ModelAndView view = null; - - Exception e = (Exception)request.getAttribute(EXCEPTION_ATTRIBUTE); - if (e != null) - { - if (e instanceof ServletException ) - { - ServletException n = (ServletException)e; - Throwable t = n.getRootCause(); - if (t != null && t instanceof Exception) - e = (Exception)t; - } - - for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) - { - view = resolver.resolveException(request, response, null, e); - if (view != null) - break; - } - - if (view != null) - { - view.addAllObjects(model); - return view; - } - } - - String viewName = null; - Integer code = (Integer)model.get("status"); - try - { - HttpStatus status = HttpStatus.valueOf(code); - viewName = errorMappings.get(status); - } - catch(Throwable t) - { - LOG.warn("cannot map status-code {}: {}", code, t.getMessage()); - } - if (viewName == null) - viewName = defaultErrorView; - return new ModelAndView(viewName, model); - } - - - /** - * Initialize the HandlerExceptionResolver used by this class. - *

- * If no bean is defined with the given name in the BeanFactory for this - * namespace, - * we default to no exception resolver. - */ - private void initHandlerExceptionResolvers(ApplicationContext context) - { - this.handlerExceptionResolvers = null; - - if (this.detectAllHandlerExceptionResolvers) - { - // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. - Map matchingBeans = - BeanFactoryUtils.beansOfTypeIncludingAncestors( - context, - HandlerExceptionResolver.class, - true, - false - ); - if (!matchingBeans.isEmpty()) - { - this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values()); - // We keep HandlerExceptionResolvers in sorted order. - AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); - } - } - else - { - try - { - HandlerExceptionResolver her = - context.getBean( - HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, - HandlerExceptionResolver.class - ); - this.handlerExceptionResolvers = Collections.singletonList(her); - } - catch (NoSuchBeanDefinitionException e) - { - // Ignore, no HandlerExceptionResolver is fine too. - } - } - - // Ensure we have at least some HandlerExceptionResolvers, by registering - // default HandlerExceptionResolvers if no other resolvers are found. - if (this.handlerExceptionResolvers == null) - { - this.handlerExceptionResolvers = - getDefaultStrategies(context, HandlerExceptionResolver.class); - LOG.debug("No HandlerExceptionResolvers found: using default"); - } - } - - /** - * Create a List of default strategy objects for the given strategy interface. - *

- * The default implementation uses the "DispatcherServlet.properties" file (in - * the same - * package as the DispatcherServlet class) to determine the class names. It - * instantiates - * the strategy objects through the context's BeanFactory. - * - * @param context the current WebApplicationContext - * @param strategyInterface the strategy interface - * @return the List of corresponding strategy objects - */ - @SuppressWarnings("unchecked") - protected List getDefaultStrategies( - ApplicationContext context, - Class strategyInterface - ) - { - String key = strategyInterface.getName(); - String value = DEFAULT_STRATEGIES.getProperty(key); - if (value != null) - { - String[] classNames = StringUtils.commaDelimitedListToStringArray(value); - List strategies = new ArrayList<>(classNames.length); - for (String className : classNames) - { - try - { - Class clazz = - ClassUtils.forName( - className, - DispatcherServlet.class.getClassLoader() - ); - Object strategy = createDefaultStrategy(context, clazz); - strategies.add((T) strategy); - } - catch (ClassNotFoundException ex) - { - throw new BeanInitializationException( - "Could not find DispatcherServlet's default strategy class [" + - className + "] for interface [" + key + "]", - ex - ); - } - catch (LinkageError err) - { - throw new BeanInitializationException( - "Error loading DispatcherServlet's default strategy class [" + - className + "] for interface [" + key + - "]: problem with class file or dependent class", - err - ); - } - } - return strategies; - } - else - { - return new LinkedList<>(); - } - } - - /** - * Create a default strategy. - *

- * The default implementation uses - * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}. - * - * @param context the current WebApplicationContext - * @param clazz the strategy implementation class to instantiate - * @return the fully configured strategy instance - * @see - * org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory() - * @see - * org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean - */ - protected Object createDefaultStrategy( - ApplicationContext context, - Class clazz - ) - { - return context.getAutowireCapableBeanFactory().createBean(clazz); - } - - - /** - * @ - * @see #addErrorMapping(HttpStatus, String) - */ - public String addErrorMapping(Integer status, String viewName) - { - if (status == null) - throw new IllegalArgumentException("The status must not be null"); - return addErrorMapping(HttpStatus.valueOf(status), viewName); - } - - /** - * Adds a mapping from a {@link HttpStatus} to a view. - * - * @param status The {@link HttpStatus}, that should be mapped. - * @param viewName The name of the view, the status should be mapped to. - * @return The name of the view, the status was previously mapped to, or - * null, if the status was not mapped before. - */ - public String addErrorMapping(HttpStatus status, String viewName) - { - if (!StringUtils.hasText(viewName)) - throw new IllegalArgumentException("The view-name must not be empty!"); - if (status == null) - throw new IllegalArgumentException("The status must not be null!"); - return errorMappings.put(status, viewName); - } - - /** - * Sets mappings from {@link HttpStatus} to specialized error-views. - * @param mappings The mappings to set. - */ - public void setErrorMappings(Map mappings) - { - errorMappings = mappings; - } - - /** - * Sets the default error-view for not-mapped status-codes. - * @param view The default error-view to set. - */ - public void setDefaultErrorView(String view) - { - defaultErrorView = view; - } - - /** - * Set whether to detect all HandlerExceptionResolver beans in this servlet's - * context. Otherwise, - * just a single bean with name "handlerExceptionResolver" will be expected. - *

- * Default is "true". Turn this off if you want this servlet to use a single - * HandlerExceptionResolver, despite multiple HandlerExceptionResolver beans - * being defined in the context. - */ - public void setDetectAllHandlerExceptionResolvers(boolean resolvers) - { - detectAllHandlerExceptionResolvers = resolvers; - } -} diff --git a/src/main/java/de/juplo/thymeproxy/ProxyHttpRequestHandler.java b/src/main/java/de/juplo/thymeproxy/ProxyHttpRequestHandler.java deleted file mode 100644 index 8c52b26..0000000 --- a/src/main/java/de/juplo/thymeproxy/ProxyHttpRequestHandler.java +++ /dev/null @@ -1,413 +0,0 @@ -package de.juplo.thymeproxy; - - -import java.io.IOException; -import java.time.Clock; -import java.time.Duration; -import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.concurrent.TimeUnit; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.http.Header; -import org.apache.http.HeaderElement; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.util.EntityUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.CacheControl; -import org.springframework.util.Assert; -import org.springframework.util.StreamUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.HttpRequestHandler; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.support.WebContentGenerator; -import org.springframework.web.util.UrlPathHelper; - - - -/** - * - * @author kai - */ -public class ProxyHttpRequestHandler - extends - WebContentGenerator - implements - HttpRequestHandler -{ - private final static Logger LOG = - LoggerFactory.getLogger(ProxyHttpRequestHandler.class); - - - public final static Long DEFAULT_TTL = 300000l; /** 5 minutes */ - - - private UrlPathHelper urlPathHelper = new UrlPathHelper(); - - private CloseableHttpClient client; - private String origin; - private Long ttl; - - private final Clock clock; - - - public ProxyHttpRequestHandler() - { - clock = Clock.systemDefaultZone(); - } - - public ProxyHttpRequestHandler(Clock clock) - { - this.clock = clock; - } - - - @Override - public void handleRequest( - HttpServletRequest request, - HttpServletResponse response - ) - throws - ServletException, - IOException - { - LOG.debug("handling: {}", request.getRequestURI()); - - String path = - (String) - request.getAttribute( - HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE - ); - - if (!StringUtils.hasText(path)) - path = urlPathHelper.getLookupPathForRequest(request); - - StringBuilder builder = new StringBuilder(); - builder.append(origin); - builder.append(path); - - String query = request.getQueryString(); - if (query != null) - { - builder.append('?'); - builder.append(query); - } - - String resource = builder.toString(); - - CloseableHttpResponse p_response = null; - try - { - HttpGet p_request = new HttpGet(resource); - - p_response = client.execute(p_request); - - StatusLine status = p_response.getStatusLine(); - int code = status.getStatusCode(); - LOG.info("{} - {}: {}", code, status.getReasonPhrase(), resource); - - setCacheControl(computeCacheControl(p_response)); - prepareResponse(response); - - /** Copy some headers, if present.. */ - ProxyHttpRequestHandler.copyHeader(p_response, response, "Date"); - ProxyHttpRequestHandler.copyHeader(p_response, response, "ETag"); - ProxyHttpRequestHandler.copyHeader(p_response, response, "Last-Modified"); - ProxyHttpRequestHandler.copyHeader(p_response, response, "Content-Length"); - ProxyHttpRequestHandler.copyHeader(p_response, response, "Content-Type"); - - switch (code) - { - case HttpServletResponse.SC_FOUND: - case HttpServletResponse.SC_OK: - /** OK. Continue as normal... */ - response.setStatus(code); - - HttpEntity entity = p_response.getEntity(); - - StreamUtils.copy(entity.getContent(), response.getOutputStream()); - - /** Release the connection */ - EntityUtils.consume(entity); - p_response.close(); - - return; - - case HttpServletResponse.SC_NOT_FOUND: - case HttpServletResponse.SC_GONE: - /** The resource can not be resolved through this origin */ - response.sendError(code, status.getReasonPhrase()); - return; - - case HttpServletResponse.SC_MOVED_PERMANENTLY: - // TODO - - case HttpServletResponse.SC_SEE_OTHER: - // TODO - - case HttpServletResponse.SC_TEMPORARY_REDIRECT: - // TODO - - default: - LOG.error("{} -- {}", p_response.getStatusLine(), resource); - // TODO: throw sensible exceptions, to communicate resolving-errors - throw new RuntimeException(p_response.getStatusLine().toString() + " -- " + resource); - } - } - catch (IOException e) - { - LOG.error("unexpected error while resolving {}: {}", resource, e.getMessage()); - // TODO: throw sensible exceptions, to communicate resolving-errors - throw new RuntimeException(e); - } - finally - { - if (p_response != null) - { - EntityUtils.consumeQuietly(p_response.getEntity()); - p_response.close(); - } - } - } - - public static void copyHeader( - HttpResponse source, - HttpServletResponse target, - String name - ) - { - Header header = source.getLastHeader(name); - if (header != null) - { - LOG.trace("copy header {}: {}", header.getName(), header.getValue()); - target.addHeader(header.getName(), header.getValue()); - } - } - - public CacheControl computeCacheControl(HttpResponse response) - { - if (ttl == null) - /** Caching is disabled! */ - return CacheControl.noStore(); - - - boolean has_cache_control = false; - boolean is_public = false; - boolean is_private = false; - boolean no_cache = false; - boolean no_store = false; - boolean no_transform = false; - boolean must_revalidate = false; - boolean proxy_revalidate = false; - Long max_age = null; - Long s_maxage = null; - - for (Header header : response.getHeaders("Cache-Control")) - { - has_cache_control = true; - for (HeaderElement element : header.getElements()) - { - switch (element.getName()) - { - case "public": - is_public = true; - break; - case "private": - is_private = true; - break; - case "no-cache": - no_cache = true; - break; - case "no-store": - no_store = true; - break; - case "no_transform": - no_transform = true; - break; - case "must-revalidate": - must_revalidate = true; - break; - case "proxy-revalidate": - proxy_revalidate = true; - case "max-age": - try - { - max_age = Long.parseLong(element.getValue()); - } - catch (NumberFormatException e) - { - LOG.warn( - "invalid header \"Cache-Control: max-age={}\"", - element.getValue() - ); - } - break; - case "s-maxage": - try - { - s_maxage = Long.parseLong(element.getValue()); - } - catch (NumberFormatException e) - { - LOG.warn( - "invalid header \"Cache-Control: s-maxage={}\"", - element.getValue() - ); - } - break; - default: - LOG.warn( - "invalid header \"Cache-Control: {}{}\"", - element.getName(), - element.getValue() == null ? "" : "=" + element.getValue() - ); - } - } - } - - if (!has_cache_control) - { - Header header = response.getLastHeader("Expires"); - if (header == null) - /** No TTL specified in response-headers: use configured default */ - max_age = ttl; - else - try - { - OffsetDateTime expires - = OffsetDateTime.parse( - header.getValue(), - DateTimeFormatter.RFC_1123_DATE_TIME - ); - - Duration delta = Duration.between(OffsetDateTime.now(clock), expires); - if (delta.isNegative()) - no_store = true; - else - max_age = delta.getSeconds(); - } - catch (DateTimeParseException e) - { - LOG.warn("invalid header \"Expires: {}\"", header.getValue()); - /** - * No TTL specified in response-headers: assume expired - * see: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21 - */ - max_age = 0l; - } - } - - CacheControl cache_control; - - if (no_store) - cache_control = CacheControl.noStore(); - else if (no_cache) - cache_control = CacheControl.noCache(); - else if (max_age != null) - cache_control = CacheControl.maxAge(max_age, TimeUnit.SECONDS); - else - cache_control = CacheControl.empty(); - - if (is_private) - cache_control.cachePrivate(); - if (is_public) - cache_control.cachePublic(); - if (no_transform) - cache_control.noTransform(); - if (must_revalidate) - cache_control.mustRevalidate(); - if (proxy_revalidate) - cache_control.proxyRevalidate(); - if (s_maxage != null) - cache_control.sMaxAge(s_maxage, TimeUnit.SECONDS); - - return cache_control; - } - - - public ProxyHttpRequestHandler setClient(CloseableHttpClient client) - { - this.client = client; - return this; - } - - public ProxyHttpRequestHandler setOrigin(String origin) - { - this.origin = origin; - return this; - } - - public ProxyHttpRequestHandler setTtl(Long ttl) - { - this.ttl = ttl; - return this; - } - - /** - * Set if URL lookup should always use full path within current servlet - * context. Else, the path within the current servlet mapping is used - * if applicable (i.e. in the case of a ".../*" servlet mapping in web.xml). - * Default is "false". - * - * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath - */ - public ProxyHttpRequestHandler setAlwaysUseFullPath(boolean alwaysUseFullPath) - { - this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath); - return this; - } - - /** - * Set if context path and request URI should be URL-decoded. - * Both are returned undecoded by the Servlet API, - * in contrast to the servlet path. - *

- * Uses either the request encoding or the default encoding according - * to the Servlet spec (ISO-8859-1). - * - * @see org.springframework.web.util.UrlPathHelper#setUrlDecode - */ - public ProxyHttpRequestHandler setUrlDecode(boolean urlDecode) - { - this.urlPathHelper.setUrlDecode(urlDecode); - return this; - } - - /** - * Set if ";" (semicolon) content should be stripped from the request URI. - * - * @see - * org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean) - */ - public ProxyHttpRequestHandler setRemoveSemicolonContent(boolean removeSemicolonContent) - { - this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent); - return this; - } - - /** - * Set the UrlPathHelper to use for the resolution of lookup paths. - *

- * Use this to override the default UrlPathHelper with a custom subclass, - * or to share common UrlPathHelper settings across multiple - * MethodNameResolvers - * and HandlerMappings. - * - * @see - * org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#setUrlPathHelper - */ - public ProxyHttpRequestHandler setUrlPathHelper(UrlPathHelper urlPathHelper) - { - Assert.notNull(urlPathHelper, "UrlPathHelper must not be null"); - this.urlPathHelper = urlPathHelper; - return this; - } -} diff --git a/src/main/java/de/juplo/thymeproxy/RegexPathMatcher.java b/src/main/java/de/juplo/thymeproxy/RegexPathMatcher.java deleted file mode 100644 index 7ee56d3..0000000 --- a/src/main/java/de/juplo/thymeproxy/RegexPathMatcher.java +++ /dev/null @@ -1,124 +0,0 @@ -package de.juplo.thymeproxy; - - -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.PathMatcher; - - - -/** - * - * @author kai - */ -public class RegexPathMatcher implements PathMatcher -{ - private final static Logger LOG = - LoggerFactory.getLogger(RegexPathMatcher.class); - - - private int flags = 0; - private Map patternCache = new HashMap<>(); - - - public RegexPathMatcher() - { - } - - public RegexPathMatcher(int flags) - { - this.flags = flags; - } - - - protected boolean compilePattern(String string) - { - try - { - Pattern pattern = Pattern.compile(string, flags); - patternCache.put(string, pattern); - return true; - } - catch(PatternSyntaxException e) - { - LOG.debug("\"{}\" is no valid pattern: {}", string, e.getMessage()); - return false; - } - } - - - @Override - public boolean isPattern(String path) - { - if (path.startsWith("^") || path.endsWith("$")) - return false; - - if (patternCache.containsKey(path)) - return true; - - return compilePattern(path); - } - - @Override - public boolean match(String pattern, String path) - { - if (!patternCache.containsKey(pattern)) - compilePattern(pattern); - Matcher matcher = patternCache.get(pattern).matcher(path); - return matcher.matches(); - } - - @Override - public boolean matchStart(String pattern, String path) - { - if (!patternCache.containsKey(pattern)) - compilePattern(pattern); - Matcher matcher = patternCache.get(pattern).matcher(path); - if (!matcher.find()) - return false; - else - return matcher.start() == 0; - } - - @Override - public String extractPathWithinPattern(String pattern, String path) - { - return ""; - } - - @Override - public Map extractUriTemplateVariables(String pattern, String path) - { - return new HashMap<>(); - } - - @Override - public Comparator getPatternComparator(String path) - { - return new Comparator() - { - @Override - public int compare(String a, String b) - { - return b.length() - a.length(); - } - }; - } - - @Override - public String combine(String a, String b) - { - boolean literal = (flags & Pattern.LITERAL) == Pattern.LITERAL; - String pattern = a + (literal ? "" : ".*") + b; - if (!isPattern(pattern)) - throw new IllegalArgumentException("Cannot combine pattern " + a + " with pattern " + b); - return pattern; - } - -} diff --git a/src/main/java/de/juplo/thymeproxy/RegexUrlHandlerMapping.java b/src/main/java/de/juplo/thymeproxy/RegexUrlHandlerMapping.java deleted file mode 100644 index 9c27b43..0000000 --- a/src/main/java/de/juplo/thymeproxy/RegexUrlHandlerMapping.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.juplo.thymeproxy; - - -import org.springframework.util.PathMatcher; -import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; - - - -/** - * - * @author kai - */ -public class RegexUrlHandlerMapping extends SimpleUrlHandlerMapping -{ - private final RegexPathMatcher matcher = new RegexPathMatcher(); - - - @Override - public PathMatcher getPathMatcher() - { - return matcher; - } -} diff --git a/src/main/java/de/juplo/thymeproxy/RequestToProxyViewNameTranslator.java b/src/main/java/de/juplo/thymeproxy/RequestToProxyViewNameTranslator.java deleted file mode 100644 index 53c83fb..0000000 --- a/src/main/java/de/juplo/thymeproxy/RequestToProxyViewNameTranslator.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.juplo.thymeproxy; - - -import javax.servlet.http.HttpServletRequest; -import org.springframework.web.servlet.RequestToViewNameTranslator; - - - -/** - * - * @author kai - */ -public class RequestToProxyViewNameTranslator implements RequestToViewNameTranslator -{ - @Override - public String getViewName(HttpServletRequest request) throws Exception - { - StringBuilder builder = new StringBuilder(); - builder.append(request.getRequestURI()); - String query = request.getQueryString(); - if (query != null) - { - builder.append('?'); - builder.append(query); - } - return builder.toString(); - } -} diff --git a/src/main/java/de/juplo/thymeproxy/UrlProxyViewController.java b/src/main/java/de/juplo/thymeproxy/UrlProxyViewController.java deleted file mode 100644 index 23ef9e3..0000000 --- a/src/main/java/de/juplo/thymeproxy/UrlProxyViewController.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.juplo.thymeproxy; - - -import javax.servlet.http.HttpServletRequest; -import org.springframework.web.servlet.mvc.AbstractUrlViewController; - - - -/** - * - * @author kai - */ -public class UrlProxyViewController extends AbstractUrlViewController -{ - @Override - protected String getViewNameForRequest(HttpServletRequest request) - { - StringBuilder builder = new StringBuilder(); - builder.append(request.getRequestURI()); - String query = request.getQueryString(); - if (query != null) - { - builder.append('?'); - builder.append(query); - } - return builder.toString(); - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 1ba166f..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,9 +0,0 @@ -thymeproxy.name=@thymeproxy.name@ -thymeproxy.origin=@thymeproxy.origin@ -server.port=@thymeproxy.port@ -thymeproxy.ttl=@thymeproxy.ttl@ -thymeproxy.cacheable=@thymeproxy.cacheable@ - -logging.level.de.juplo=info -logging.level.org.apache.http=@httpclient.logging.level@ -logging.level.org.apache.http.wire=ERROR -- 2.20.1