Как мне выйти из тела неудавшегося ответа на запрос Spring WebFlux WebClient при возврате ответа вызывающей стороне? - PullRequest
1 голос
/ 06 мая 2020

Я новичок в реактивном программировании, и у меня есть служба REST, которая принимает запрос, а затем вызывает другой API с помощью WebFlux WebClient. Когда API отвечает ответом 4xx или 5xx, я хочу зарегистрировать тело ответа в своей службе, а затем передать ответ вызывающей стороне. Я нашел несколько способов ведения журнала ответа, но они обычно возвращают вызывающему объекту Mono.error, а я не хочу этого делать. У меня это почти работает, но когда я делаю запрос в свою службу, пока я возвращаю код 4xx, который вернул API, мой клиент просто зависает, ожидая тела ответа, и служба, похоже, никогда не завершает обработку потока . Я использую Spring Boot версии 2.2.4.RELEASE.

Вот что у меня есть:

Контроллер:

@PostMapping(path = "create-order")
public Mono<ResponseEntity<OrderResponse>> createOrder(@Valid @RequestBody CreateOrderRequest createOrderRequest) {
    return orderService.createOrder(createOrderRequest);
}

Сервис:

public Mono<ResponseEntity<OrderResponse>> createOrder(CreateOrderRequest createOrderRequest) {
    return this.webClient
            .mutate()
            .filter(OrderService.errorHandlingFilter(ORDERS_URI, createOrderRequest))
            .build()
            .post()
            .uri(ORDERS_URI)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(createOrderRequest)
            .exchange()
            .flatMap(response -> response.toEntity(OrderResponse.class));
}

public static ExchangeFilterFunction errorHandlingFilter(String uri, CreateOrderRequest request) {
    return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
        if (clientResponse.statusCode() != null && (clientResponse.statusCode().is5xxServerError() || clientResponse.statusCode().is4xxClientError())) {
            return clientResponse.bodyToMono(String.class)
                    .flatMap(errorBody -> OrderService.logResponseError(clientResponse, uri, request, errorBody));
        } else {
            return Mono.just(clientResponse);
        }
    });
}

static Mono<ClientResponse> logResponseError(ClientResponse response, String attemptedUri, CreateOrderRequest orderRequest, String responseBody) {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    try {
        log.error("Response code {} received when attempting to hit {}, request:{}, response:{}",
                response.rawStatusCode(), attemptedUri, objectMapper.writeValueAsString(orderRequest),
                responseBody);
    } catch (JsonProcessingException e) {
        log.error("Error attempting to serialize request object when reporting on error for request to {}, with code:{} and response:{}",
                attemptedUri, response.rawStatusCode(), responseBody);
    }
    return Mono.just(response);
}

Как видите, я просто пытаюсь вернуть Mono исходного ответа из метода logResponseError. Для своего тестирования я отправляю тело с плохим элементом, что приводит к ответу 422 Unprocessable Entity от конечной точки ORDERS_URI в API, который я вызываю. Но по какой-то причине, хотя клиент, который вызвал конечную точку порядка создания, получает 422, он никогда не получает тело. Если я изменю значение return в методе logResponseError на

return Mono.error(new Exception("Some error"));

, я получаю 500 на клиенте, и запрос завершается. Если кто-нибудь знает, почему он не завершается, когда я пытаюсь отправить ответ, я хотел бы знать, что я делаю не так.

1 Ответ

1 голос
/ 06 мая 2020

Не могу и пирог съесть!

Проблема в том, что вы дважды пытаетесь получить тело ответа, что недопустимо. Обычно при этом возникает ошибка.

Один раз в

return clientResponse.bodyToMono(String.class) 

, но также и в

response.toEntity(OrderResponse.class)

, который на самом деле запускает

@Override
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
    return WebClientUtils.toEntity(this, bodyToMono(bodyType));
}

Таким образом, одним из решений было бы обрабатывать ResponseEntity вместо ClientResponse следующим образом, поскольку вы на самом деле не хотите делать какие-либо реактивные вещи с телом

public Mono<ResponseEntity<OrderResponse>> createOrder(CreateOrderRequest createOrderRequest) {
    return this.webClient
            //no need for mutate unless you already have things specified in 
            //base webclient?
            .post()
            .uri(ORDERS_URI)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(createOrderRequest)
            .exchange()
            //Here you map the response to an entity first
            .flatMap(response -> response.toEntity(OrderResponse.class))
            //Then run the errorHandler to do whatever
            //Use doOnNext since there isn't any reason to return anything
            .doOnNext(response -> 
                errorHandler(ORDERS_URI,createOrderRequest,response));

}

//Void doesn't need to return
public static void  errorHandler(String uri, CreateOrderRequest request,ResponseEntity<?> response) {
    if( response.getStatusCode().is5xxServerError() 
        || response.getStatusCode().is4xxClientError())
            //run log method if 500 or 400
            OrderService.logResponseError(response, uri, request);
}

//No need for redundant final param as already in response
static void logResponseError(ResponseEntity<?> response, String attemptedUri, CreateOrderRequest orderRequest) {
    //Do the log stuff
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    try {
        log.error("Response code {} received when attempting to hit {}, request:{}, response:{}",
                response.getStatusCodeValue(), attemptedUri, objectMapper.writeValueAsString(orderRequest),
                response.getBody());
    } catch (JsonProcessingException e) {
        log.error("Error attempting to serialize request object when reporting on error for request to {}, with code:{} and response:{}",
                attemptedUri, response.getStatusCodeValue(), response.getBody());
    }
}

Обратите внимание, что на самом деле нет причин для используйте ExchangeFilter, поскольку на самом деле вы не выполняете никакой фильтрации, а просто выполняете действие на основе ответа

...