使用 Spring MVC HandlerInterceptorAdapter 从 HttpServletResponse 记录响应正文 (HTML)

Posted

技术标签:

【中文标题】使用 Spring MVC HandlerInterceptorAdapter 从 HttpServletResponse 记录响应正文 (HTML)【英文标题】:Logging response body (HTML) from HttpServletResponse using Spring MVC HandlerInterceptorAdapter 【发布时间】:2011-01-10 15:31:48 【问题描述】:

我正在尝试记录(为了简单起见,现在只是为了控制台编写)将由 HttpServletResponse 返回的最终呈现的 html。 (即主体)为此,我使用 Spring MVC 中的 HandlerInterceptorAdapter,如下所示:

public class VxmlResponseInterceptor extends HandlerInterceptorAdapter 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception 
        System.out.println(response.toString());
    

这按预期工作,我在控制台中看到了 HTTP 响应标头。我的问题是,是否有一种相对简单的方法可以将整个响应正文(即最终呈现的 HTML)记录到控制台,而不必求助于 PrintWriter、OutputStream 等。

提前致谢。

【问题讨论】:

这通常是在容器的帮助下完成的......你在什么地方运行这个? 我通过 jetty-maven-plugin 在 Jetty 7 中运行它,但我不明白为什么这很重要。我想查看浏览器将收到的 html 响应。 【参考方案1】:

最好使用 Servlet Filter 而不是 Spring HandlerInterceptor 来完成,因为允许 Filter 替换请求和/或响应对象,您可以使用此机制来替换带有记录响应输出的包装器的响应。

这将涉及编写HttpServletResponseWrapper 的子类,覆盖getOutputStream(可能还有getWriter())。这些方法将返回OutputStream/PrintWriter 实现,这些实现将响应流虹吸到日志中,除了发送到其原始目的地。一个简单的方法是使用Apache Commons IO 中的TeeOutputStream,但自己实现并不难。

这里有一个你可以做的事情的例子,利用 Spring 的 GenericFilterBeanDelegatingServletResponseStream 以及 TeeOutputStream,让事情变得更容易:

public class ResponseLoggingFilter extends GenericFilterBean 

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException 
      HttpServletResponse responseWrapper = loggingResponseWrapper((HttpServletResponse) response);     
      filterChain.doFilter(request, responseWrapper);
   

   private HttpServletResponse loggingResponseWrapper(HttpServletResponse response) 
      return new HttpServletResponseWrapper(response) 
         @Override
         public ServletOutputStream getOutputStream() throws IOException 
            return new DelegatingServletOutputStream(
               new TeeOutputStream(super.getOutputStream(), loggingOutputStream())
            );
         
      ;
   

   private OutputStream loggingOutputStream() 
      return System.out;
   

这会将所有内容记录到 STDOUT。如果你想记录到一个文件,它会变得更复杂,确保流被关闭等等,但原理保持不变。

【讨论】:

确实它变得更加复杂,因为在 DelegatingServletOutputStream 上没有调用 close,因为在容器关闭流时包装器超出了范围。你有什么想法可以解决这个问题吗?【参考方案2】:

如果您正在使用(或考虑)logback 作为您的日志记录框架,那么已经有一个很好的 servlet 过滤器可以做到这一点。查看documentation 中的 TeeFilter 章节。

【讨论】:

【参考方案3】:

一段时间以来,我一直在寻找一种记录完整 HTTP 请求/响应的方法,并发现它已在 Tomcat 7 RequestDumperFilter 中为我解决。它的工作原理与 Tomcat 7 容器所宣传的一样。如果您想在 Jetty 中使用它,该类可以独立运行,或者像我一样,复制并适应我的环境的特定需求。

【讨论】:

看起来这个过滤器也包含在这个答案中:***.com/questions/5672904/…。 对 Twiggs 先生来说,Tomcat 7 RequestDumperFilter 只能转储 header,这很简单。【参考方案4】:

我通过 maven Central 创建了一个小型库 spring-mvc-logger。

添加到 pom.xml:

<dependency>
    <groupId>com.github.isrsal</groupId>
    <artifactId>spring-mvc-logger</artifactId>
    <version>0.2</version>
</dependency>

添加到 web.xml:

<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>com.github.isrsal.logging.LoggingFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

添加到 log4j.xml:

<logger name="com.github.isrsal.logging.LoggingFilter">
    <level value="DEBUG"/>
</logger>

【讨论】:

经过这么多年,它仍然有效。非常感谢,它节省了我的时间。【参考方案5】:

下面粘贴的代码适用于我的测试,可以从我的github project 下载,在将基于该解决方案的解决方案应用于生产项目后分享

    @Configuration
public class LoggingFilter extends GenericFilterBean 

    /**
     * It's important that you actually register your filter this way rather then just annotating it
     * as @Component as you need to be able to set for which "DispatcherType"s to enable the filter
     * (see point *1*)
     * 
     * @return
     */
    @Bean
    public FilterRegistrationBean<LoggingFilter> initFilter() 
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());

        // *1* make sure you sett all dispatcher types if you want the filter to log upon
        registrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));

        // *2* this should put your filter above any other filter
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registrationBean;
    

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException 

        ContentCachingRequestWrapper wreq = 
            new ContentCachingRequestWrapper(
                (HttpServletRequest) request);

        ContentCachingResponseWrapper wres = 
            new ContentCachingResponseWrapper(
                (HttpServletResponse) response);

        try 

            // let it be ...
            chain.doFilter(wreq, wres);

            // makes sure that the input is read (e.g. in 404 it may not be)
            while (wreq.getInputStream().read() >= 0);

            System.out.printf("=== REQUEST%n%s%n=== end request%n",
                    new String(wreq.getContentAsByteArray()));

            // Do whatever logging you wish here, in this case I'm writing request 
            // and response to system out which is probably not what you wish to do
            System.out.printf("=== RESPONSE%n%s%n=== end response%n",
                    new String(wres.getContentAsByteArray()));

            // this is specific of the "ContentCachingResponseWrapper" we are relying on, 
            // make sure you call it after you read the content from the response
            wres.copyBodyToResponse();

            // One more point, in case of redirect this will be called twice! beware to handle that
            // somewhat

         catch (Throwable t) 
            // Do whatever logging you whish here, too
            // here you should also be logging the error!!!
            throw t;
        

    

【讨论】:

以上是关于使用 Spring MVC HandlerInterceptorAdapter 从 HttpServletResponse 记录响应正文 (HTML)的主要内容,如果未能解决你的问题,请参考以下文章

浅析Spring MVC的工作原理及其与Spring的关系

Spring 3 MVC:使用自定义验证器显示验证消息

Spring MVC RequestMapping的使用方法

Spring MVC系列初识Spring MVC

在 spring-boot 项目中使用 spring mvc xml 项目

检查使用 JSTL/Spring-MVC 标签关于 Spring Binding 错误