【问题标题】:Java spring get body of post requestJava Spring获取发布请求的主体
【发布时间】:2014-12-10 09:24:59
【问题描述】:

我有以下问题:我尝试在 Spring 控制器处理之前获取 POST 请求的主体。为此,我使用了 HandlerInterceptorAdapter 的 preHandle() 方法。

如本次讨论中所述Spring REST service: retrieving JSON from Request 我也使用 HttpServletRequestWrapper。使用这个包装器,我设法打印了第一个 POST 请求的正文,但第二个 POST 抛出 IOException: StreamClosed。

您对如何获取所有 POST 请求的正文有任何想法吗?

这是拦截器的 preHandle() 方法:

@Override
public boolean preHandle(HttpServletRequest request,
        HttpServletResponse response, Object handler) throws Exception {
    System.out.println(request.getMethod());
    MyRequestWrapper w = new MyRequestWrapper(request);

    BufferedReader r = w.getReader();
    System.out.println(r.readLine());

    return super.preHandle(request, response, handler);
}

HttpServletRequestWrapper:

public class MyRequestWrapper extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
private HttpServletRequest request;

public MyRequestWrapper(HttpServletRequest request) {
    super(request);
    this.request = request;
}

@Override
public ServletInputStream getInputStream() throws IOException {
    cachedBytes = new ByteArrayOutputStream();

    if (request.getMethod().equals("POST"))
        cacheInputStream();

    return new CachedServletInputStream();
}

@Override
public BufferedReader getReader() throws IOException {
    return new BufferedReader(new InputStreamReader(getInputStream()));
}

private void cacheInputStream() throws IOException {
    /*
     * Cache the inputstream in order to read it multiple times. For
     * convenience, I use apache.commons IOUtils
     */
    ServletInputStream inputStream = super.getInputStream();

    if (inputStream == null) {
        return;
    }

    IOUtils.copy(inputStream, cachedBytes);
}

/* An inputstream which reads the cached request body */
public class CachedServletInputStream extends ServletInputStream {
    private ByteArrayInputStream input;

    public CachedServletInputStream() {
        /* create a new input stream from the cached request body */
        input = new ByteArrayInputStream(cachedBytes.toByteArray());
    }

    @Override
    public int read() throws IOException {
        return input.read();
    }
}
}

控制台输出:

2014-10-15 12:13:00 INFO [http-nio-8080-exec-1] org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization completed in 9 ms
GET
null
GET
null
POST
{"long":null,"owner":{"__type":"Owner","id":20,"version":1,"md5Password":""},"string":"ws","tool":{"__type":"Tool","id":33,"version":1}}
POST
2014-10-15 12:13:00 ERROR [http-nio-8080-exec-3] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet dispatcherServlet threw exception
java.io.IOException: Stream closed

