【问题标题】:java.lang.IllegalStateException: getReader() has already been called for this requestjava.lang.IllegalStateException: getReader() 已经为此请求调用
【发布时间】:2011-11-11 05:30:31
【问题描述】:

我想将日志记录添加到我的 Servlet,所以我创建了过滤器,它应该显示请求并转到 Servlet。但不幸的是,我遇到了异常:

java.lang.IllegalStateException: getReader() has already been called for this request
    at org.apache.catalina.connector.Request.getInputStream(Request.java:948)
    at org.apache.catalina.connector.RequestFacade.getInputStream(RequestFacade.java:338)
    at com.noelios.restlet.ext.servlet.ServletCall.getRequestEntityStream(ServletCall.java:190)

为了解决这个问题,我找到了 Wrapper 的解决方案,但它不起作用。我还能在代码中使用/更改什么?有什么想法吗?

[MyHttpServletRequestWrapper]

public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper
{
    public MyHttpServletRequestWrapper(HttpServletRequest request)
    {
        super(request);
    }

    private String getBodyAsString()
    {
        StringBuffer buff = new StringBuffer();
        buff.append(" BODY_DATA START [ ");
        char[] charArr = new char[getContentLength()];
        try
        {
            BufferedReader reader = new BufferedReader(getReader());
            reader.read(charArr, 0, charArr.length);
            reader.close();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        buff.append(charArr);
        buff.append(" ] BODY_DATA END ");
        return buff.toString();
    }

    public String toString()
    {
        return getBodyAsString();
    }
}

[我的过滤器]

public class MyFilterimplements Filter
{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        final HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        final HttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper(httpServletRequest);
        final String requestBody = requestWrapper.toString();

        chain.doFilter(request, response);
    }
}

【问题讨论】:

    标签: java servlets servlet-filters


    【解决方案1】:

    看起来restlet框架在Request对象上调用了getRequestEntityStream(),而后者又调用了getInputStream(),所以在请求上调用getReader()会抛出IllegalStateException。 getReader() 和 getInputStream() 的 Servlet API 文档说:

     public java.io.BufferedReader getReader()
        ...
        ...
    Throws:
        java.lang.IllegalStateException - if getInputStream() method has been called on this request
    
     public ServletInputStream getInputStream()
        ...
        ...
        Throws:
        java.lang.IllegalStateException - if the getReader() method has already been called for this request
    

    从文档看来,我们不能在 Request 对象上同时调用 getReader() 和 getInputStream()。我建议您在包装器中使用getInputStream() 而不是getReader()

    【讨论】:

      【解决方案2】:

      主要问题是您无法将输入同时读取为二进制流和字符流,即使一个在过滤器中调用而另一个在 servlet 中调用也不行。

      【讨论】:

        【解决方案3】:

        据我所知,servlet 在这方面从根本上被破坏了。您可以按照here 的概述尝试解决此问题,但是当其他事情尝试解决此问题时,这会导致其他神秘问题。

        实际上,他建议克隆请求,读取正文,然后在克隆的类中覆盖 getReader 和 getInputStream 方法以返回已检索到的内容。

        我最终得到的代码是这样的:

        import javax.servlet.ServletInputStream;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletRequestWrapper;
        import java.io.*;
        
        //this class stops reading the request payload twice causing an exception
        public class WrappedRequest extends HttpServletRequestWrapper
        {
            private String _body;
            private HttpServletRequest _request;
        
            public WrappedRequest(HttpServletRequest request) throws IOException
            {
                super(request);
                _request = request;
        
                _body = "";
                try (BufferedReader bufferedReader = request.getReader())
                {
                    String line;
                    while ((line = bufferedReader.readLine()) != null)
                        _body += line;
                }
            }
        
            @Override
            public ServletInputStream getInputStream() throws IOException
            {
                final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(_body.getBytes());
                return new ServletInputStream()
                {
                    public int read() throws IOException
                    {
                        return byteArrayInputStream.read();
                    }
                };
            }
        
            @Override
            public BufferedReader getReader() throws IOException
            {
                return new BufferedReader(new InputStreamReader(this.getInputStream()));
            }
        }
        

        无论如何,这似乎工作正常,直到我们意识到从浏览器上传文件不起作用。我一分为二,发现这是罪魁祸首。

        那篇文章中的 cmets 中的一些人说您需要重写方法来处理参数,但没有解释如何做到这一点。

        因此,我检查了两个请求是否有任何差异。但是,在克隆请求后,它具有相同的参数集(原始请求 + 克隆的都没有)以及相同的标头集。

        但是,请求以某种方式受到影响,并进一步加深了对请求的理解——在我的情况下,导致库 (extdirectspring) 中出现 bizaare 错误,其中某些东西试图将内容读取为 Json。取出过滤器中读取正文的代码使其再次工作。

        我的调用代码如下所示:

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
        {
            HttpServletRequest properRequest = ((HttpServletRequest)request);
        
            String pathInfo = properRequest.getPathInfo();
            String target = "";
            if(pathInfo == null)
                pathInfo = "";
        
            if(pathInfo.equals("/router"))
            {
                //note this is because servlet requests hate you!
                //if you read their contents more than once then they throw an exception so we need to do some madness
                //to make this not the case
                WrappedRequest wrappedRequest = new WrappedRequest(properRequest);
                target = ParseExtDirectTargetFrom(wrappedRequest);
                request = wrappedRequest;
            }
        
            boolean callingSpecialResetMethod = pathInfo.equals("/resetErrorState") || target.equals("resetErrorState");
            if(_errorHandler.IsRejectingRequests() && !callingSpecialResetMethod)
                return;
        
            try {
                filterChain.doFilter(request, response);
            }
            catch (Exception exception) {
                ((HttpServletResponse) response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "ERROR");
                _errorHandler.NotifyOf(exception);
            }
        }
        

        我省略了ParseExtDirectTargetFrom 的内容,但它调用了getReader()。

        在我的情况下,过滤器适用于所有其他请求,但在这种情况下的奇怪行为让我意识到有些事情不太正确,而我试图做的事情(为测试实现合理的异常处理行为)并不值得可能会破坏未来的随机请求(因为我无法弄清楚是什么导致请求被破坏)。

        另外值得注意的是,损坏的代码是不可避免的——我认为它可能是从春天开始的,但 ServletRequest 一直向上——即使你是通过子类化 HttpServlet 从头开始​​制作一个 servlet,你也能得到这一切。

        我的建议是这样 - 不要在过滤器中阅读请求正文。您将打开一罐蠕虫,稍后会导致奇怪的问题。

        【讨论】:

          【解决方案4】:

          使用 ContentCachingRequestWrapper 类。在 thi 中包装 HttpServletRequest 将解决问题

          示例:如果你想转换你的 "HttpServletRequest servletRequest" 你可以做一些类似的事情

          import org.springframework.web.util.ContentCachingRequestWrapper;
          
          ContentCachingRequestWrapper request = new ContentCachingRequestWrapper(servletRequest);
          

          希望对你有帮助!!!

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-07-21
            • 1970-01-01
            • 2023-03-08
            • 1970-01-01
            • 2020-01-08
            • 2012-12-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多