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);
}
}
Ответы
Ответ 1
Похоже, что структура restlet вызывала getRequestEntityStream()
в объекте Request, который в свою очередь вызывает getInputStream()
, поэтому вызов getReader()
на запрос вызывает IllegalStateException
. Документация API Servlet для 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()
в вашей обертке.
Ответ 2
Основная проблема заключается в том, что вы не можете читать ввод как двоичный поток, так и поток символов, даже если он вызывается в фильтре, а другой в сервлете.
Ответ 3
Насколько я могу судить, сервлеты в этом отношении принципиально нарушены. Вы можете попробовать и обойти эту проблему, как описано здесь, но это вызывает другие загадочные проблемы, когда другие вещи пытаются и работают с ней.
Фактически он предлагает клонировать запрос, читая тело, а затем в клонированном классе, переопределяя методы 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()));
}
}
В любом случае, похоже, что это работает нормально, пока мы не поняли, что загрузка файла из браузера не работает. Я разделился через изменения и обнаружил, что это преступник.
Некоторые люди в комментариях в этой статье говорят, что вам нужно переопределить методы, связанные с параметрами, но не объясняйте, как это сделать.
В результате я проверил, есть ли разница в двух запросах. Однако после клонирования запроса у него были идентичные наборы параметров (как у исходного запроса +, так и для клонирования), а также с одинаковым набором заголовков.
Однако каким-то образом запрос выполнялся и искажал понимание запроса дальше по строке - в моем случае вызвал ошибку bizaare в библиотеке (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
Используйте класс ContentCachingRequestWrapper. Wrap HttpServletRequest в этом случае решит проблему