如何正确在WebClient中获取自定义错误消息体?

huangapple go评论82阅读模式
英文:

How to get custom error body message in WebClient properly?

问题

What i trying to achieve is to get my response error with 404 code and the error body with WebClient, how do i do this properly?

here is my response with error code 404 and the body response from another API :

{
"timestamp": "2020-09-02T07:36:01.960+00:00",
"message": "Data not found!",
"details": "uri=/api/partnershipment/view"
}

and here is how my consuming code looked like :

Map<String, Long> req = new HashMap<String, Long>();
req.put("id", 2L);

PartnerShipmentDto test = webClient.post()
.uri(urlTest).body(Mono.just(req), PartnerShipmentDto.class)
.exchange()
.flatMap(res -> {
if(res.statusCode().isError()){
res.body((clientHttpResponse, context) -> {
throw new ResourceNotFound(clientHttpResponse.getBody().toString());
});
throw new ResourceNotFound("aaaa");

} else {
    return res.bodyToMono(PartnerShipmentDto.class);
}

})
.block();

and here is my ResourNotFound.java class :

@SuppressWarnings("serial")
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFound extends RuntimeException {

public ResourceNotFound(String message){
    super(message);
}

}

and here is my Global Exception handler using @ControllerAdvice :

@ControllerAdvice
@RestController
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request) {

    ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
    logger.error(ex.getMessage());
    return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}

@ExceptionHandler(ResourceNotFound.class)
public final ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFound ex, WebRequest request) {

    ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
    logger.error(ex.getMessage());
    return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
}

}

but the response i got printed in my ResourceNotFound exception is like this (this is my error from consumer side) :

{
"timestamp": "2020-09-02T07:50:48.132+00:00",
"message": "FluxMap",
"details": "uri=/api/shipmentaddressgrouping/store"
}

it written "FluxMap" only, how i get the "message" field? i would like to get the "timestamp" and "details" field too

英文:

What i trying to achieve is to get my response error with 404 code and the error body with WebClient, how do i do this properly?

here is my response with error code 404 and the body response from another API :

{
  &quot;timestamp&quot;: &quot;2020-09-02T07:36:01.960+00:00&quot;,
  &quot;message&quot;: &quot;Data not found!&quot;,
  &quot;details&quot;: &quot;uri=/api/partnershipment/view&quot;
}

and here is how my consuming code looked like :

    Map&lt;String,Long&gt; req = new HashMap&lt;String,Long&gt;();
    req.put(&quot;id&quot;, 2L);

    PartnerShipmentDto test = webClient.post()
    .uri(urlTest).body(Mono.just(req), PartnerShipmentDto.class)
    .exchange()
    .flatMap(res -&gt; {
        if(res.statusCode().isError()){
            res.body((clientHttpResponse, context) -&gt; {
                throw new ResourceNotFound(clientHttpResponse.getBody().toString());
            });
            throw new ResourceNotFound(&quot;aaaa&quot;);

        } else {
            return res.bodyToMono(PartnerShipmentDto.class);
        }
    })
    .block();

and here is my ResourNotFound.java class :

@SuppressWarnings(&quot;serial&quot;)
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFound extends RuntimeException {
    
    public ResourceNotFound(String message){
        super(message);
    }
    
}

and here is my Global Exception handler using @ControllerAdvice :

@ControllerAdvice
@RestController
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    public final ResponseEntity&lt;Object&gt; handleAllException(Exception ex, WebRequest request) {

        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
        logger.error(ex.getMessage());
        return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(ResourceNotFound.class)
    public final ResponseEntity&lt;Object&gt; handleResourceNotFoundException(ResourceNotFound ex, WebRequest request) {

        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
        logger.error(ex.getMessage());
        return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
    }

}

but the response i got printed in my ResourceNotFound exception is like this (this is my error from consumer side) :

{
  &quot;timestamp&quot;: &quot;2020-09-02T07:50:48.132+00:00&quot;,
  &quot;message&quot;: &quot;FluxMap&quot;,
  &quot;details&quot;: &quot;uri=/api/shipmentaddressgrouping/store&quot;
}

it written "FluxMap" only, how i get the "message" field? i would like to get the "timestamp" and "details" field too

答案1

得分: 2

以下是翻译好的内容:

例子代码中的主要问题在于以下这行代码:

throw new ResourceNotFound(clientHttpResponse.getBody().toString());

这里的类型是 Flux&lt;DataBuffer&gt;,而不是实际的响应主体。这就导致了你所遇到的问题。

解决这个问题的方法是在错误响应主体上调用 bodyToMono 方法,并将其映射到一个 Java 对象上。可以通过 Web 客户端公开的 onStatus 操作符来针对特定的状态码执行特定的操作。

下面的代码片段应该可以解决这个问题:

webClient.post()
        .uri(uriTest).body(Mono.just(req), PartnerShipmentDto.class)
        .retrieve()
        .onStatus(HttpStatus::isError, res -> res.bodyToMono(ErrorBody.class)
                .onErrorResume(e -> Mono.error(new ResourceNotFound("aaaa")))
                .flatMap(errorBody -> Mono.error(new ResourceNotFound(errorBody.getMessage())))
        )
        .bodyToMono(PartnerShipmentDto.class)
        .block();

ErrorBody 类应该包含你想要从 JSON 映射到 Java 对象的所有字段。下面的示例只映射了 "message" 字段。

public class ErrorBody {
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
英文:

The main issue with the example code you have give is the following line of code

throw new ResourceNotFound(clientHttpResponse.getBody().toString());

The type of this is Flux&lt;DataBuffer&gt;, not the actual response body. This is leading to the issue you are seeing.

The way to solve this is invoking the bodyToMono method on the error response body and mapping to a java object. This can be done via the onStatus operator expose from the web client that allows you to take specific actions on specific status codes.

The code snippet below should resolve this

    webClient.post()
            .uri(uriTest).body(Mono.just(req), PartnerShipmentDto.class)
            .retrieve()
            .onStatus(HttpStatus::isError, res -&gt; res.bodyToMono(ErrorBody.class)
                    .onErrorResume(e -&gt; Mono.error(new ResourceNotFound(&quot;aaaa&quot;)))
                    .flatMap(errorBody -&gt; Mono.error(new ResourceNotFound(errorBody.getMessage())))
            )
            .bodyToMono(PartnerShipmentDto.class)
            .block();

The class ErrorBody should contain all of the fields you want to map from json to the java object. The example below only maps the "message" field.

public class ErrorBody {
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

huangapple
  • 本文由 发表于 2020年9月2日 16:06:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/63701274.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定