【问题讨论】:

    标签: spring post interceptor


    【解决方案1】:

    您正在尝试读取 Wrapper 中的原始请求,但在此之后,原始请求仍在被读取 - 因此请求输入流已被消耗,无法再次读取。

    考虑使用 javax.servlet.Filter,而不是使用拦截器。在 doFilter 方法中,您可以将包装好的请求沿链向下传递。

    【讨论】:

    • 我认为过滤器也会有同样的问题,因为我必须传递一个已经被消费的流。奇怪的是,包装器无法复制请求流并将其进一步传递。还是有其他方法可以解决这个问题?
    【解决方案2】:

    我使用了实现过滤器和拦截器的过滤器,它扩展了 HandlerInterceptorAdapter(因为在过滤器中所有字段都可以为空,我无法将任何内容保存到 DB。请参阅Autowired Null Pointer Exception)来检索请求和响应正文并将它们保存到D B。如果您的过滤器工作正常,则仅使用过滤器。

    过滤器。在这里,我包装了一个请求和一个响应,不仅要从它们中读取一次。您可以为此使用 ContentCachingRequestWrapper 和 ContentCachingResponseWrapper 。

        @Component
        public class RequestLogFilter implements Filter {
    
        private final Logger logger = LoggerFactory.getLogger(RequestLogFilter.class);
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // TODO Auto-generated method stub
    
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
            logger.info("======================> FILTER <======================");
    
            HttpServletRequest requestToCache = new ContentCachingRequestWrapper((HttpServletRequest) request);
            HttpServletResponse responseToCache = new ContentCachingResponseWrapper((HttpServletResponse) response);
    
            // before method
            chain.doFilter(requestToCache, responseToCache);
            // after method
    
            // your logic(save to DB, logging...)
            getRequestData(request);
            getResponseData(response);
        }
    
        @Override
        public void destroy() {
    
        }
    
    }
    

    -

    @Component
    public class RequestLogInterceptor extends HandlerInterceptorAdapter {
    
        private final Logger logger = LoggerFactory.getLogger(RequestLogInterceptor.class);
    
        @Autowired
        private InboundRequestLogStore inboundRequestLogStore;
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
    
            logger.info("====================> INTERCEPTOR <========================");
    
            try {
                if (request.getAttribute(InboundRequestAspect.INBOUND_LOG_MARKER) != null) {
    
                    InboundRequestLogRecord logRecord = new InboundRequestLogRecord();
    
                    logRecord.setIpAddress(request.getRemoteAddr());
    
                    // getting request and response body
                    logRecord.setRequestBody(getRequestData(request));
                    logRecord.setResponseBody(getResponseData(response));
    
                    logRecord.setResponseCode(((HttpServletResponse) response).getStatus());
    
                    String uri = request.getScheme() + "://" + request.getServerName()
                            + ("http".equals(request.getScheme()) && request.getServerPort() == 80
                                    || "https".equals(request.getScheme()) && request.getServerPort() == 443 ? ""
                                            : ":" + request.getServerPort())
                            + request.getRequestURI()
                            + (request.getQueryString() != null ? "?" + request.getQueryString() : "");
                    logRecord.setUrl(uri);
                    inboundRequestLogStore.add(logRecord);  // save to DB
    
                  } else {
    
                            ((ContentCachingResponseWrapper) response).copyBodyToResponse(); // in other case you send null to the response
    
                  }
    
    
            } catch (Exception e) {
                logger.error("error ", e);
    
                try {
                        ((ContentCachingResponseWrapper) response).copyBodyToResponse(); // in other case you send null to the response
                    } catch (Exception e2) {
                        // TODO Auto-generated catch block
                        logger.error("error ", e2);
                    }
    
            }
    
    
    
        }
    
        public static String getRequestData(final HttpServletRequest request) throws UnsupportedEncodingException {
            String payload = null;
            ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
            if (wrapper != null) {
                byte[] buf = wrapper.getContentAsByteArray();
                if (buf.length > 0) {
                    payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
                }
            }
            return payload;
        }
    
            public static String getResponseData(final HttpServletResponse response) throws UnsupportedEncodingException, IOException {
                String payload = null;
                ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
                if (wrapper != null) {
                    byte[] buf = wrapper.getContentAsByteArray();
                    if (buf.length > 0) {
                        payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
                    }
                    wrapper.copyBodyToResponse();   // in other case you send null to the response
                }
                return payload;
            }
    
        }
    

    添加到 servlet-context.xml

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**" />
            <beans:bean class="path.to.RequestLogInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    

    命名空间:

    <beans:beans xmlns="http://www.springframework.org/schema/mvc"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    

    ContentCachingRequestWrapper - http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingRequestWrapper.html

    ContentCachingResponseWrapper - http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingResponseWrapper.html

    【讨论】:

    • 你确定你的 Interceptor-example 工作正常吗?对我来说,在 postHandle() 方法中获取响应主体似乎是不可能的,因为它已经写入了 OutputStream。这是拦截器(与过滤器相比)的主要限制。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-06
    • 2021-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多