解决HttpServletRequest.inputStream复用问题

2019-06-23

众所周知,request对象中的inputStream只能读取一次,下次再读就没有了,在一些场景中,这种特性是不适用的:比如需要在拦截器中读取请求体,然后做相关的参数校验,做完校验后请求打到Controller中就读取不到了。

解决的方法很简单,通过HttpServletRequestWrapperHttpServletRequest对象包装一下,保存requestBody的副本,最后通过过滤器将包装后的request对象替换掉。

附完整代码:

/**
 * 包装HttpServletRequest,使其inputStream可复用。
 * @author wuwenze
 * @date 2019-06-21
 */
@Configuration
@Order(1)
@WebFilter(urlPatterns = ["/**"])
class HttpServletRequestWrapperFilter : Filter {
    override fun doFilter(req: ServletRequest?, resp: ServletResponse?, chain: FilterChain?) {
        when (req) {
            is HttpServletRequest -> chain?.doFilter(MyHttpServletRequestWrapper(req), resp)
            else -> chain?.doFilter(req, resp)
        }
    }

    class MyHttpServletRequestWrapper(request: HttpServletRequest) : HttpServletRequestWrapper(request) {
        private var body: ByteArray = request.getBodyString().toByteArray(Charset.forName("UTF-8"))

        @Throws(IOException::class)
        override fun getReader(): BufferedReader {
            return BufferedReader(InputStreamReader(inputStream))
        }

        @Throws(IOException::class)
        override fun getInputStream(): ServletInputStream {
            val byteArrayInputStream = ByteArrayInputStream(body)
            return object : ServletInputStream() {

                @Throws(IOException::class)
                override fun read(): Int {
                    return byteArrayInputStream.read()
                }

                override fun isFinished(): Boolean {
                    return false
                }

                override fun isReady(): Boolean {
                    return false
                }

                override fun setReadListener(readListener: ReadListener) {

                }
            }
        }
    }
}

获取requestBody的扩展函数:

fun HttpServletRequest.getBodyString(): String {
    val result = StringBuffer()
    var reader: BufferedReader? = null
    try {
        var line: String? = null
        reader = BufferedReader(InputStreamReader(this.inputStream, Charset.forName("UTF-8")))
        while ({ line = reader.readLine();line }() != null) {
            result.append(line)
        }
    } catch (e: IOException) {
        // ignore
    } finally {
        reader?.close()
    }
    return result.toString()
}

这样就能多次使用了,每次读取的其实是存储在MyHttpServletRequestWrapper中的副本。