如何使用Micronaut客户端接受自定义HTTP错误代码?

时间:2019-07-08 20:10:09

标签: http-status-codes micronaut

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

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

此外,当尝试使用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)如何避免请求超时并直接抛出。

1 个答案:

答案 0 :(得分:0)

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

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