首页 > 解决方案 > 如何编写自定义 GlobalFilter 以检查 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卡住并且不转发请求。

我怎样才能正确解码身体?

标签: kotlinspring-cloudspring-cloud-gateway

解决方案


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

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成功。


推荐阅读