Http Servlet 请求在读取一次后从 POST 正文中丢失参数

新手上路,请多包涵

我正在尝试访问 Java Servlet 过滤器中的两个 http 请求参数,这里没有什么新东西,但惊讶地发现这些参数已经被使用了!因此,它们不再在过滤器链中可用。

似乎这只发生在参数进入 POST 请求正文(例如表单提交)时。

有没有办法读取参数而不消耗它们?

到目前为止,我只找到了这个参考: Servlet Filter using request.getParameter lost Form data

谢谢!

原文由 amuniz 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.5k
2 个回答

顺便说一句,解决这个问题的另一种方法是不使用过滤器链,而是构建自己的拦截器组件,也许使用方面,它可以对解析的请求体进行操作。它也可能更有效,因为您只需将请求 InputStream 转换为您自己的模型对象一次。

但是,我仍然认为希望多次读取请求正文是合理的,特别是当请求通过过滤器链时。我通常会将过滤器链用于我想保留在 HTTP 层的某些操作,与服务组件分离。

正如 Will Hartung 所建议的,我通过扩展 HttpServletRequestWrapper 来实现这一点,使用请求 InputStream 并基本上缓存字节。

 public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
  private ByteArrayOutputStream cachedBytes;

  public MultiReadHttpServletRequest(HttpServletRequest request) {
    super(request);
  }

  @Override
  public ServletInputStream getInputStream() throws IOException {
    if (cachedBytes == null)
      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
     */
    cachedBytes = new ByteArrayOutputStream();
    IOUtils.copy(super.getInputStream(), cachedBytes);
  }


  /* An input stream which reads the cached request body */
  private static class CachedServletInputStream extends     ServletInputStream {

    private final ByteArrayInputStream buffer;

    public CachedServletInputStream(byte[] contents) {
      this.buffer = new ByteArrayInputStream(contents);
    }

    @Override
    public int read() {
      return buffer.read();
    }

    @Override
    public boolean isFinished() {
      return buffer.available() == 0;
    }

    @Override
    public boolean isReady() {
      return true;
    }

    @Override
    public void setReadListener(ReadListener listener) {
  throw new RuntimeException("Not implemented");
    }
  }
}

现在可以通过在将原始请求传递给过滤器链之前包装原始请求来多次读取请求正文:

 public class MyFilter implements Filter {
  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {

    /* wrap the request in order to read the inputstream multiple times */
    MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);

    /* here I read the inputstream and do my thing with it; when I pass the
     * wrapped request through the filter chain, the rest of the filters, and
     * request handlers may read the cached inputstream
     */
    doMyThing(multiReadRequest.getInputStream());
    //OR
    anotherUsage(multiReadRequest.getReader());
    chain.doFilter(multiReadRequest, response);
  }
}

该解决方案还允许您通过 getParameterXXX 方法多次读取请求正文,因为基础调用是 getInputStream() ,这当然会读取缓存的请求 InputStream

编辑

对于 ServletInputStream 接口的较新版本。您需要提供更多方法的实现,例如 isReadysetReadListener 等。请参阅下面评论中提供的这个 问题

原文由 pestrella 发布,翻译遵循 CC BY-SA 4.0 许可协议

我知道我迟到了,但这个问题对我来说仍然很重要,这篇 SO 帖子是谷歌的热门帖子之一。我将继续发布我的解决方案,希望其他人可以节省几个小时。

在我的例子中,我需要用他们的身体记录所有请求和响应。使用 Spring Framework 答案实际上非常简单,只需使用 ContentCachingRequestWrapperContentCachingResponseWrapper

 import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LoggingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);

        try {
            chain.doFilter(requestWrapper, responseWrapper);
        } finally {

            String requestBody = new String(requestWrapper.getContentAsByteArray());
            String responseBody = new String(responseWrapper.getContentAsByteArray());
            // Do not forget this line after reading response content or actual response will be empty!
            responseWrapper.copyBodyToResponse();

            // Write request and response body, headers, timestamps etc. to log files

        }

    }

}

原文由 Mikk 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题