使用 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 的 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 中使用它,该类可以独立运行,或者像我一样,复制并适应我的环境的特定需求。
【讨论】:
看起来这个过滤器也包含在这个答案中:***.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 RequestMapping的使用方法