'Change request body in ServerHttpRequestDecorator using AbstractGatewayFilterFactory - Webflux

Spring Boot Version: 2.5.1, Spring Cloud Version: 2020.0.3

Hello guys !!!

I need your help ...

My question is that I can't modify the request body in spring gateway. Follow:

I have a MobileGatewayFilterFactory class that extends from AbstractGatewayFilterFactory where the apply method returns a custom filter: MobileGatewayFilter.

@Component
class MobileGatewayFilterFactory :
    AbstractGatewayFilterFactory<MobileGatewayFilterFactory.Config>(Config::class.java),
    Ordered {

    override fun apply(config: Config): GatewayFilter {
        logger.info { "Loading MobileGatewayFilter with config ${config.className}, ${config.execution}, ${config.custom}" }
        return MobileGatewayFilter(config)
    }

    override fun getOrder(): Int {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1
    }

    data class Config(
        val className: String,
        val execution: String,
        val custom: String?
    )
}

So, inside the MobileGatewayFilter class I implement the business rules to determine which filter is running: PRE or POST filter. This is done in the filter method of the MobileGatewayFilter class where there is a condition to determine the type of decoration being executed, using reflection. If it is a request, the ServerHttpRequestDecorator is executed and a ServerHttpResponseDecorator otherwise.

class MobileGatewayFilter(private val config: MobileGatewayFilterFactory.Config) : GatewayFilter, Ordered {

    override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
        return when (config.execution) {
            "PRE" -> chain.filter(exchange.mutate().request(decoratorRequest(exchange)).build())
            "POST" -> chain.filter(exchange.mutate().response(decoratorResponse(exchange)).build())
            else -> chain.filter(exchange)
        }
    }

    override fun getOrder(): Int {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1
    }

    private fun decoratorResponse(exchange: ServerWebExchange): ServerHttpResponse {
        val aClass = Class.forName(config.className)
        val obj = aClass.getConstructor(ServerHttpResponse::class.java, MobileGatewayFilterFactory.Config::class.java)
        return obj.newInstance(exchange.response, config) as ServerHttpResponseDecorator
    }

    private fun decoratorRequest(exchange: ServerWebExchange): ServerHttpRequest {
        val aClass = Class.forName(config.className)
        val obj = aClass.getConstructor(ServerHttpRequest::class.java, MobileGatewayFilterFactory.Config::class.java)
        return obj.newInstance(exchange.request, config) as ServerHttpRequestDecorator
    }
}

Furthermore, I have a CustomerDataBodyDecorator that extends the ServerHttpRequestDecorator and overrides the getBody method. The getBody method is where the request body must be modified.

class CustomerDataBodyDecorator(
    private val exchange: ServerHttpRequest,
    private val config: MobileGatewayFilterFactory.Config
) : ServerHttpRequestDecorator(exchange) {

    override fun getBody(): Flux<DataBuffer> {
        logger.info { "getBody chamado ..." }
        val body: Flux<DataBuffer> = exchange.body
        var requestData = ""
        body.subscribe {
            val content = ByteArray(it.readableByteCount())
            it.read(content)
            DataBufferUtils.release(it)
            requestData = String(content, Charset.forName("UTF-8"))
            logger.info { "Request: $requestData" }
        }
        val factory = DefaultDataBufferFactory()
        val buffer = factory.wrap(requestData.toByteArray())
        return Flux.just(buffer)
    }
}

However, the above code doesn't work because the return is executed first with empty requestData and after subscribe method is executed. I know that in Webflux the subscribe method is necessary to indicate to the publisher the information consumption needs

application.yml

id: opengw-mobile-simulation
uri: ${custom.resources.opengw}
predicates:
  - Path=/opengw/v1/mobile/simulation
filters:
  - name: Mobile
    args:
      className: br.com.decorator.CustomerDataBodyDecorator
      execution: PRE
      custom: ${custom.resources.customer}
  - RewritePath=/opengw/v1/(?<segment>/?.*), /$\{segment}

I read several topics here but I couldn't find a solution that worked.

How can I read and then modify the request body of the Flux object in this scenario?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source