Programing/JAVA

getReader() has already been called for this request 해결 방법

리커니 2023. 5. 25.
반응형

java.lang.IllegalStateException: getReader() has already been called for this request 의 원인과

해결방법을 알아보겠습니다.

 

원인

위의 Exception은 request.getReader()를 한번 이상 사용할 때 발생합니다.

request.getReader() 를 사용하게 되면 request body 를 읽기위한 스트림을 반환하고, 읽는동안 내부적으로 포인트를 사용하여 읽은 위치를 기억하게 됩니다. 

처음 다 읽은 후 두번째 읽을 때는 이미 포인터가 body의 마지막부분을 기억하고 있기 때문에 읽을 데이터가 없다고 판단하게 되는 것이죠.

 

예를들어 Interceptor에서 아래와 같은 코드로 body의 데이터를 조회한다고 할 때

이미 인터셉터에서 request.getReader()로 body 데이터에 접근하기 때문에

Controller에서 @RequestBody로 접근을 하게되면 body에 데이터가 없어 body가 누락되었다고 판단하게 됩니다.

 

[Interceptor]

BufferedReader reader = request.getReader();
StringBuilder requestBody = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
    requestBody.append(line);
}
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(requestBody.toString());
String col = jsonNode.get("col").asText();

[Controller]

@PostMapping(value = "/test", consumes = MediaType.APPLICATION_JSON_VALUE, 
	produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "getReader() test", notes = "getReader() test")
@CustomApiResponse
public ResponseEntity<?> loginCheckByApple(@Valid @RequestBody TestDTO param) {
//...
}

Interceptor와 아래서 설명할 Filter에 대해서는 아래의 Link를 참고하세요!

Link : https://aljjabaegi.tistory.com/632

 

Springboot MVC Filter, Interceptor, AOP 차이 실행시점 구현방법

Springboot Filter, Interceptor, AOP 차이 실행시점 구현방법 이번 포스팅에서는 Springboot MVC 모델을 활용해 Filter, Interceptor, AOP를 구현해보고 차이점과 실행시점에 대해서 알아보도록 하겠습니다. 우선 Spr

aljjabaegi.tistory.com

 

해결

HttpServletRequestWrapper를 사용하여 HttpRequest에서 inputStream을 읽어 저장 한 뒤 다음 요청부터는 저장된 inputStream을 복사하여 전달함으로써 문제를 해결할 수 있습니다.

 

1. HttpServletRequestWrapper 구현

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.lang3.StringUtils;
import org.zeroturnaround.zip.commons.IOUtils;

public class CustomRequestWrapper extends HttpServletRequestWrapper {

    private final Charset encoding;
    private byte[] rawData;

    public CustomRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);

        String characterEncoding = request.getCharacterEncoding();
        if (StringUtils.isBlank(characterEncoding)) {
            characterEncoding = StandardCharsets.UTF_8.name();
        }
        this.encoding = Charset.forName(characterEncoding);

        try {
            InputStream inputStream = request.getInputStream();
            this.rawData = IOUtils.toByteArray(inputStream);
        } catch (IOException e) {
            throw e;
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.rawData);
        ServletInputStream servletInputStream = 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 servletInputStream;
    }

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

    @Override
    public ServletRequest getRequest() {
        return super.getRequest();
    }
}

 

2. Filter 등록

특정 URI에서만 동작, 혹은 모든 URI에서 동작하도록 Filter를 등록하여 filter에서 CustomRequestWrapper를 생성해 전달합니다. (/test/...로 시작하는 모든 URI에서 동작)

 

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

@WebFilter(urlPatterns="/test/*")
public class RequestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        CustomRequestWrapper rereadableRequestWrapper = new CustomRequestWrapper((HttpServletRequest) request);
        chain.doFilter(rereadableRequestWrapper, response);
    }
}

 

Filter 동작을 위해서 Springboot application main class에서 @ServletComponentScan을 추가합니다.

 

@SpringBootApplication
@ServletComponentScan
public class TestApplication extends SpringBootServletInitializer {
//...
}

 

 

 

참고 : https://meetup.nhncloud.com/posts/44

 

Spring Interceptor(혹은 Servlet Filter)에서 POST 방식으로 전달된 JSON 데이터 처리하기 : NHN Cloud Meetup

Spring Interceptor(혹은 Servlet Filter)에서 POST 방식으로 전달된 JSON 데이터 처리하기

meetup.nhncloud.com

 

반응형

댓글

💲 추천 글