簡體   English   中英

java.lang.IllegalStateException: 已經為此請求調用了getReader()

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

我想將日志記錄添加到我的 Servlet,因此我創建了過濾器,它應該向 Servlet 顯示請求和 go。 但不幸的是我遇到了異常:

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

[我的過濾器]

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

看起來restlet框架已經在請求對象上調用了getRequestEntityStream() ,而后者又調用了getInputStream() ,所以在請求上調用getReader()會拋出IllegalStateException 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

從文檔看來,我們不能在 Request 對象上同時調用 getReader() 和 getInputStream() 。 我建議您在包裝器中使用getInputStream()而不是getReader()

據我所知,servlet 在這方面從根本上被打破了。 您可以按照此處概述的方法嘗試解決此問題,但是當其他事物嘗試使用它時,這會導致其他神秘問題。

實際上,他建議克隆請求,讀取正文,然后在克隆的類中覆蓋 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);
    }
}

我ommitted的內容ParseExtDirectTargetFrom但它調用getReader()。

在我的情況下,過濾器適用於所有其他請求,但在這種情況下的奇怪行為讓我意識到有些事情不太正確,我正在嘗試做的事情(為測試實現合理的異常處理行為)不值得潛在地破壞隨機未來的請求(因為我無法弄清楚是什么導致請求被破壞)。

另外值得注意的是,損壞的代碼是不可避免的——我認為它可能是從 spring 開始的,但 ServletRequest 一直在上升——即使你是通過繼承HttpServlet從頭開始制作 servlet,你也能得到這一切

我的建議是 -不要在 filter 中讀取請求正文 您將打開一罐蠕蟲,稍后會導致奇怪的問題。

使用ContentCachingRequestWrapper類。 將 HttpServletRequest 包裹在這將解決問題

示例:如果你想轉換你的“HttpServletRequest servletRequest”,你可以做一些類似的事情

import org.springframework.web.util.ContentCachingRequestWrapper;

ContentCachingRequestWrapper request = new ContentCachingRequestWrapper(servletRequest);

希望能幫助到你!!!

主要問題是您不能同時讀取二進制流和字符流的輸入,即使在過濾器中調用一個而在 servlet 中調用另一個也是如此。

好吧,也許這很明顯,但我想與您分享這段對我來說工作正常的代碼。 在一個 Spring 的 JWT 引導項目中,對於客戶端的請求,需要將所有請求及其響應保存在數據庫表中,同時授權訪問以消耗資源。 當然,我使用 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;
}

}

所以我的解決方案是使用 de 本地屬性 requestBody 的 getter 和 setter 方法,以驗證這是否是 null 並且不會再次調用 getReader() 方法,因為在設置值時保存在 memory 中。 這對我來說很完美。 問候。

實施其他解決方案可能會導致您出現以下異常

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

要讀取 HttpServletRequest,需要執行以下操作。

背景:

為了從請求中獲取請求主體,提供了 HttpServletRequest 和 InputStream class。getReader() 通常用於 stream 請求。 這個function內部調用了getInputStream() function,返回給我們stream供我們讀取請求。 現在,請注意它是一個 stream,並且只能打開一次。 因此,在閱讀本文時(即實施此線程中給出的解決方案),它通常會拋出“流已關閉”。 例外。 現在發生這種情況是因為 tomcat 服務器已經打開並讀取了一次請求。 因此,我們需要在這里實現一個包裝器,它可以幫助我們通過保留一個實例來重新打開一個已經讀取過的 stream。 同樣,這個包裝器不能直接使用,而是需要在 spring 過濾器級別添加,而 tomcat 服務器正在讀取它。

代碼:

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

現在我們需要使用包裝器在過濾器中實現它,如下所示。

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

然后,可以按如下方式使用建議的實現來獲取請求正文,如下所示:

    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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM