it-swarm.com.ru

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

Похоже, что структура рестлета вызвала getRequestEntityStream() для объекта Request, который, в свою очередь, вызывает getInputStream(), поэтому вызов getReader() для запроса выдает IllegalStateException. Документация API сервлета для getReader () и getInputStream () гласит:

 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 () для объекта Request. Я предлагаю вам использовать getInputStream() вместо getReader() в вашей оболочке.

13
Suresh Kumar

Основная проблема заключается в том, что вы не можете прочитать входные данные как двоичный поток, так и поток символов, даже если один вызывается в фильтре, а другой - в сервлете. 

5
Kennet

Насколько я могу судить, сервлеты в этом отношении принципиально сломаны. Вы можете попытаться обойти эту проблему, как изложено здесь , но это вызывает другие загадочные проблемы, когда другие вещи пытаются работать с ней.

По сути, он предлагает клонировать запрос, прочитать тело, а затем в клонированном классе переопределить методы 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 ().

В моем случае фильтр работал для всех других запросов, но странное поведение в этом случае заставило меня понять, что что-то было не совсем правильно, и то, что я пытался сделать (реализовать разумное поведение обработки исключений для тестов), не стоило потенциально разбивать случайным образом. будущие запросы (так как я не мог понять, что вызвало сбой запроса).

Также стоит отметить, что неработающий код неизбежен - я предположил, что это может быть что-то из Spring, но ServletRequest идет полным ходом - это все, что вы получаете, даже если вы делали сервлет с нуля, создав подклассы HttpServlet

Моя рекомендация будет такой - не читать тело запроса в фильтре. Вы откроете банку с червями, которые позже вызовут странные проблемы.

4
JonnyRaa

Используйте ContentCachingRequestWrapper class. Оберните HttpServletRequest в это, чтобы решить проблему

0
Swarit Agarwal