question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Making Spring WebClient calls causes RequestContext.current() to throw IllegalStateException

See original GitHub issue

We are running a Spring Boot WebFlux service written in Kotlin with coroutines. The Armeria Spring Boot starter has been used, with Armeria providing the org.springframework.web.reactive.function.client.WebClient implementation.

In any handler function, we’re seeing that making any HTTP call with the Armeria WebClient causes any subsequent calls to RequestContext.current() throw IllegalStateException with "RequestContext unavailable".

We noticed the issue as we have a decorator to pass on some specific request headers from the originating request to our gRPC endpoints. When we added a WebClient call before making the gRPC calls, the decorator was no longer able to grab the headers as the context is not found.

Here’s a minimal service which reproduces the issue. The first log prints the context, the second one throws IllegalStateException.

import com.linecorp.armeria.common.RequestContext
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.awaitExchange
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.bodyValueAndAwait
import org.springframework.web.reactive.function.server.coRouter

@SpringBootApplication
class ContextBugApplication

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

@Configuration
class ContextClobberingConfiguration(
    webClientBuilder: WebClient.Builder
) {
    private val webClient: WebClient = webClientBuilder.baseUrl("https://httpstat.us").build()
    private val logger = LoggerFactory.getLogger(this::class.java)

    @Bean
    fun routes() = coRouter {
        GET("/").invoke {
            RequestContext.current<RequestContext>().also {
                logger.debug("Request context: $it") // Request context: [sreqId=..., chanId=..., ...]
            }

            webClient.get().uri("/200").awaitExchange()

            RequestContext.current<RequestContext>().also {
                logger.debug("Request context: $it") // throws IllegalStateException
            }

            ServerResponse.ok().bodyValueAndAwait("ok")
        }
    }
}

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:9 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
michaelpearce-atcommented, Feb 15, 2021

Hi @minwoox, thanks for the quick response!

I can confirm that when I explicitly use the coroutine dispatcher (by wrapping the handler code with withContext), I am then able to successfully access the root context before and after making the WebClient call.

val dispatcher = RequestContext.current<RequestContext>().eventLoop().asCoroutineDispatcher()
withContext(dispatcher) {
    webClient.get().uri("/200").awaitExchange { }

    RequestContext.current<RequestContext>().also {
        logger.debug("Request context: $it") // Success
    }

    ServerResponse.ok().bodyValueAndAwait("ok")
}

Is this expected behaviour? We don’t see the same thing when making gRPC calls (using a FutureStub with .await()) - the original request context is preserved afterwards.

0reactions
minwooxcommented, Feb 15, 2021

While we could switch to Armeria annotations, it would be a larger refactor. If it turns out that is the most effective approach, we can tackle that work in the future

I think you don’t have to. 😄 I guess @ikhoon is just telling an option.

Armeria’s documentation about having to manually manage scopes,

Sorry, we need one. 😅 You might want to refer to a slide though. https://github.com/minwoox/deview2020-requestscoping/blob/master/DEVIEW2020_RequestScoping.pdf

Read more comments on GitHub >

github_iconTop Results From Across the Web

Web on Reactive Stack - Spring
If you have a Spring MVC application with calls to remote services, try the reactive WebClient . You can return reactive types (Reactor, ......
Read more >
Get Root/Base Url In Spring MVC - Stack Overflow
public String getURLBase(HttpServletRequest request) throws MalformedURLException { URL requestURL = new URL(request.getRequestURL().
Read more >
Spring Boot Reference Guide
Calling REST Services with WebClient . ... Spring Boot makes it easy to create stand-alone, production-grade ... that is started from a main()...
Read more >
reactor/reactor - Gitter
Ive used spring.io docs to make a reactive rest service but I dont feel super ... but in signals like when a exception...
Read more >
Index (spring-web 5.2.8.RELEASE API) - Javadoc.io
Calls the initFilterBean() method that might contain custom initialization ... AsyncRestTemplate() - Constructor for class org.springframework.web.client.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found