简体   繁体   English

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

[英]java.lang.IllegalStateException: getReader() has already been called for this request

I want to add logging to my Servlet, so I've created Filter which should display request and go to the Servlet.我想将日志记录添加到我的 Servlet,因此我创建了过滤器,它应该向 Servlet 显示请求和 go。 But unfortunately I've encoutered exception:但不幸的是我遇到了异常:

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)

So to fix this problem I've found solution with Wrapper, but it doesn't work.所以为了解决这个问题,我找到了 Wrapper 的解决方案,但它不起作用。 What else can I use/change in code?我还能在代码中使用/更改什么? Any ideas?有任何想法吗?

[MyHttpServletRequestWrapper] [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);
    }
}

Looks like the restlet framework has called getRequestEntityStream() on the Request object which in turn calls getInputStream() , so calling getReader() on the request throws IllegalStateException .看起来restlet框架已经在请求对象上调用了getRequestEntityStream() ,而后者又调用了getInputStream() ,所以在请求上调用getReader()会抛出IllegalStateException The Servlet API documentation for getReader() and getInputStream() says: 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

From the documentation it seems that we cannot call both getReader() and getInputStream() on the Request object.从文档看来,我们不能在 Request 对象上同时调用 getReader() 和 getInputStream() 。 I suggest you use getInputStream() rather than getReader() in your wrapper.我建议您在包装器中使用getInputStream()而不是getReader()

As far as I can tell servlets are fundamentally broken in this regard.据我所知,servlet 在这方面从根本上被打破了。 You can try and work around this problem as outlined here but that causes other mysterious problems when other things try and work with it.您可以按照此处概述的方法尝试解决此问题,但是当其他事物尝试使用它时,这会导致其他神秘问题。

Effectively he suggests cloning the request, reading the body and then in the the cloned class overriding the getReader and getInputStream methods to return the stuff already retrieved.实际上,他建议克隆请求,读取正文,然后在克隆的类中覆盖 getReader 和 getInputStream 方法以返回已检索的内容。

The code I ended up with was this:我最终得到的代码是这样的:

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

Anyway this appeared to be working fine until we realised that uploading a file from the browser wasn't working.无论如何,这似乎工作正常,直到我们意识到从浏览器上传文件不起作用。 I bisected through the changes and discovered this was the culprit.我一分为二,发现这是罪魁祸首。

Some people in the comments in that article say you need to override methods to do with parameters but don't explain how to do this.在那篇文章的评论中,有些人说你需要覆盖方法来处理参数,但没有解释如何做到这一点。

As a result I checked to see if there was any difference in the two requests.结果我检查了这两个请求是否有任何不同。 However after cloning the request it had identical sets of parameters (both original request + cloned had none) aswell as an identical set of headers.然而,在克隆请求后,它具有相同的参数集(原始请求 + 克隆的都没有)以及相同的标头集。

However in some manner the request was being effected and screwing up the understanding of the request further down the line - in my case causing a bizaare error in a library (extdirectspring) where something was trying to read the contents as Json.然而,以某种方式,请求正在受到影响,并进一步破坏了对请求的理解——在我的情况下,导致库 (extdirectspring) 中出现了一个奇怪的错误,其中有些东西试图将内容读取为 Json。 Taking out the code that read the body in the filter made it work again.取出读取过滤器中主体的代码使其再次工作。

My calling code looked like this:我的调用代码如下所示:

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

I've ommitted the contents of ParseExtDirectTargetFrom but it calls getReader().我ommitted的内容ParseExtDirectTargetFrom但它调用getReader()。

