<modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.3.5.RELEASE</version>
- </parent>
-
- <groupId>de.juplo</groupId>
- <artifactId>thymeproxy-starter</artifactId>
+ <groupId>de.juplo.jackson</groupId>
+ <artifactId>simple-mapping</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <!-- application-settings -->
- <thymeproxy.name>${project.name}</thymeproxy.name>
- <thymeproxy.origin>http://localhost:8080</thymeproxy.origin>
- <thymeproxy.port>8888</thymeproxy.port>
- <thymeproxy.ttl>300000</thymeproxy.ttl><!-- 5 minutes -->
- <thymeproxy.cacheable>true</thymeproxy.cacheable>
- <httpclient.logging.level>ERROR</httpclient.logging.level>
-
- <!-- used versions (not defined in spring-boot) -->
- <httpclient-spring-boot-starter.version>1.0-SNAPSHOT</httpclient-spring-boot-starter.version>
- <thymeproxy.version>1.0-SNAPSHOT</thymeproxy.version>
-
</properties>
<dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-thymeleaf</artifactId>
- </dependency>
- <dependency>
- <groupId>de.juplo</groupId>
- <artifactId>thymeproxy</artifactId>
- <version>${thymeproxy.version}</version>
- </dependency>
- <dependency>
- <groupId>de.juplo</groupId>
- <artifactId>httpclient-spring-boot-starter</artifactId>
- <version>${httpclient-spring-boot-starter.version}</version>
- </dependency>
-
- <!-- Httpclient -->
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- </dependency>
-
- <!-- Needed to parse HTML5 with Thymeleaf -->
- <dependency>
- <groupId>net.sourceforge.nekohtml</groupId>
- <artifactId>nekohtml</artifactId>
- <version>${nekohtml.version}</version>
- </dependency>
-
- <!-- Needed to parse JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <!-- Testing -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
- <scope>runtime</scope>
+ <scope>test</scope>
</dependency>
</dependencies>
- <build>
- <plugins>
- <plugin>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <!-- Needed, because otherwise, filtering of maven properties won't work -->
- <configuration>
- <addResources>false</addResources>
- </configuration>
- <dependencies>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>springloaded</artifactId>
- <version>1.2.4.RELEASE</version>
- </dependency>
- </dependencies>
- </plugin>
- </plugins>
- </build>
+ <artifactId>spring-boot-dependencies</artifactId>
+ <version>1.3.5.RELEASE</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
</project>
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;
*
* @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 =
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<String, Object> 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 */
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;
}
+++ /dev/null
-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<IProcessor> getProcessors()
- {
- final Set<IProcessor> processors = new HashSet<>();
- processors.add(new ImportVariablesAttrProcessor());
- return processors;
- }
-}
+++ /dev/null
-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<String, Object> 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
+++ /dev/null
-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;
- }
-}
+++ /dev/null
-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<HttpStatus, String> errorMappings = new HashMap<>();
-
- /** List of HandlerExceptionResolvers used by this servlet */
- private List<HandlerExceptionResolver> 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<String, Object> 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.
- * <p>
- * 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<String, HandlerExceptionResolver> 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.
- * <p>
- * 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 <T> List<T> getDefaultStrategies(
- ApplicationContext context,
- Class<T> strategyInterface
- )
- {
- String key = strategyInterface.getName();
- String value = DEFAULT_STRATEGIES.getProperty(key);
- if (value != null)
- {
- String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
- List<T> 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.
- * <p>
- * 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
- * <code>null</code>, 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<HttpStatus, String> 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.
- * <p>
- * 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;
- }
-}
+++ /dev/null
-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 <i>undecoded</i> by the Servlet API,
- * in contrast to the servlet path.
- * <p>
- * 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.
- * <p>
- * 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;
- }
-}
+++ /dev/null
-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<String, Pattern> 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<String, String> extractUriTemplateVariables(String pattern, String path)
- {
- return new HashMap<>();
- }
-
- @Override
- public Comparator<String> getPatternComparator(String path)
- {
- return new Comparator<String>()
- {
- @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;
- }
-
-}
+++ /dev/null
-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;
- }
-}
+++ /dev/null
-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();
- }
-}
+++ /dev/null
-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();
- }
-}
+++ /dev/null
-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