如何编写自定义GlobalFilter以便在Spring Cloud Gateway中检查请求正文?

时间:2019-11-14 15:56:41

标签: kotlin spring-cloud spring-cloud-gateway

我想验证GlobalFilter中的正文。

我需要读取两个包含正文校验和的http标头,并将其与正文本身进行比较:

internal class MyFilter : GlobalFilter {

    override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain) =
        ByteArrayDecoder()
            .decodeToMono(
                exchange.request.body,
                ResolvableType.forClass(ByteBuffer::class.java),
                exchange.request.headers.contentType,
                null
            )
            .flatMap { /* my logic checking body against request headers */ chain.filter(exchange) }
}

问题是decodingToMono卡住了,无法转发请求。

我如何正确解码身体?

1 个答案:

答案 0 :(得分:0)

我设法编写了一个在读取正文后不会卡住的过滤器:

interface BodyFilter {
    fun filter(
        body: Mono<ByteArrayResource>,
        exchange: ServerWebExchange,
        passRequestFunction: () -> Mono<Void>
    ): Mono<Void>
}

class HeaderAndBodyGlobalFilter(private val bodyFilter: BodyFilter) : GlobalFilter {

    private val messageReaders: List<HttpMessageReader<*>> = HandlerStrategies.withDefaults().messageReaders()

    override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
        val serverRequest: ServerRequest = ServerRequest.create(exchange, messageReaders)
        val body: Mono<ByteArrayResource> = serverRequest.bodyToMono<ByteArrayResource>(ByteArrayResource::class.java)
        return bodyFilter.filter(body, exchange) { reconstructRequest(body, exchange, chain) }
    }

    private fun reconstructRequest(
        body: Mono<ByteArrayResource>,
        exchange: ServerWebExchange,
        chain: GatewayFilterChain
    ): Mono<Void> {
        val headers: HttpHeaders = writableHttpHeaders(exchange.request.headers)
        val outputMessage = CachedBodyOutputMessage(exchange, headers)

        return BodyInserters.fromPublisher(
            body,
            ByteArrayResource::class.java
        ).insert(outputMessage, BodyInserterContext())
            .then(Mono.defer {
                val decorator: ServerHttpRequestDecorator = decorate(
                    exchange, headers, outputMessage
                )
                chain
                    .filter(exchange.mutate().request(decorator).build())
            })
    }

    private fun decorate(
        exchange: ServerWebExchange,
        headers: HttpHeaders,
        outputMessage: CachedBodyOutputMessage
    ): ServerHttpRequestDecorator {
        return object : ServerHttpRequestDecorator(exchange.request) {
            override fun getHeaders(): HttpHeaders {
                val contentLength = headers.contentLength
                val httpHeaders = HttpHeaders()
                httpHeaders.putAll(super.getHeaders())
                if (contentLength > 0) {
                    httpHeaders.contentLength = contentLength
                } else {
                    // TODO: this causes a 'HTTP/1.1 411 Length Required' // on
                    // httpbin.org
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked")
                }
                return httpHeaders
            }

            override fun getBody(): Flux<DataBuffer> {
                return outputMessage.body
            }
        }
    }
}

然后BodyFilter的实现在失败时返回Mono.empty()或在成功时调用passRequestFunction