0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

request.reader.readText()が空?ContentCachingRequestWrapperで解決!

Posted at

request.reader.readText() が空になるのは、@RequestBody アノテーションによって既にリクエストボディが消費されているからです。Springでは、@RequestBody を利用するとリクエストボディが自動的にデシリアライズされ、HttpServletRequest のストリームが消費済み状態になります。

リクエストボディを複数回利用したい場合、以下のようなアプローチを取ることができます。


解決方法 1: @RequestBody とキャッシュ済みリクエストボディを利用

Springの ContentCachingRequestWrapper を利用して、リクエストボディをキャッシュしつつ @RequestBody を使います。

コード例

  • コントローラ
@PostMapping("/api/login/")
fun login(
    @RequestBody loginForm: LoginForm,
    request: HttpServletRequest
): ResponseEntity<Any> {
    // リクエストボディ
    val wrapper = request as ContentCachingRequestWrapper
    val requestBody = String(wrapper.contentAsByteArray, Charsets.UTF_8)

    return ResponseEntity.ok().build()
}

解決方法 2: @RequestBodyHttpServletRequest を同時に利用しない

@RequestBody を削除し、リクエストボディの読み取りとデシリアライズを手動で行います。

コード例

@PostMapping("/api/login/")
fun login(
    request: HttpServletRequest
): ResponseEntity<Any> {
    // リクエストボディ
    val requestBody = request.reader.readText()

    // Formのマッピング
    val mapper = ObjectMapper()
    val loginForm = mapper.readValue(requestBody, LoginForm::class.java)


    return ResponseEntity.ok().build()
}

解決方法 3: カスタム Filter を利用する

リクエストボディをキャッシュする HttpServletRequestWrapper を利用します。この方法はリクエストボディを複数回利用できるようにする汎用的な解決策です。

コード例

  • CachedBodyHttpServletRequest クラス
import java.io.*
import javax.servlet.*
import javax.servlet.http.*

class CachedBodyHttpServletRequest(request: HttpServletRequest) : HttpServletRequestWrapper(request) {
    private val cachedBody: ByteArray

    init {
        cachedBody = request.inputStream.readBytes()
    }

    override fun getInputStream(): ServletInputStream {
        return CachedServletInputStream(cachedBody)
    }

    override fun getReader(): BufferedReader {
        return cachedBody.inputStream().bufferedReader(Charsets.UTF_8)
    }

    private class CachedServletInputStream(private val cachedBody: ByteArray) : ServletInputStream() {
        private val inputStream = ByteArrayInputStream(cachedBody)

        override fun read(): Int = inputStream.read()
        override fun isFinished(): Boolean = inputStream.available() == 0
        override fun isReady(): Boolean = true
        override fun setReadListener(readListener: ReadListener?) {}
    }
}
  • Filter を登録
import javax.servlet.*
import javax.servlet.http.*

class RequestCachingFilter : Filter {
    override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
        if (request is HttpServletRequest) {
            val cachedRequest = CachedBodyHttpServletRequest(request)
            chain.doFilter(cachedRequest, response)
        } else {
            chain.doFilter(request, response)
        }
    }
}
  • フィルターを Spring に登録
@Configuration
class FilterConfig {
    @Bean
    fun requestCachingFilter(): FilterRegistrationBean<RequestCachingFilter> {
        val registrationBean = FilterRegistrationBean(RequestCachingFilter())
        registrationBean.addUrlPatterns("/api/login/*")
        return registrationBean
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?