2017-04-24 63 views
0

我一直在一个相当荒谬的问题挣扎了几天: 我正在使用Spring MVC与FreeMarker的模板。春天和SiteMesh错误页面没有装饰(跳过主要过滤器)

这是在Tomcat容器上运行(使用Cargo进行本地测试)。

我正在工作的问题具有在标准化错误页面中实现统一行为的简要内容,但涵盖了可能遇到的各种类型的错误。 (例外的后端服务,权限不足,HTTP错误,等冒泡)

到目前为止,结果如下(包括图形): Examples of 3 Successful error displays(basic request and intercepted exceptions, against failing example (HTTP errors 4xx, 5xx, etc)

  • 图一:普通的导航页 - 按预期呈现。
  • 图B &图C:ControllerAdvice.java捕获的服务和权限异常 - 同样没有问题。图D:任何HTTP错误(是的,如果触发该响应,甚至是418) - 正确检索内部Freemarker模板并使用绑定填充,但过滤器应用的装饰无法触发。

目前我们正在使用Spring配置的servlet处理,从而在web.xml精美疏:

的web.xml

<?xml version="1.0" encoding="UTF-8"?> 
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
     http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 
    version="3.1"> 

<!-- 
This application uses the config of the mapping by Spring MVC 
This is why you will not see servlet declarations here 

The web app is defined in 
- butler.SpringWebInit 
- butler.SpringWebConfig 
--> 

    <context-param> 
     <description>Escape HTML form data by default when using Spring tags</description> 
     <param-name>defaultHtmlEscape</param-name> 
     <param-value>true</param-value> 
    </context-param> 

<!-- Disabling welcome list file for Tomcat, handling it in Spring MVC --> 
    <welcome-file-list> 
     <welcome-file/> 
    </welcome-file-list> 

<!-- Generic Error redirection, allows for handling in Spring MVC --> 
    <error-page> 
     <location>/http-error</location> 
     <!-- Was originally just "/error" it was changed for internal forwarding/proxying/redirection attempts --> 
    </error-page> 
</web-app> 

配置由SpringWebInit.java处理我没有做任何修改:

SpringWebInit.java

/** 
* Automatically loaded by class org.springframework.web.SpringServletContainerInitializer 
* 
* @see http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-container-config 
* 
*  According to {@link AbstractSecurityWebApplicationInitializer}, this class should be 
*  annotated with a Order so that it is loaded before {@link SpringSecurityInit} 
*/ 
@Order(0) 
public class SpringWebInit extends AbstractAnnotationConfigDispatcherServletInitializer implements InitializingBean { 
    private final Logger LOG = LoggerFactory.getLogger(getClass()); 

    @Override 
    public void afterPropertiesSet() throws Exception { 
    LOG.info("DispatcherServlet loaded"); 
    } 

    @Override 
    protected Class<?>[] getServletConfigClasses() { 
    return null; // returning null, getRootConfigClasses() will handle this as well 
    } 

    @Override 
    protected String[] getServletMappings() { 
    return new String[] {"/**"}; // Spring MVC should handle everything 
    } 

    @Override 
    protected Class<?>[] getRootConfigClasses() { 
    return new Class[] {SpringWebConfig.class, SpringSecurityConfig.class}; 
    } 

    @Override 
    protected Filter[] getServletFilters() { 
    CharacterEncodingFilter characterEncodingFilter = 
     new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true); 
    return new Filter[] {characterEncodingFilter, new SiteMeshFilter()}; 
    } 

} 

这反过来又加载的各种配置的Freemarker的和SiteMesh的:

SpringWebConfig.java

@EnableWebMvc 
@Configuration 
@PropertySource("classpath:/butler-init.properties") 
@ComponentScan({"butler"}) 
class SpringWebConfig extends WebMvcConfigurerAdapter implements InitializingBean { 
    private final Logger LOG = LoggerFactory.getLogger(getClass()); 

