为了账号安全,请及时绑定邮箱和手机立即绑定

请问Http servlet请求在读取一次后会丢失来自POST主体的Params

/ 猿问

请问Http servlet请求在读取一次后会丢失来自POST主体的Params

幕布斯6054654 2019-10-21 16:12:10

Http servlet请求在读取一次后会丢失来自POST主体的Params

我试图访问Javaservlet过滤器中的两个http请求参数,这里没有什么新的,但是惊讶地发现这些参数已经被消耗了!因此,它在过滤器链中不再可用。

似乎只有当参数出现在POST请求体(例如表单提交)中时才会发生这种情况。

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

到目前为止,我只找到了这个引用:使用request.get参量的servlet筛选器丢失表单数据.

谢谢!


查看完整描述

3 回答

?
慕桂英4014372

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

但是,我仍然认为,当请求在过滤器链中移动时,希望不止一次地读取请求体是合理的。我通常会使用过滤器链进行某些操作,这些操作我希望保留在HTTP层,并将其与服务组件分离。

我通过扩展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 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();
    }
  }}

现在,通过将原始请求包装在筛选链中之前,可以多次读取请求主体:

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等。请参阅问题如下文评论所述。



查看完整回答
反对 回复 2019-10-22
?
慕函数4003404

我知道我迟到了,但这个问题仍然与我有关,所以这篇文章是谷歌最热门的文章之一。我将继续发布我的解决方案,希望其他人能节省几个小时。

在我的例子中,我需要用它们的身体记录所有请求和响应。使用SpringFramework答案其实很简单,只需使用ContentCachingRequestWrapper感应器.

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

        }

    }}



查看完整回答
反对 回复 2019-10-22
?
白衣非少年

唯一的方法是自己在过滤器中使用整个输入流,从其中获取所需的内容,然后为所阅读的内容创建一个新的InputStream,并将InputStream放入ServletRequestWrapper(或HttpServletRequestWrapper)。

缺点是,您必须自己解析有效负载,标准不允许您使用该功能。

增编

正如我所说的,您需要查看HttpServletRequestWrapper。

在过滤器中,继续调用FilterChain.doFilter(请求、响应)。

对于简单的过滤器,请求和响应与传递给过滤器的请求和响应相同。不必是这样的。您可以用自己的请求和/或响应替换这些请求。

HttpServletRequestWrapper是专门为这一目的而设计的。将原始请求传递给它,然后就可以拦截所有的呼叫。您将创建自己的子类,并将getInputStream方法替换为您自己的方法之一。您不能更改原始请求的输入流,因此可以使用这个包装器并返回您自己的输入流。

最简单的情况是在字节缓冲区中使用原始请求输入流,在它上执行您想要的任何魔术,然后从该缓冲区创建一个新的ByteArrayInputStream。这就是在包装器中返回的内容,它被传递给FilterChain.doFilter方法。

您需要子类ServletInputStream,并为ByteArrayInputStream编写另一个包装器,但这也不是什么大事。



查看完整回答
反对 回复 2019-10-22

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信