首页 > 解决方案 > 如何使用 Micronaut 客户端接受自定义 HTTP 错误代码?

问题描述

我正在为外部 API 设置 HTTP 客户端。如果发送的有效负载出现错误(310 到 323),该服务器会出现异常状态代码。

然而,Micronaut 的 HTTP 客户端在尝试解析该响应时会抛出 IllegalArgumentException,因为它的已知状态代码列表有限 ( https://github.com/micronaut-projects/micronaut-core/blob/master/http/src/main /java/io/micronaut/http/HttpStatus.java )

此外,当尝试使用 JavaRx 库编写异步客户端时,该错误不会导致立即抛出错误。相反,它会挂起几秒钟并引发 ReadTimeoutException。

我尝试了 Kotlin 扩展来扩展 HttpStatus 枚举,但失败了。不确定它的使用方式是否真的有可能。

这是客户端代码:

@Context
@Requires(beans = [FooConfiguration::class])
class FooClient(
    @Client("foo") private val httpClient: RxHttpClient,
    private val config: FooConfiguration
) {
    private val quoteOrderEndpoint = "/api/quote"

    private val AUTH_HEADER = "x-foo-bar"

    fun quoteOrder(quoteRequest: QuoteOrderRequest): Single<QuoteOrderResponse> {
        val body = JsonFormat.printer().print(quoteRequest)
        val httpRequest = HttpRequest.POST<Any>(quoteOrderEndpoint, body)
            .header(AUTH_HEADER, config.apiKey)

        return httpClient.exchange(httpRequest, String::class.java)
            .map { response ->
                val builder = QuoteOrderResponse.newBuilder()
                JsonFormat.parser().merge(response.body(), builder)
                builder.build()
            }.singleOrError()
    }
}

@ConfigurationProperties("${ServiceHttpClientConfiguration.PREFIX}.foo")
class FooConfiguration {
    lateinit var apiKey: String
}

这是测试代码:

class FooClientTest {
    lateinit var client: FooClient
    lateinit var config: FooConfiguration

    @BeforeAll
    fun setup() {
        val rxHttpClient = RxHttpClient.create(URL("https://sandbox.foo.com"))
        config = FooConfiguration()
        config.apiKey = "***"
        client = FooClient(rxHttpClient, config)
    }

    @Test
    fun testQuoteOrder() {
        // This payload is incomplete and the API returns a 310 error.
        val request = QuoteOrderRequest.newBuilder()
            .build()
        val response = client.quoteOrder(request).blockingGet()
        assertEquals(request.customer.country, response.customer.country)
    }
}

这是完整的堆栈跟踪:

17:00:41.446 [multithreadEventLoopGroup-1-2] WARN  i.n.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
java.lang.IllegalArgumentException: Invalid HTTP status code: 310
    at io.micronaut.http.HttpStatus.valueOf(HttpStatus.java:146)
    at io.micronaut.http.client.FullNettyClientHttpResponse.<init>(FullNettyClientHttpResponse.java:82)
    at io.micronaut.http.client.DefaultHttpClient$10.channelRead0(DefaultHttpClient.java:1764)
    at io.micronaut.http.client.DefaultHttpClient$10.channelRead0(DefaultHttpClient.java:1725)
    at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.micronaut.http.netty.stream.HttpStreamsHandler.channelRead(HttpStreamsHandler.java:185)
    at io.micronaut.http.netty.stream.HttpStreamsClientHandler.channelRead(HttpStreamsClientHandler.java:180)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297)
    at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1429)
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1199)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1243)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:502)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:441)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:278)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:644)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:579)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:496)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:458)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)
17:00:41.463 [multithreadEventLoopGroup-1-2] WARN  io.sentry.dsn.Dsn - *** Couldn't find a suitable DSN, Sentry operations will do nothing! See documentation: https://docs.sentry.io/clients/java/ ***
17:00:41.470 [multithreadEventLoopGroup-1-2] WARN  io.sentry.DefaultSentryClientFactory - No 'stacktrace.app.packages' was configured, this option is highly recommended as it affects stacktrace grouping and display on Sentry. See documentation: https://docs.sentry.io/clients/java/config/#in-application-stack-frames

io.micronaut.http.client.exceptions.ReadTimeoutException: Read Timeout

    at io.micronaut.http.client.exceptions.ReadTimeoutException.<clinit>(ReadTimeoutException.java:26)
    at io.micronaut.http.client.DefaultHttpClient.lambda$null$28(DefaultHttpClient.java:1071)
    at io.reactivex.internal.operators.flowable.FlowableOnErrorNext$OnErrorNextSubscriber.onError(FlowableOnErrorNext.java:103)
    at io.reactivex.internal.operators.flowable.FlowableTimeoutTimed$TimeoutSubscriber.onTimeout(FlowableTimeoutTimed.java:139)
    at io.reactivex.internal.operators.flowable.FlowableTimeoutTimed$TimeoutTask.run(FlowableTimeoutTimed.java:170)
    at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
    at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

所以,问题是:

1) 如何向 HTTP 客户端添加自定义状态代码。

2)如何避免请求超时直接抛出。

标签: http-status-codesmicronaut

解决方案


这是一个错误http-client,显然没有解决方法。

https://github.com/micronaut-projects/micronaut-core/issues/2189


推荐阅读