    @Autowired 
    LoggedInUserService loggedInUserService; 

    @Override 
    public void afterPropertiesSet() throws Exception { 
    LOG.info("Web Mvc Configurer loaded"); 
    } 

    @Override 
    public void addInterceptors(InterceptorRegistry registry) { 
    registry.addInterceptor(userHeaderInterceptor()); 
    } 

    @Override 
    public void addResourceHandlers(ResourceHandlerRegistry registry) { 
    registry.addResourceHandler("/static/**").addResourceLocations("/static/").setCacheControl(
     CacheControl.maxAge(30, TimeUnit.MINUTES).noTransform().cachePublic().mustRevalidate()); 
    } 

    @Bean 
    FreeMarkerViewResolver viewResolver() throws TemplateException { 
    FreeMarkerViewResolver resolver = new FreeMarkerViewResolver(); 
    resolver.setCache(/*true*/false); // Set to false for debugging 
    resolver.setPrefix(""); 
    resolver.setSuffix(".ftlh"); 
    resolver.setRequestContextAttribute("rContext"); 
    resolver.setContentType("text/html;charset=UTF-8"); 

    DefaultObjectWrapper wrapper = 
     new DefaultObjectWrapperBuilder(freemarker.template.Configuration.getVersion()).build(); 
    Map<String, Object> attrs = new HashMap<>(); 
    attrs.put("loggedInUserService", wrapper.wrap(loggedInUserService)); 
    resolver.setAttributesMap(attrs); 

    return resolver; 
    } 

    @Bean 
    FreeMarkerConfigurer freeMarkerConfig() { 
    Properties freeMarkerVariables = new Properties(); 
    // http://freemarker.org/docs/pgui_config_incompatible_improvements.html 
    // http://freemarker.org/docs/pgui_config_outputformatsautoesc.html 
    freeMarkerVariables.put(freemarker.template.Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY, 
     freemarker.template.Configuration.getVersion().toString()); 

    FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer(); 
    freeMarkerConfigurer.setDefaultEncoding("UTF-8"); 
    freeMarkerConfigurer.setTemplateLoaderPath("/WEB-INF/mvc/view/ftl/"); 
    freeMarkerConfigurer.setFreemarkerSettings(freeMarkerVariables); 
    return freeMarkerConfigurer; 
    } 

    @Bean 
    UserHeaderInterceptor userHeaderInterceptor() { 
    return new UserHeaderInterceptor(); 
    } 

    @Bean 
    static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { 
    return new PropertySourcesPlaceholderConfigurer(); 
    } 
} 

SiteMeshFilter.java

public class SiteMeshFilter extends ConfigurableSiteMeshFilter { 

    @Override 
    protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {  

    // Don't use decorator REST api pages 
    builder.addExcludedPath("/api/*"); 

    builder.addDecoratorPath("/*", Views.DECORATOR_HEADER_FOOTER); 
    builder.setIncludeErrorPages(true); 
    } 
} 

最后,在肉的问题在于,错误处理是通过DefaultControllerAdvice.java来处理的,DefaultControllerAdvice.java提供了拦截异常的规则,ErrorController.java本身处理映射,最终处理消息(显示关于错误的信息,根据错误的类型,等等)

DefaultControllerAdvice.java

@ControllerAdvice(annotations = Controller.class) 
class DefaultControllerAdvice { 

    private static String EXCEPTION = "butlerexception"; 

    @ExceptionHandler(ServiceException.class) 
    public String exceptionHandler(ServiceException se, Model model) { 
    model.addAttribute(EXCEPTION, se.getMessage()); 
    return Views.ERROR; 
    } 

    @ExceptionHandler(PermissionException.class) 
    public String exceptionHandler(PermissionException pe, Model model) { 
    model.addAttribute(EXCEPTION, "Incorrect Permissions"); 
    return Views.ERROR; 
    } 

    /*@ResponseStatus(HttpStatus.NOT_FOUND) 
    @ExceptionHandler(IOException.class) 
    public String exceptionHandler(Model model) { // Trying another way of intercepting 404 errors 
    model.addAttribute(EXCEPTION, "HTTP Error: 404"); 
    return Views.ERROR; 
    }*/ 
} 

ErrorController。java的

@Controller 
class ErrorController extends AbstractController { 

    @Autowired 
    private LoggedInUserService loggedInUserService; 

    @RequestMapping(path="error",method = {GET,POST}) // Normal Error Controller, Returns fully decorated page without issue for Exceptions and normal requests. 
    public String error(RedirectAttributes redirectAttributes, HttpServletResponse response,Model model) { 
    //if (redirectAttributes.containsAttribute("errorCode")) { // Trying to invisibly use redirection 
    // Map<String, ?> redirAttribs = redirectAttributes.getFlashAttributes(); 
    // model.addAttribute("butlerexception", "HTTP Error: "+redirAttribs.get("errorCode")); 
    //} else { 
    model.addAttribute("butlerexception", "Error"); 
    //} 
    return ERROR; 
    } 

    @RequestMapping("/http-error") // Created to test HTTP requests being proxied via ServiceExceptions, Redirections, etc... 
    public String httpError(/*RedirectAttributes redirectAttributes,*/ HttpServletResponse response, HttpServletRequest request, Model model){ 
    model.addAttribute("butlerexception", "HTTP Error: " + response.getStatus()); 

    //throw new ServiceException("HTTP Error: " + response.getStatus()); // Trying to piggyback off Exception handling 

    //redirectAttributes.addFlashAttribute("errorCode", response.getStatus()); // Trying to invisibly use redirection 
    //redirectAttributes.addFlashAttribute("originalURL",request.getRequestURL()); 
    return /*"redirect:"+*/ERROR; 
    } 
} 

到目前为止,我曾尝试:

  • 抛出异常,以背驮式关闭工作ControllerAdvice规则。 - 结果未修饰。
  • 在规则中添加响应代码,IONotFound和NoHandler发现异常 - 结果未修饰。
  • 重定向到错误页面 - 结果装饰正确,但URL和响应代码不正确,试图用原始请求URL掩盖URL导致正确的URL和代码,但与之前缺少装饰。

此外,从调试日志中,我可以看到Spring Security的过滤器正常触发,但涉及装饰站点(用于登录和匿名请求)的错误只能触发HTTP错误。

目前的一个限制因素是,我无法在系统中直接定义web.xml(因为这里和Spring文档中提到的许多解决方案似乎都要求),而不会对开发过程造成过度干扰这个阶段。 (我也不要实现这种变化的权威机构(初级等级))

为方便起见,几个到目前为止,我已经试过的解决方案:

在这一点上,我真的不知道还有什么可以尝试,我到底在想什么?

编辑:它原来是在SiteMesh的一个bug与已通过的sitemesh后重新设置的contentType以触发装修解决了.setContentType(...)触发做到:Bug report with description and solution

回答

0

事实证明这一个两部分问题,首先SiteMesh3处理错误页面意味着它认为它已经处理了所有的过滤器,即使错误导致装饰器被跳过。 (expanded upon in this issue on github

第二部分是当SpringMVC调用.setContentType(...)时,SiteMesh3似乎只缓冲装饰页面。

由于Spring只会对未定义内容类型的元素触发,而错误已经在他们到达Spring之前就已经定义了它们的内容类型,所以这已经跳闸了。 (expanded upon by my lead in this issue

我的负责人设法通过在SiteMesh之后添加一个过滤器来解决此问题,该过滤器触发了.setContentType(...)并强制SiteMesh缓冲装饰页面。

这有点沉重,因为它意味着内容类型每次请求设置两次,但它的工作原理。


编辑:原来在这里度过了张便条,请不要给予好评,以避免收到代表对解决我的带动下找到,却发现博客中解释说,自答案不赚代表 - 好哇!