【问题标题】:Handle Spring WebFlux WebClient timeout in Kotlin在 Kotlin 中处理 Spring WebFlux WebClient 超时
【发布时间】:2021-08-13 14:03:51
【问题描述】:

我正在 Kotlin (1.4.30) 中使用 Spring WebFlux WebClient (Boot 2.4.3) 进行 get http 调用。当请求超时时,它会因异常而失败,但我想返回一个默认值。我看到在retrieve() 之后使用了对onErroronStatus 等的引用,但在我的情况下它们似乎不可用(仅bodytoEntityawaitExchange

电话:

suspend fun conversation(id: String): Conversation =
    client.get().uri("/conversation/{id}", id).retrieve().awaitBody()

带有连接和读取超时的 WebClient 配置:

fun webClient(url: String, connectTimeout: Int, readTimeout: Long, objectMapper: ObjectMapper): WebClient =
    WebClient.builder()
      .baseUrl(url)
      .exchangeStrategies(
        ExchangeStrategies.builder()
          .codecs { configurer -> configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(objectMapper)) }
          .build())
      .clientConnector(
        ReactorClientHttpConnector(
          HttpClient.create()
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout)
            .doOnConnected { connection ->
              connection.addHandlerLast(ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS))
            }))
      .build()

响应模型:

@JsonIgnoreProperties(ignoreUnknown = true)
data class Conversation(
  val replyTimestamp: Map<String, String>,
)

如何在超时而不是因异常而失败时返回默认响应(与空地图的对话)?

更新:

我在下面尝试了 JArgente 的建议:使用 awaitExchange 更新了调用,并设置了有效的 WireMock 响应,延迟时间(1010 毫秒)比超时时间(1000 毫秒)更长。

Result 仍然是 ReadTimeoutException,因此在这种情况下查看 http 状态代码无济于事。

  private val defaultConversation = Conversation(emptyMap())

  suspend fun conversation(id: String): Conversation =
    client.get()
      .uri("/conversation/{id}", id)
      .awaitExchange {
          response -> if (response.statusCode() == HttpStatus.OK)  response.awaitBody() else defaultConversation
      }

回复:

{
  "replyTimestamp": {
    "1": "2021-02-23T15:30:28.753Z",
    "2": "2021-02-23T16:30:28.753Z"
  }
}

模拟配置:

{
  "mappings":
  [
    {
      "priority": 1,
      "request": {
        "method": "GET",
        "urlPathPattern": "/conversation/1"
      },
      "response": {
        "status": 200,
        "fixedDelayMilliseconds": 1010,
        "headers": {
          "content-type": "application/json;charset=utf-8"
        },
        "bodyFileName": "conversation1.json"
      }
    }
  ]
}

【问题讨论】:

  • 当您使用协程时,您可以简单地使用 try-catch 来处理相关异常。

标签: spring kotlin spring-webflux spring-webclient


【解决方案1】:

你得到一个异常是因为在你的方法中,你期望得到一个对话类型的响应,但是因为你收到一个错误,所以正文是不同的。 在这种情况下,您应该处理响应的方式应该是首先查看 HTTP 状态代码,然后相应地转换正文。 这是 spring.io 的一个例子 https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html

在您的情况下,当您收到错误的状态代码时,您应该创建一个新的空对话并返回它

val entity = client.get()
  .uri("/conversation/{id}", id)
  .accept(MediaType.APPLICATION_JSON)
  .awaitExchange {
        if (response.statusCode() == HttpStatus.OK) {
             return response.awaitBody<Conversation>()
        }
        else if (response.statusCode().is4xxClientError) {
             return response.awaitBody<ErrorContainer>()
        }
        else {
             throw response.createExceptionAndAwait()
        }
  }

【讨论】:

  • 查看状态码对超时没有帮助。我根据您的建议更新了问题。
【解决方案2】:

根据 Martin 的建议,最终只是将调用包装在 try/catch 中:

suspend inline fun <reified T : Any> WebClient.ResponseSpec.tryAwaitBodyOrElseLogged(default: T, log: Logger) : T =
  try {
    awaitBody()
  } catch (e: Exception) {
    log.warn("Remote request failed, returning default value ($default)", e)
    default
  }
private val log = LoggerFactory.getLogger(this::class.java)
private val default = Conversation(emptyMap())

suspend fun conversation(id: String): Conversation =
  client.get()
    .uri("/conversation/{id}", id)
    .retrieve()
    .tryAwaitBodyOrElseLogged(default, log)

我认为有一些惯用的方式,但这很好用。

【讨论】:

    猜你喜欢
    • 2019-12-04
    • 2018-08-18
    • 2018-08-06
    • 2021-08-06
    • 2021-05-02
    • 2018-02-24
    • 2018-05-09
    • 1970-01-01
    • 2018-06-14
    相关资源
    最近更新 更多