1. 소개
이 예제에서는 Spring을 사용하여 HttpServletRequest 에서 본문을 여러 번 읽는 방법을 배웁니다 .
HttpServletRequest 는 본문을 읽기 위해 getInputStream () 메소드를 노출하는 인터페이스입니다 . 기본적 으로이 InputStream 의 데이터는 한 번만 읽을 수 있습니다 .
2. Maven 의존성
가장 먼저 필요한 것은 적절한 spring-webmvc 및 javax.servlet 의존성입니다.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
또한 application / json 콘텐츠 유형을 사용하고 있으므로 jackson-databind 의존성이 필요합니다.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0</version>
</dependency>
Spring은이 라이브러리를 사용하여 JSON과 변환합니다.
3. Spring의 ContentCachingRequestWrapper
Spring은 ContentCachingRequestWrapper 클래스를 제공합니다 . 이 클래스는 본문을 여러 번 읽을 수있는 getContentAsByteArray () 메소드를 제공합니다 .
하지만이 클래스에는 제한이 있습니다. getInputStream () 및 getReader () 메서드를 사용하여 본문을 여러 번 읽을 수 없습니다 .
이 클래스는 InputStream을 사용 하여 요청 본문을 캐시합니다 . 필터 중 하나 에서 InputStream 을 읽으면 필터 체인의 다른 후속 필터가 더 이상 읽을 수 없습니다. 이러한 제한으로 인해이 클래스는 모든 상황에 적합하지 않습니다.
이 한계를 극복하기 위해 이제보다 범용적인 솔루션을 살펴 보겠습니다.
4. HttpServletRequest 확장
HttpServletRequestWrapper 를 확장 하는 새로운 클래스 CachedBodyHttpServletRequest를 만들어 보겠습니다 . 이렇게하면 HttpServletRequest 인터페이스 의 모든 추상 메서드를 재정의 할 필요가 없습니다 .
HttpServletRequestWrapper 클래스에는 두 개의 추상 메서드 getInputStream () 및 getReader ()가 있습니다. 이 두 메서드를 모두 재정의하고 새 생성자를 만듭니다.
4.1. 생성자
먼저 생성자를 만들어 보겠습니다. 그 안에 실제 InputStream 에서 본문을 읽어서 byte [] 객체 에 저장 합니다.
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}
}
결과적으로 우리는 본문을 여러 번 읽을 수 있습니다.
4.2. getInputStream ()
다음으로 getInputStream () 메서드를 재정의하겠습니다 . 이 메서드를 사용하여 원시 본문을 읽고 객체로 변환합니다.
이 메서드에서 CachedBodyServletInputStream 클래스 ( ServletInputStream 구현)의 새 객체를 생성하고 반환합니다 .
@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream(this.cachedBody);
}
4.3. getReader ()
그런 다음 getReader () 메서드를 재정의합니다 . 이 메서드는 BufferedReader 개체를 반환 합니다.
@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
5. ServletInputStream 구현
ServletInputStream 을 구현할 클래스 CachedBodyServletInputStream을 만들어 보겠습니다 . 이 클래스에서는 새 생성자를 만들고 isFinished () , isReady () 및 read () 메서드 를 재정의합니다 .
5.1. 생성자
먼저 바이트 배열을받는 새 생성자를 만들어 보겠습니다.
그 안에 그 바이트 배열을 사용하여 새로운 ByteArrayInputStream 인스턴스를 생성합니다 . 그런 다음 전역 변수 cachedBodyInputStream에 할당합니다 .
public class CachedBodyServletInputStream extends ServletInputStream {
private InputStream cachedBodyInputStream;
public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
}
5.2. 읽다()
그런 다음 read () 메서드를 재정의합니다 . 이 메서드에서 ByteArrayInputStream # read를 호출합니다 .
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
5.3. 은 끝났어()
그런 다음 isFinished () 메서드를 재정의합니다 . 이 메서드는 InputStream 에 읽을 데이터가 더 있는지 여부를 나타냅니다 . 읽을 수있는 바이트가 0 인 경우 true를 반환 합니다 .
@Override
public boolean isFinished() {
return cachedBody.available() == 0;
}
5.4. 준비되었다()
마찬가지로 isReady () 메서드를 재정의합니다 . 이 메서드는 InputStream 이 읽을 준비가 되었는지 여부를 나타냅니다 .
이미 InputStream 을 바이트 배열로 복사 했으므로 항상 사용할 수 있음을 나타 내기 위해 true 를 반환 합니다 .
@Override
public boolean isReady() {
return true;
}
6. 필터
마지막으로 CachedBodyHttpServletRequest 클래스를 사용하기위한 새 필터를 만들어 보겠습니다 . 여기서는 Spring의 OncePerRequestFilter 클래스를 확장 합니다. 이 클래스에는 추상 메서드 doFilterInternal ()이 있습니다.
이 메소드에서는 실제 요청 객체에서 CachedBodyHttpServletRequest 클래스 의 객체를 생성 합니다 .
CachedBodyHttpServletRequest cachedBodyHttpServletRequest =
new CachedBodyHttpServletRequest(request);
그런 다음 이 새로운 요청 래퍼 객체를 필터 체인에 전달 합니다. 따라서 getInputStream () 메서드 에 대한 모든 후속 호출은 재정의 된 메서드를 호출합니다.
filterChain.doFilter(cachedContentHttpServletRequest, response);
7. 결론
이 사용방법(예제)에서는 ContentCachingRequestWrapper 클래스 를 빠르게 살펴 보았습니다 . 우리는 또한 그 한계를 보았습니다.
그런 다음 HttpServletRequestWrapper 클래스 의 새로운 구현을 만들었습니다 . ServletInputStream 클래스 의 객체를 반환 하도록 getInputStream () 메서드를 재정의했습니다 .
마지막으로 요청 래퍼 개체를 필터 체인에 전달하는 새 필터를 만들었습니다. 그래서 우리는 요청을 여러 번 읽을 수있었습니다.
예제의 전체 소스 코드는 GitHub 에서 찾을 수 있습니다 .
- https://docs.spring.io/spring-framework/docs/current/reference/html
- https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times
'Java' 카테고리의 다른 글
Spring에서 요청 매개 변수로 열거 형 사용 (0) | 2021.04.14 |
---|---|
Spring 웹 애플리케이션의 Flash 속성 사용방법(예제) (0) | 2021.04.14 |
Hibernate Spatial 소개 (0) | 2021.04.14 |
JPA를 사용한 저장 프로 시저 사용방법(예제) (0) | 2021.04.14 |
메모리 내 데이터베이스를 사용한 자체 포함 테스트 (0) | 2021.04.13 |