'Why is reactor context throwing "Context does not contain key" when .block() is called?

I have a WebFilter that injects a context

public class MyWebFilter implements WebFilter {
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    return chain.filter(exchange)
      .contextWrite(ctx -> ctx.put("context", "some-context"));
  }
}

In my Controller, I can read it and return it successfully

@RestController
public class MyController {
  @GetMapping(path = "test")
  public Mono<String> test() {
    return Mono.deferContextual(ctx -> Mono.just(ctx.get("context")));
  }
}

However, if I call .block() on it:

Mono.deferContextual(ctx -> Mono.just(ctx.get("context"))).block();

it throws this exception

java.util.NoSuchElementException: Context does not contain key: context

I'm having trouble wrapping my head around the order of execution in the reactive world.



Solution 1:[1]

When you call .block(), that line is subscribed to and evaluated immediately, without any connection to the context provided in the outer chain. When you return without .block, it becomes part of the larger chain which has the context, and isn't evaluated until something subscribes to the outer chain at which time the context is available.

Solution 2:[2]

You're going to want Spring to handle this as 'realistically' as possible. Create a test and use the WebTestClient. (Don't call .block() ever or you'll DoS your server w/a half dozen users. Don't inject/call your controller methods directly, you need all the proxies, filters, etc.)

This is kotlin but you should be able to figure it out:

@SpringBootApplication
class WebfluxScratchpadApplication {
    @Bean
    fun filter(): WebFilter = WebFilter { serverWebExchange, webFilterChain ->
        webFilterChain.filter(serverWebExchange)
            .contextWrite {
                it.put("myContextKey", "!!")
            }
    }
}

@RestController
class MyController {
    @GetMapping("/foo")
    fun foo() = Mono.just("foo")
        .flatMap { s ->
            Mono.deferContextual { ctx ->
                Mono.just(String.format("%s%s", s, ctx["myContextKey"]))
            }
        }
}

fun main(args: Array<String>) {
    runApplication<WebfluxScratchpadApplication>(*args)
}

@WebFluxTest
class WebfluxScratchpadApplicationTests {

    @Autowired
    lateinit var client: WebTestClient

    @Test
    fun stuff() {
        Assertions.assertEquals(
            "foo!!",
            client.get()
                .uri("/foo")
                .exchange()
                .expectBody<String>()
                .returnResult()
                .responseBody!!
        )
    }
}


Sources

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

Source: Stack Overflow

Solution Source
Solution 1 Alexander Taylor
Solution 2 Adam Bickford