web-dev-qa-db-ja.com

java.lang.IllegalStateException:getReader()はこのリクエストに対してすでに呼び出されています

サーブレットにロギングを追加したいので、リクエストを表示してサーブレットに移動するフィルターを作成しました。しかし、残念ながら例外を発見しました:

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

RestletフレームワークがRequestオブジェクトでgetRequestEntityStream()を呼び出し、次にgetInputStream()を呼び出しているため、要求でgetReader()を呼び出すとIllegalStateExceptionがスローされます。 getReader()およびgetInputStream()のサーブレット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()を使用することをお勧めします。

18
Suresh Kumar

主な問題は、1つがフィルターで呼び出され、もう1つがサーブレットで呼び出されても、バイナリストリームと文字ストリームの両方として入力を読み取ることができないことです。

6
Kennet

私が知る限り、サーブレットはこの点で根本的に壊れています。 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()));
    }
}

とにかく、ブラウザーからのファイルのアップロードが機能していないことに気づくまで、これは正常に機能しているように見えました。私は変化を二分し、これが犯人であることを発見しました。

その記事のコメントの一部の人々は、パラメーターを使用するためにメソッドをオーバーライドする必要があると述べていますが、これを行う方法を説明していません。

その結果、2つのリクエストに違いがあるかどうかを確認しました。ただし、リクエストのクローンを作成した後は、同じパラメーターセット(元のリクエスト+クローンにはどちらもなかった)と同じヘッダーセットがありました。

しかし、何らかの方法でリクエストが実行されており、リクエストの理解をさらに混乱させています-私の場合、何かがJsonとしてコンテンツを読み取ろうとしていたライブラリ(extdirectspring)でbizaareエラーが発生しました。フィルターで本体を読み取るコードを削除すると、フィルターは再び機能します。

私の呼び出しコードは次のようになりました:

@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

私の推薦はこれです-フィルターでリクエストボディを読まないでください。後で奇妙な問題を引き起こすワームの缶を開けます。

5
JonnyRaa

ContentCachingRequestWrapperクラスを使用します。これでHttpServletRequestをラップすると問題が解決します

Sample"HttpServletRequest servletRequest"を変換したい場合、次のようなことができます

import org.springframework.web.util.ContentCachingRequestWrapper;

ContentCachingRequestWrapper request = new ContentCachingRequestWrapper(servletRequest);

それが役に立てば幸い!!!

2
Swarit Agarwal