如何使用保留请求正文和响应正文的 servlet 过滤器记录请求和响应?

Posted

技术标签:

【中文标题】如何使用保留请求正文和响应正文的 servlet 过滤器记录请求和响应?【英文标题】:How to log request and response using servlet filter preserving the request body and response body? 【发布时间】:2017-02-21 08:49:54 【问题描述】:

我正在尝试在我的应用程序中记录每个传入请求和传出响应。我使用的是 jee 6,所以我没有 ContainerRequestFilter 和 ContainerResponseFilter 类。所以我决定使用过滤器。

我用 @WebFilter("/*") 注释了一个类并实现了 Filter 接口。我成功读取了请求标头和请求正文。遇到一些困难,我还阅读了响应标头和响应正文。下面是一段代码sn-p

MyHttpServletResponseWrapper wrapper = new MyHttpServletResponseWrapper((HttpServletResponse) response);
chain.doFilter(request, wrapper);

MyHttpServletResponseWrapper 类

public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper 
    private StringWriter sw = new StringWriter();
    public MyHttpServletResponseWrapper(HttpServletResponse response)  super(response); 

    public PrintWriter getWriter()  return new PrintWriter(sw); 

    public ServletOutputStream getOutputStream() 
        return new ServletOutputStream ()
            public void write(int B)  sw.append((char) B); 
        ;
    

    public String getCopy()  sw.toString(); 

记录响应后,我将响应写回输出流,以便客户端可以接收响应。下面是代码

logResponse(wrapper);
response.getOutputStream().write(wrapper.getCopy().getBytes());

我无法理解的是,阅读后如何将请求正文放回输入流中。

在 Jersey 这样的标准 API 中,有一种方便的方法可以使用 setEntityInputStream(inputStream) 将其放回原处

如何使用标准 Java ServletOutputStream api 来实现。

我并不是要重新发明***。我试图避免使用 Jersey,以便我的代码可以轻松迁移到新的 jee 版本。我也想了解像 Jersey 这样的 API 是如何做到的。

要阅读正文的回复,我阅读了以下链接,但它对我不起作用。 Container 抛出异常,说 writer 已经获得。

Capture and log the response body

附言

我想我知道如何重新设置输入流。下面是一些代码

MyRequestWrapper requestwrapper = new MyRequestWrapper((HttpServletRequest) request);

chain.doFilter(requestwrapper, wrapper);

MyRequestWrapper 类

public class MyRequestWrapper extends HttpServletRequestWrapper 
    String body; 
    int counter;
    public MyRequestWrapper(HttpServletRequest request)  super(request); 

    public void setBody(String body)  this.body = body;

    public ServletInputStream getInputStream() 
        return new ServletInputStream() 
            public interest read() throws IOException 
                if(counter >=body.length()) return -1;
                return body.charAt(counter++);
            
        ;
    

我知道重写的 getInputStream 和 getOutputStream 中的代码质量不是很好。现在我并不担心这个。我想知道这是否是一个正确的想法?如果那样,那么我想专注于代码质量。

【问题讨论】:

【参考方案1】:
/** I know its too late but it can be helpfull for others as well. **//
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

public class ApiIOLoggerFilter implements Filter 
@Override 
public void init(FilterConfig filterConfig) 
@Override 
public void destroy() 
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
    if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) 
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
        try 
            chain.doFilter(requestWrapper, responseWrapper);
         finally 
            performRequestAudit(requestWrapper);
            performResponseAudit(responseWrapper);
        
     else 
        chain.doFilter(request, response);
    


private void performRequestAudit(ContentCachingRequestWrapper requestWrapper) 
    if (requestWrapper != null && requestWrapper.getContentAsByteArray() != null && requestWrapper.getContentAsByteArray().length > 0) 
        logger.info("\n Headers::  \n Request Body:: ", new ServletServerHttpRequest((HttpServletRequest)requestWrapper.getRequest()).getHeaders(),
                getPayLoadFromByteArray(requestWrapper.getContentAsByteArray(), requestWrapper.getCharacterEncoding()));
    


private void performResponseAudit(ContentCachingResponseWrapper responseWrapper)
        throws IOException 
    if (responseWrapper != null && responseWrapper.getContentAsByteArray() != null
            && responseWrapper.getContentAsByteArray().length > 0) 
        logger.info("Response Body:: ", getPayLoadFromByteArray(responseWrapper.getContentAsByteArray(),
                responseWrapper.getCharacterEncoding()));
     else 
        performErrorResponseAudit(responseWrapper);
    
    responseWrapper.copyBodyToResponse();


private void performErrorResponseAudit(ContentCachingResponseWrapper responseWrapper) 
    logger.warn("HTTP Error Status Code::" + responseWrapper.getStatus());


private String getPayLoadFromByteArray(byte[] requestBuffer, String charEncoding) 
    String payLoad = "";
    try 
        payLoad = new String(requestBuffer, charEncoding);
     catch (UnsupportedEncodingException unex) 
        payLoad = "Unsupported-Encoding";
    
    return payLoad;



【讨论】:

这真的很有帮助。但前提是Spring 4.1.3+

以上是关于如何使用保留请求正文和响应正文的 servlet 过滤器记录请求和响应?的主要内容,如果未能解决你的问题,请参考以下文章

servlet 中读取请求正文的问题

使用 HttpRequestException 获取失败请求的响应正文

Java过滤器 - 根据请求有条件地更改响应正文

如何根据使用wiremock和JSON的请求正文匹配获得响应

当 HTTP 请求返回状态 401 时,如何在 Java 中解析响应正文

带有 XML 响应和正文的 SOAP 请求 swift 3,Alamofire