In my case the filter was working for all other requests but the strange behaviour in this case made me realise something wasn't quite right and what I was trying to do (implement sensible exception handling behaviour for tests) wasn't worth potentially breaking random future requests (as I couldn't figure out what had caused the request to become broken).在我的情况下,过滤器适用于所有其他请求,但在这种情况下的奇怪行为让我意识到有些事情不太正确,我正在尝试做的事情(为测试实现合理的异常处理行为)不值得潜在地破坏随机未来的请求(因为我无法弄清楚是什么导致请求被破坏)。

Also it's worth noting that the broken code is unavoidable - I assumed it might be something from spring but ServletRequest goes all the way up - thats all you get even if you were making a servlet from scratch by subclassing HttpServlet另外值得注意的是,损坏的代码是不可避免的——我认为它可能是从 spring 开始的,但 ServletRequest 一直在上升——即使你是通过继承HttpServlet从头开始制作 servlet,你也能得到这一切

My recommendation would be this - don't read the request body in a filter .我的建议是 -不要在 filter 中读取请求正文 You'll be opening up a can of worms that will cause strange problems later on.您将打开一罐蠕虫,稍后会导致奇怪的问题。

Use ContentCachingRequestWrapper class.使用ContentCachingRequestWrapper类。 Wrap HttpServletRequest in thi will resolve issue将 HttpServletRequest 包裹在这将解决问题

Sample : if you want to convert your "HttpServletRequest servletRequest" you can do some thing like示例:如果你想转换你的“HttpServletRequest servletRequest”,你可以做一些类似的事情

import org.springframework.web.util.ContentCachingRequestWrapper;

ContentCachingRequestWrapper request = new ContentCachingRequestWrapper(servletRequest);

Hope it helps!!!希望能帮助到你!!!

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

Well, maybe this is something quite obvious, but I want to share with you this code that work OK for me.好吧,也许这很明显,但我想与您分享这段对我来说工作正常的代码。 In a Spring boot project with JWT, for request of client, was necesary save all requests with their responses in a database table, and the same time authorize the access to consume the resources.在一个 Spring 的 JWT 引导项目中,对于客户端的请求,需要将所有请求及其响应保存在数据库表中,同时授权访问以消耗资源。 Off Course i use getReader() for get request body, but i was obtain java.lang.IllegalStateException...当然,我使用 getReader() 获取请求正文,但我获得了 java.lang.IllegalStateException ...

@Slf4j
@Component
@RequiredArgsConstructor
public class CustomAuthorizationFilter extends OncePerRequestFilter {

private final AuthorizationService authorizationService;
private String requestBody;     

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
    HttpRequestDto requestDto = new HttpRequestDto();
    try {
        if (RequestMethod.POST.name().equalsIgnoreCase(request.getMethod()) && requestBody != null) { //This line and validation is useful for me [requestBody != null]
            requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
        }
        //Do all JWT control
        requestDto.setRequestURI(request.getRequestURI());
        requestDto.setMethod(request.getMethod());
        requestDto.setBody(requestBody);
    }catch (IOException ie) {
        responseError(_3001, response, ie);
    } finally {
        try {
            ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
            filterChain.doFilter(request, responseWrapper);
            saveResponse(responseWrapper, requestDto);
        } catch (ServletException | IOException se) {
            responseError(_3002, response, se);
        }
    }
}


private void saveResponse(ContentCachingResponseWrapper responseWrapper, HttpRequestDto requestDto) {
    try {
        HttpResponseDto responseDto = new HttpResponseDto();
        responseDto.setStatus(responseWrapper.getStatus());
        byte[] responseArray = responseWrapper.getContentAsByteArray();
        String responseBody = new String(responseArray, responseWrapper.getCharacterEncoding());
        responseDto.setBody(responseBody);
        responseWrapper.copyBodyToResponse();
        authorizationService.seveInDatabase(requestDto, responseDto);            
    } catch (Exception e) {
        log.error("Error ServletException | IOException in CustomAuthorizationFilter.saveResponse", e);
    }
}

private void responseError(LogCode code, HttpServletResponse response, Exception e) {
    try {
        Map<String, Object> error = new HashMap<>();
        error.put("log", LogUtil.getLog(code));
        error.put("message", e.getMessage());
        response.setContentType(APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(response.getOutputStream(), error);
        e.printStackTrace();
    } catch (IOException ie) {
        log.error("Error IOException in HttpLoggingFilter.responseError:", ie);
    }
}


public String getRequestBody() {
    return requestBody;
}

public void setRequestBody(String requestBody) {
    this.requestBody = requestBody;
}

}

So my solution was use getter and setter methods of de local attribute requestBody, for validate if this is or not null and does not call again getReader() method because save in memory when set value.所以我的解决方案是使用 de 本地属性 requestBody 的 getter 和 setter 方法,以验证这是否是 null 并且不会再次调用 getReader() 方法,因为在设置值时保存在 memory 中。 This worked perfect for me.这对我来说很完美。 Regards.问候。

Implementing other solutions would possibly lead you to the following exception实施其他解决方案可能会导致您出现以下异常

java.lang.IllegalStateException: getInputStream() has already been called for this request

To read HttpServletRequest, following needs to be implemented.要读取 HttpServletRequest,需要执行以下操作。

Background:背景:

To get the request body from the request, HttpServletRequest is provided with and InputStream class. The getReader() is what is usually used to stream the request.为了从请求中获取请求主体,提供了 HttpServletRequest 和 InputStream class。getReader() 通常用于 stream 请求。 This function internally calls getInputStream() function, which returns us the stream for us to read the request.这个function内部调用了getInputStream() function,返回给我们stream供我们读取请求。 Now, note that its a stream, and can be opened only once.现在,请注意它是一个 stream,并且只能打开一次。 Hence, while reading this (ie implementing the solutions given in this thread) it usually throws "stream is already closed."因此,在阅读本文时(即实施此线程中给出的解决方案),它通常会抛出“流已关闭”。 exception.例外。 Now this happens because, the tomcat server has already opened and read the request once.现在发生这种情况是因为 tomcat 服务器已经打开并读取了一次请求。 Hence, we need to implement a wrapper here, which helps us to re-open an already read stream by keeping an instance of it.因此,我们需要在这里实现一个包装器,它可以帮助我们通过保留一个实例来重新打开一个已经读取过的 stream。 This wrapper again, cannot be used directly, instead, needs to be added at spring filter level, while the tomcat server is reading it.同样,这个包装器不能直接使用,而是需要在 spring 过滤器级别添加,而 tomcat 服务器正在读取它。

Code:代码:

Servlet Request Wrapper Class: Servlet 请求包装器 Class:

import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
@Slf4j
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final String body;public MyHttpServletRequestWrapper(HttpServletRequest request) {
    super(request);

    StringBuilder stringBuilder = new StringBuilder();
    BufferedReader bufferedReader = null;

    try {
        InputStream inputStream = request.getInputStream();

        if (inputStream != null) {
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

            char[] charBuffer = new char[128];
            int bytesRead = -1;

            while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                stringBuilder.append(charBuffer, 0, bytesRead);
            }
        } else {
            stringBuilder.append("");
        }
    } catch (IOException ex) {
        log.error("Error reading the request body...");
    } finally {
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException ex) {
                log.error("Error closing bufferedReader...");
            }
        }
    }

    body = stringBuilder.toString();
}

