Java 使用Spring MVC HandlerInterceptorAdapter从HttpServletResponse记录响应体(HTML)

Java 使用Spring MVC HandlerInterceptorAdapter从HttpServletResponse记录响应体(HTML),java,spring,spring-mvc,servlets,Java,Spring,Spring Mvc,Servlets,我正在尝试记录(为了简单起见,现在只是为了控制台编写)HttpServletResponse将返回的最终呈现HTML。(即主体)为此,我使用Spring MVC的HandlerInterceptorAdapter,如下所示: public class VxmlResponseInterceptor extends HandlerInterceptorAdapter { @Override public void afterCompletion(HttpServletRequest

我正在尝试记录(为了简单起见,现在只是为了控制台编写)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等进行跳转


提前感谢。

这最好使用Servlet而不是Spring来完成,因为允许使用
过滤器
来替换请求和/或响应对象,并且可以使用此机制将响应替换为记录响应输出的包装器

这将涉及编写的子类,重写
getOutputStream
(也可能是
getWriter()
)。这些方法将返回
OutputStream
/
PrintWriter
实现,除了将响应流发送到其原始目标之外,还将响应流虹吸到日志中。一个简单的方法是使用from,但自己实现并不困难

下面是一个示例,您可以使用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。如果你想登录到一个文件,它会变得更复杂,比如确保流被关闭等等,但原理是一样的。

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

中的TeeFilter一章。我一直在寻找一种方法来记录完整的HTTP请求/响应,发现它在中为我解决了。它的工作原理与Tomcat7容器中的广告一样。如果您想在Jetty中使用它,该类可以独立运行,或者像我一样,复制并适应我环境的特定需要。

我通过maven central提供了一个小型库

添加到pom.xml:

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

com.github.isrsal
spring mvc记录器
0.2
添加到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>

日志过滤器
com.github.isrsal.logging.LoggingFilter
日志过滤器
/*
添加到log4j.xml:

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

下面粘贴的代码适用于我的测试,可以从my下载,并在生产项目上应用基于此的解决方案后共享

    @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;
        }

    }
}
@配置
公共类LoggingFilter扩展了GenericFilterBean{
/**
*重要的是,你要以这种方式注册你的过滤器,而不是仅仅注释它
*as@Component as您需要能够设置为哪个“DispatcherType”启用过滤器
*(见第*1*点)
* 
*@返回
*/
@豆子
公共过滤器注册bean initFilter(){
FilterRegistrationBean registrationBean=新的FilterRegistrationBean();
setFilter(新的LoggingFilter());
//*1*如果希望筛选器登录,请确保设置了所有dispatcher类型
registrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
//*2*这将使您的过滤器高于任何其他过滤器
registrationBean.setOrder(有序的、最高的_优先级);
返回注册bean;
}
@凌驾
public void doFilter(ServletRequest请求、ServletResponse响应、FilterChain链)
抛出IOException、ServletException{
ContentCachingRequestWreq=
新ContentCachingRequestWrapper(
(HttpServletRequest)请求);
ContentCachingResponseWrapper wres=
新内容CachingResponseWrapper(
(HttpServletResponse)响应);
试一试{
//随它去吧。。。
链式过滤器(wreq,wres);
//确保输入已读取(例如,在404中可能未读取)
while(wreq.getInputStream().read()>=0);
System.out.printf(“==请求%n%s%n==结束请求%n”,
新字符串(wreq.getContentAsByteArray());
//在这里做任何你想做的记录,在这种情况下,我正在写请求
//以及对系统输出的响应,这可能不是您希望做的
System.out.printf(“==响应%n%s%n==结束响应%n”,
新字符串(wres.getContentAsByteArray());
//这是我们所依赖的“ContentCachingResponseWrapper”的具体内容,
//确保在阅读响应内容后调用它
wres.copybodtoresponse();
//还有一点,在重定向的情况下,这将被调用两次!小心处理
//有点
}捕获(可丢弃的t){
//你在这里也可以随便做什么
//在这里你也应该记录错误!!!
掷t;
}
}
}

这通常是在容器的帮助下完成的。。。。你在运行什么?我在Jetty 7中通过Jetty maven插件运行它,但我不明白这有什么关系。我希望看到浏览器将要接收的html响应。事实上,它确实变得更加复杂,因为在DelegatingServletOutputStream上没有调用close,因为包装器在容器关闭流时超出范围。您对此有什么想法吗?看起来这个过滤器也包含在这个答案中:。对Twiggs先生来说,Tomcat 7 RequestDumperFilter只能转储头文件,这非常简单。这么多年后,它仍然可以工作。非常感谢,它救了我一天。