π1λ¨κ³ : HTTP Request / Response λ‘κ·Έλ₯Ό μ΄λμμ μ²λ¦¬ν΄μΌ ν κΉ?
HTTP Request / Response λ‘κ·Έλ κ³΅ν΅ μ²λ¦¬κ° νμν μμμ λλ€. μ΄λ₯Ό μ²λ¦¬νκΈ° μν λ°©λ²μΌλ‘ μ£Όλ‘ μ¬μ©λλ μΈκ°μ§κ° μμ΅λλ€.
1. Servlet Filter
Servlet Filter λ Dispatcher Servlet μ μ / νμ λμνλ©°, μ¬μ©μμ μμ²μ΄λ μλ΅μ κ°μ₯ λ¨Όμ λ§μ£Όν©λλ€. νν°λ μ€νλ§μ κ³ μ κΈ°λ₯μ΄ μλλΌ μλ° μλΈλ¦Ώμμ μ 곡νλ κΈ°λ₯μ λλ€. Filter λ λμΌν Servlet Container (e.g Tomcat) λ΄μμ νμν μμλ€μ νμ©νμ¬ λμν©λλ€.
2. Handler Interceptor
Interceptor λ Dispatcher Servlet μ΄ μ€νλ ν νΈμΆλ©λλ€. Spring Context μμ κ΄λ¦¬λκΈ° λλ¬Έμ λͺ¨λ λΉ(Bean)μ μ κ·Όμ΄ κ°λ₯νλ€λ μ₯μ μ΄ μμ΅λλ€.
3. AOP
Interceptor μ filter λ μ£Όμλ‘ λμμ ꡬλΆν΄μ κ±Έλ¬λ΄μΌ νλ λ°λ©΄, AOP λ μ£Όμ, νλΌλ―Έν°, μ΄λ Έν μ΄μ λ± λ€μν λ°©λ²μΌλ‘ λμμ μ§μ ν μ μμ΅λλ€. AOP advice μ Handler Interceptor μ κ°μ₯ ν° μ°¨μ΄μ μ νλΌλ―Έν°μ μ°¨μ΄μ μ λλ€. Advice μ κ²½μ° JoinPoint λ ProceedingJointPoint λ±μ νμ©ν΄μ νΈμΆνμ§λ§, HandlerInterceptor λ Filter μ μ μ¬νκ² HttpServletRequest, HttpServletResponse λ₯Ό νλΌλ―Έν°λ‘ μ¬μ©ν©λλ€.
β κ²°λ‘ μ μΌλ‘ μ λ Filter λ₯Ό μ ννμμ΅λλ€.
HTTP μμ²μ Spring μ μΈλΆ μμ, μ¦ μΉ μλ² λ μ΄μ΄μμ μμλ©λλ€. λ§μ½ Aspect μ€ν μ μ μ€λ₯κ° λ°μνλ©΄ HTTP μμ² λ‘κ·Έλ₯Ό λ¨κΈΈ μ μκ² λ©λλ€. AOPμ Interceptor λ Spring μμμμ λμνλ―λ‘ HTTP μμ²/μλ΅ λ‘κ·Έλ₯Ό λ¨κΈ°κΈ° μν΄μλ Filter κ° μ ν©νλ€κ³ νλ¨νμμ΅λλ€.
π 2λ¨κ³ : μμ²λ HTTP μ 보μ μ΄λ»κ² μ κ·Όν΄μΌ ν κΉ?
InputStream μ 1λ²λ§ μ½μ μ μμ΅λλ€. λ°λΌμ μ΄λ―Έ μΊμ± κΈ°λ₯ ꡬνλ ContentCachingRequestWrapper λ₯Ό μ¬μ©νκ±°λ HttpServletRequestWrapper λ₯Ό μμν΄μ μ§μ μΊμ± κΈ°λ₯μ ꡬννλ λ°©λ²μ μ νν μ μμ΅λλ€.
1. ContentCachingRequestWrapper
μ΄ ν΄λμ€λ getContentAsByteArray() λ©μλλ₯Ό μ 곡νμ¬, body λ₯Ό μ¬λ¬λ² μ½μ μ μκ² ν΄μ€λλ€. μ΄ λ©μλλ μ€νΈλ¦Όμ΄ μλΉλ μμ μ μΊμ±λκΈ° λλ¬Έμ, ν λ² read κ° λμ΄μΌλ§ μΊμ±μ΄ μ΄λ£¨μ΄μ§λλ€.
λ¬Έμ μν©
Servlet Filter μμ μμ²μ μΆλ ₯νλ € νμΌλ, request body κ° λΉ κ°μΌλ‘ λμ€λ λ¬Έμ κ° λ°μνμμ΅λλ€.
val cachingRequest = ContentCachingRequestWrapper(request)
val cachingResponse = ContentCachingResponseWrapper(response)
try {
logRequest(cachingRequest)
filterChain.doFilter(cachingRequest, cachingResponse)
} finally {
logResponse(cachingResponse)
cachingResponse.copyBodyToResponse()
}
μμΈμ InputStream μ΄ read λ μμ μμ μΊμ±μ΄ μ΄λ£¨μ΄μ§λλ°, μ΄ μ½λμμλ InputStream μ΄ μμ§ μ½νμ§ μμκΈ° λλ¬Έμ request body κ° λΉμ΄ μμμ΅λλ€. μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ doFilter() μ΄νμ request λ₯Ό μΆλ ₯νλ € νμΌλ, μμΈκ° λ°μνλ©΄ λ‘κ·Έλ₯Ό λ³Ό μ μλ λ¬Έμ κ° λ°μνμμ΅λλ€.
μ΄ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ HttpServletRequestWrapper λ₯Ό μμλ°μ RequestWrapper λ₯Ό 컀μ€ν νμ¬ μ¬μ©νμμ΅λλ€.
2. HttpServletRequestWrapper
class RequestWrapper(request: HttpServletRequest) : HttpServletRequestWrapper(request) {
private val cachedInputStream: ByteArray
init {
val requestInputStream = request.inputStream
cachedInputStream = StreamUtils.copyToByteArray(requestInputStream)
}
override fun getInputStream(): ServletInputStream {
return object : ServletInputStream() {
private val cachedBodyInputStream = ByteArrayInputStream(cachedInputStream)
override fun isFinished(): Boolean {
return try {
cachedBodyInputStream.available() == 0
} catch (e: IOException) {
e.printStackTrace()
false
}
}
override fun isReady(): Boolean {
return true
}
override fun setReadListener(readListener: ReadListener) {
throw UnsupportedOperationException()
}
override fun read(): Int {
return cachedBodyInputStream.read()
}
}
}
}
val cachingRequest = RequestWrapper(request)
val cachingResponse = ContentCachingResponseWrapper(response)
try {
logRequest(cachingRequest)
filterChain.doFilter(cachingRequest, cachingResponse)
} finally {
logResponse(cachingResponse)
cachingResponse.copyBodyToResponse()
MDC.clear()
}