problem
ContentCachingRequestWrapper
SecurityContextHolderAwareRequestWrapper
HttpServlet3RequestFactory$Servlet3SecurityContextHolderAwareRequestWrapper
1. fillter -> Body , Param 캐싱
2. Controller -> InputStream 사용
3. Intercepter (로깅) -> 캐싱된 InputStream 사용
InputStream 은 읽으면 다음에는 읽을 수 없다
( 한번은 Controller 에서 읽고 기타 작업들 .. )
로깅을 하려면 InputStream 값을 여러번 읽어야 한다.
Spring 에서는 ContentCachingRequestWrapper 를 제공해 준다.
사용방법은 https://soobysu.tistory.com/203
[Spring] ServletRequest 캐싱 (ContentCachingRequestWrapper)
problem요청처리가 완료된 후 ServletRequest 의 body 를 log 로 남기려고 했다.ServletRequest 는 inputStream 이므로 한번 밖에 읽을 수 없다.Controller 는 RequestBody 로 서블릿 request 를 읽는다. 그래서 인터셉터
soobysu.tistory.com
하지만 3번 에서 HttpServletRequest 을 디버깅 해보면
SecurityContextHolderAwareRequestWrapper 로 래핑되어있다.
Spring Security 를 사용하면 request 를 SecurityContextHolderAwareRequestWrapper 로 래핑을 하게 된다.
solution
구현
Body 와 Param 을 캐싱해줄 CachedHttpServletRequest 를 만들어 주자.
HttpServletRequestWrapper 를 상속받아서 구현해주자.
class CachedHttpServletRequest(request: HttpServletRequest) : HttpServletRequestWrapper(request) {
private var cachedBody: ByteArray
private var multipartFiles: Map<String, MultipartFile> = emptyMap()
private var parameterMap: Map<String, List<String>> = emptyMap()
init {
if (request is MultipartHttpServletRequest) {
request.fileMap.mapValues { it.value }
request.parameterMap.mapValues { it.value.toList() }
}else{
request.inputStream.use { cachedBody = it.readBytes() }
}
}
override fun getInputStream(): ServletInputStream {
return cachedBody?.let { CachedBodyServletInputStream(it) } ?: super.getInputStream()
}
fun getCachedBody(): String? {
return cachedBody?.let { String(it, Charsets.UTF_8) }
}
fun getCachedParameters(): Map<String, List<String>> {
return parameterMap
}
fun getMultipartFiles(): Map<String, MultipartFile> {
return multipartFiles
}
}
필자는 필터에서 래핑을 해주었다.
@Component
class ContentCachedFilter : OncePerRequestFilter(){
private val resolver = StandardServletMultipartResolver()
@Throws(IOException::class)
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val isMultipart = resolver.isMultipart(request)
val wrapper = when {
isMultipart -> {
val multipartRequest = resolver.resolveMultipart(request)
CachedHttpServletRequest(multipartRequest)
}
else ->
CachedHttpServletRequest(request)
}
filterChain.doFilter(wrapper, response)
}
}
사용
사용은 inputStream.readBytes() 으로 그냥 사용해주면 된다.
request.inputStream.readBytes()
private fun extractBody(request: HttpServletRequest): Map<String, Any> {
return try {
val cachedBody = request.inputStream.readBytes()
if (cachedBody.isEmpty()) {
emptyMap()
} else {
objectMapper.readValue(cachedBody, object : TypeReference<Map<String, Any>>() {}
)
}
} catch (e: Exception) {
println("Failed to parse request body: ${e.message}")
emptyMap()
}
}
굿?

problem
ContentCachingRequestWrapper
SecurityContextHolderAwareRequestWrapper
HttpServlet3RequestFactory$Servlet3SecurityContextHolderAwareRequestWrapper
1. fillter -> Body , Param 캐싱
2. Controller -> InputStream 사용
3. Intercepter (로깅) -> 캐싱된 InputStream 사용
InputStream 은 읽으면 다음에는 읽을 수 없다
( 한번은 Controller 에서 읽고 기타 작업들 .. )
로깅을 하려면 InputStream 값을 여러번 읽어야 한다.
Spring 에서는 ContentCachingRequestWrapper 를 제공해 준다.
사용방법은 https://soobysu.tistory.com/203
[Spring] ServletRequest 캐싱 (ContentCachingRequestWrapper)
problem요청처리가 완료된 후 ServletRequest 의 body 를 log 로 남기려고 했다.ServletRequest 는 inputStream 이므로 한번 밖에 읽을 수 없다.Controller 는 RequestBody 로 서블릿 request 를 읽는다. 그래서 인터셉터
soobysu.tistory.com
하지만 3번 에서 HttpServletRequest 을 디버깅 해보면
SecurityContextHolderAwareRequestWrapper 로 래핑되어있다.
Spring Security 를 사용하면 request 를 SecurityContextHolderAwareRequestWrapper 로 래핑을 하게 된다.
solution
구현
Body 와 Param 을 캐싱해줄 CachedHttpServletRequest 를 만들어 주자.
HttpServletRequestWrapper 를 상속받아서 구현해주자.
class CachedHttpServletRequest(request: HttpServletRequest) : HttpServletRequestWrapper(request) {
private var cachedBody: ByteArray
private var multipartFiles: Map<String, MultipartFile> = emptyMap()
private var parameterMap: Map<String, List<String>> = emptyMap()
init {
if (request is MultipartHttpServletRequest) {
request.fileMap.mapValues { it.value }
request.parameterMap.mapValues { it.value.toList() }
}else{
request.inputStream.use { cachedBody = it.readBytes() }
}
}
override fun getInputStream(): ServletInputStream {
return cachedBody?.let { CachedBodyServletInputStream(it) } ?: super.getInputStream()
}
fun getCachedBody(): String? {
return cachedBody?.let { String(it, Charsets.UTF_8) }
}
fun getCachedParameters(): Map<String, List<String>> {
return parameterMap
}
fun getMultipartFiles(): Map<String, MultipartFile> {
return multipartFiles
}
}
필자는 필터에서 래핑을 해주었다.
@Component
class ContentCachedFilter : OncePerRequestFilter(){
private val resolver = StandardServletMultipartResolver()
@Throws(IOException::class)
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val isMultipart = resolver.isMultipart(request)
val wrapper = when {
isMultipart -> {
val multipartRequest = resolver.resolveMultipart(request)
CachedHttpServletRequest(multipartRequest)
}
else ->
CachedHttpServletRequest(request)
}
filterChain.doFilter(wrapper, response)
}
}
사용
사용은 inputStream.readBytes() 으로 그냥 사용해주면 된다.
request.inputStream.readBytes()
private fun extractBody(request: HttpServletRequest): Map<String, Any> {
return try {
val cachedBody = request.inputStream.readBytes()
if (cachedBody.isEmpty()) {
emptyMap()
} else {
objectMapper.readValue(cachedBody, object : TypeReference<Map<String, Any>>() {}
)
}
} catch (e: Exception) {
println("Failed to parse request body: ${e.message}")
emptyMap()
}
}
굿?
