【问题标题】:Logging response body (HTML) from HttpServletResponse using Spring MVC HandlerInterceptorAdapter使用 Spring MVC HandlerInterceptorAdapter 从 HttpServletResponse 记录响应正文 (HTML)
【发布时间】:2020-08-22 16:41:15
【问题描述】:

我正在尝试记录(为了简单起见,现在只是为了控制台编写)将由 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 响应。

标签: java spring spring-mvc servlets


【解决方案1】:

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

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

这里有一个你可以做的事情的例子,利用 Spring 的 GenericFilterBean 和 DelegatingServletResponseStream 以及 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 中使用它,该类可以独立运行,或者像我一样,复制并适应我的环境的特定需求。

    【讨论】:

    【解决方案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;
            }
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-13
      • 2021-02-10
      • 2019-03-31
      相关资源
      最近更新 更多