2

I'm new to Helidon framework and started with Helidon 2 with JDK11 for our microservice. As per the requirement we need to invoke external HTTP/HTTPS endpoints and for that started exploring helidon webclient from below resources. https://helidon.io/docs/v2/#/se/webclient/01_introduction

https://medium.com/helidon/helidon-web-client-72e22f5d509a It works fine for success scenarios, However for error use cases (e.g 400. Bad Request), we want to capture the error message that is thrown by the external APIs, but not getting any convenient way to do so and always endup getting the stacktrace

Caused by: io.helidon.webclient.WebClientException: Request failed with code 400
    at io.helidon.webclient.WebClientRequestBuilderImpl.getContentFromClientResponse(WebClientRequestBuilderImpl.java:615)
    at io.helidon.common.reactive.MultiMapperPublisher$MapperSubscriber.onNext(MultiMapperPublisher.java:73)
    at io.helidon.common.reactive.DeferredScalarSubscription.complete(DeferredScalarSubscription.java:91)
    at io.helidon.common.reactive.MultiFromCompletionStage$CompletionStageSubscription.accept(MultiFromCompletionStage.java:72)
    at io.helidon.common.reactive.MultiFromCompletionStage$CompletionStageSubscription.accept(MultiFromCompletionStage.java:52)
    at io.helidon.common.reactive.MultiFromCompletionStage$AtomicBiConsumer.accept(MultiFromCompletionStage.java:95)
    at io.helidon.common.reactive.MultiFromCompletionStage$AtomicBiConsumer.accept(MultiFromCompletionStage.java:88)
    at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
    at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
    at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2073)
    at io.helidon.webclient.NettyClientHandler.lambda$channelRead0$8(NettyClientHandler.java:200)
    at java.base/java.util.concurrent.CompletableFuture.uniRunNow(CompletableFuture.java:815)
    at java.base/java.util.concurrent.CompletableFuture.uniRunStage(CompletableFuture.java:799)
    at java.base/java.util.concurrent.CompletableFuture.thenRun(CompletableFuture.java:2121)
    at io.helidon.webclient.NettyClientHandler.lambda$channelRead0$9(NettyClientHandler.java:193)
    at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
    at java.base/java.util.concurrent.CompletableFuture.uniWhenCompleteStage(CompletableFuture.java:883)
    at java.base/java.util.concurrent.CompletableFuture.whenComplete(CompletableFuture.java:2251)
    at java.base/java.util.concurrent.CompletableFuture.whenComplete(CompletableFuture.java:143)
    at io.helidon.webclient.NettyClientHandler.channelRead0(NettyClientHandler.java:186)
    at io.helidon.webclient.NettyClientHandler.channelRead0(NettyClientHandler.java:61)
    at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:311)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:432)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.logging.LoggingHandler.channelRead(LoggingHandler.java:271)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1504)
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1253)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1300)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:508)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:447)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    ... 1 more

On debugging Further, this is the implementation I'm seeing

    private MessageBodyReadableContent getContentFromClientResponse(WebClientResponse response) {
        if (response.status().code() >= Status.MOVED_PERMANENTLY_301.code()) {
            throw new WebClientException("Request failed with code " + response.status().code());
        } else {
            return response.content();
        }
    }

So it clearly throws anything above 301 and so couldnt get the response content. The external endpoint throws below message with 400 Bad request and we want to capture this message with Helidon webclient.

{
    "errorCode": "FSS-177",
    "errorDescription": "FileSize is too minimum for Multipart upload",
    "errorParameters": []
}

Can anyone help here if I'm missing anything.

Laird Nelson
  • 15,321
  • 19
  • 73
  • 127
Sourabh
  • 43
  • 6

1 Answers1

4

There are two ways to get a response entity with WebClient:

Approach 1 - get entity directly

client.get()
  .uri("http://localhost:8080/greet")
  .request(String.class)
  .forSingle(System.out::println);

This approach directly reads an entity, but as there is not way to represent failures, we throw an exception if the response code is above 400, to let you know something is wrong. If you need entity of failures, you should use

Approach 2 - get response

client.get()
  .uri("http://localhost:8080/greet")
  .request()
  .forSingle(response -> {
    System.out.println(response.status());
    response.content()
      .as(String.class)
      .forSingle(System.out::println);
  });

This approach first gives you a response, which gives you access to status, headers and entity. Then you can read the entity to process it, even if the status is in the error ranges

Tomas Langer
  • 451
  • 3
  • 5
  • I'm using Helidon 2 MP, couldnt use response object inside forSingle method. Below is my current approach. WebClientRequestBuilder builder = webClient.method(method).uri(url); Single t =builder.submit(jsonBody,type) return t.get(); – Sourabh Sep 30 '22 at 11:11
  • 1
    In general, using Helidon reactive webclient in Helidon MP is not recommended, as Helidon MP is usually blocking, so you would be better off using JAX-RS client, or MicroProfile REST Client. You still can use webclient, in next comment I will post code to use in blocking world – Tomas Langer Oct 01 '22 at 17:40
  • 1
    // blocking case, do not use in reactive environment WebClientResponse response = webClient.method(method) .uri(url) .submit(jsonBody) .await(Duration.ofSeconds(10)); if (response.status().family() == Http.ResponseStatus.Family.SUCCESSFUL) { return response.content().as(type); } // read entity of an error ErrorResponse error = response.content().as(ErrorResponse.class); – Tomas Langer Oct 01 '22 at 17:44