java.lang.IllegalStateException:已经为此请求调用了getReader()

时间:2011-09-06 10:45:54

标签: java servlets servlet-filters

我想将记录添加到我的Servlet中,所以我创建了Filter,它应该显示请求并转到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();
    }
}

[MyFilter]

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);
    }
}

4 个答案:

答案 0 :(得分:15)

看起来,restlet框架在Request对象上调用了getRequestEntityStream(),然后调用getInputStream(),因此请求getReader()上的IllegalStateException会抛出 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 。 getReader()和getInputStream()的Servlet API文档说:

getInputStream()

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

答案 1 :(得分:6)

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

答案 2 :(得分: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()));
    }
}

无论如何,这似乎工作正常,直到我们意识到从浏览器上传文件不起作用。我对这些变化进行了分析,发现这是罪魁祸首。

该文章评论中的一些人说你需要覆盖用于参数的方法,但不解释如何做到这一点。

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

然而,在某种程度上,请求正在生效并且进一步搞砸了对请求的理解 - 在我的情况下导致库(extdirectspring)中出现奇怪的错误,其中某些东西试图将内容读作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,所有你得到的东西

我的建议是 - 不要在过滤器中读取请求正文。你将打开一堆蠕虫,以后会引起奇怪的问题。

答案 3 :(得分:1)

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