[英]java.lang.IllegalStateException: Cannot call getInputStream() after getReader() has already been called for the current request
[英]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.