2017-04-24 219 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緩衝裝飾頁面。

這有點沉重,因爲它意味着內容類型每次請求設置兩次,但它的工作原理。


編輯:原來在這裏度過了張便條,請不要給予好評,以避免收到代表對解決我的帶動下找到,卻發現博客中解釋說,自答案不賺代表 - 好哇!

相關問題