How to pass context back into the WebFilter when using SpringBootWebFlux with coroutines

1.3k Views Asked by At

Suppose I have the following application with a WebFilter and I'm trying to pass some context back from the controller into the filter.

@RestController
class MyController {
    @PostMapping("/test")
    suspend fun postSomething(): ResponseEntity<Unit> {
        val valueFromFilter = coroutineContext[ReactorContext.Key]?.context?.get<String>("myKey") ?: "EMPTY"
        logger.info { "Inside handler = $valueFromFilter" } // this works since Reactor populate coroutineContext with respective ReactorContext
        coroutineContext[ReactorContext.Key]?.context?.put("handlerKey", "hello")
        return ResponseEntity.ok().build()
    }
}

@Component
class MyFilter : WebFilter {
    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        logger.info { "Before" }
        return chain.filter(exchange)
            .contextWrite(Context.of("myKey", "myValue"))
            .doOnEach {
                val valueFromHandler = if (it.contextView.hasKey("handlerKey")) it.contextView.get<String>("handlerKey") else "EMPTY"
                logger.info { "After handler = $valueFromHandler" } // But this doesn't work since Reactor doesn't restore ReactorContext from respective coroutineContext
            }
    }
}

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    run(MyApplication::class.java, *args)
}

In this example if we made a request to /test it would print the following

Before
Inside handler = myValue
After handler = EMPTY

I understand that when we call chain.filter(exchange) to proceed with the request and the handler is a Kotlin suspend function the framework fills in coroutineContext with ReactorContext.Key to actualReactorContext. Although my question is why doesn't Spring restore the context that I may have filled in the controller back, so I can use it after calling chain.filter(exchange). Moreover if there is anyway possible to do this currently.

1

There are 1 best solutions below

0
On

Just figured out I was looking into the wrong way of doing it. Instead one can simply just use the attributes of ServerWebExchange to pass context around like

@RestController
class MyController {
    @PostMapping("/test")
    suspend fun postSomething(webExchange: ServerWebExchange): ResponseEntity<Unit> {
        val valueFromFilter = webExchange.attributes["myKey"] as? String ?: "EMPTY"
        logger.info { "Inside handler = $valueFromFilter" }
        webExchange.attributes["handlerKey"] = "hello"
        return ResponseEntity.ok().build()
    }
}

@Component
class MyFilter : WebFilter {
    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        logger.info { "Before" }
        exchange.attributes["myKey"] = "myValue"
        return chain.filter(exchange)
            .doOnEach {
                val valueFromHandler = exchange.attributes["handlerKey"] as? String ?: "EMPTY"
                logger.info { "After handler = $valueFromHandler" }
            }
    }
}

This way we can pass context between both the filter and the handler.