@Override
public ServletInputStream getInputStream () {
    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());

    ServletInputStream inputStream = new ServletInputStream() {
        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }

        public int read () throws IOException {
            return byteArrayInputStream.read();
        }
    };

    return inputStream;
}

@Override
public BufferedReader getReader(){
    return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}

Now we need to use the wrapper my implementing it in a filter, as shown below.现在我们需要使用包装器在过滤器中实现它,如下所示。

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
@Component
@Order(1)
@Slf4j
public class ServletFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (servletRequest instanceof HttpServletRequest) {
            requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest) servletRequest);
        }
        if (Objects.isNull(requestWrapper)){
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            filterChain.doFilter(requestWrapper, servletResponse);
        }
}
}

Then, the suggested implementations can be used as follows to get the request body as following:然后,可以按如下方式使用建议的实现来获取请求正文,如下所示:

    private String getRequestBody(HttpServletRequest request) {
    try {
        return request.getReader().lines().collect(Collectors.joining());
    }catch (Exception e){
        e.printStackTrace();
        return "{}";
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 java.lang.IllegalStateException:在为当前请求调用 getReader() 后无法调用 getInputStream() - java.lang.IllegalStateException: Cannot call getInputStream() after getReader() has already been called for the current request java.lang.IllegalStateException:此请求已调用getInputStream() - java.lang.IllegalStateException: getInputStream() has already been called for this request java.lang.IllegalStateException:已为此响应调用getWriter() - java.lang.IllegalStateException: getWriter() has already been called for this response java.lang.IllegalStateException:已经为此响应调用了 getOutputStream() - java.lang.IllegalStateException: getOutputStream() has already been called for this response java.lang.IllegalStateException:已为此响应调用了getOutputStream() - java.lang.IllegalStateException: getOutputStream() has already been called for this response Servlet - java.lang.IllegalStateException:已为此响应调用了getWriter() - Servlet - java.lang.IllegalStateException: getWriter() has already been called for this response 原因:java.lang.IllegalStateException:此响应已经调用了getOutputStream()。 - Caused by: java.lang.IllegalStateException: getOutputStream() has already been called for this response org.apache.jasper.JasperException:java.lang.IllegalStateException:此响应已调用getOutputStream() - org.apache.jasper.JasperException: java.lang.IllegalStateException: getOutputStream() has already been called for this response Spring:java.lang.IllegalStateException:已经为此响应调用了getOutputStream() - Spring: java.lang.IllegalStateException: getOutputStream() has already been called for this response java.lang.IllegalStateException:此响应已经被调用getOutputStream()-SpringBoot - java.lang.IllegalStateException: getOutputStream() has already been called for this response - SpringBoot
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM