Skip to content

Commit 6cb4b8b

Browse files
committed
Add onRawStatus to WebClient
- Add onRawStatus to WebClient.ResponseSpec, allowing users to deal with raw status codes that are not in HttpStatus. - No longer throw an exception status codes not in HttpStatus. Closes gh-23367
1 parent 7b69726 commit 6cb4b8b

File tree

3 files changed

+86
-27
lines changed

3 files changed

+86
-27
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.function.BiFunction;
2929
import java.util.function.Consumer;
3030
import java.util.function.Function;
31+
import java.util.function.IntPredicate;
3132
import java.util.function.Predicate;
3233
import java.util.function.Supplier;
3334

@@ -396,8 +397,10 @@ public HttpHeaders getHeaders() {
396397

397398
private static class DefaultResponseSpec implements ResponseSpec {
398399

400+
private static final IntPredicate STATUS_CODE_ERROR = value -> value >= 400;
401+
399402
private static final StatusHandler DEFAULT_STATUS_HANDLER =
400-
new StatusHandler(HttpStatus::isError, DefaultResponseSpec::createResponseException);
403+
new StatusHandler(STATUS_CODE_ERROR, DefaultResponseSpec::createResponseException);
401404

402405
private final Mono<ClientResponse> responseMono;
403406

@@ -414,11 +417,24 @@ private static class DefaultResponseSpec implements ResponseSpec {
414417
@Override
415418
public ResponseSpec onStatus(Predicate<HttpStatus> statusPredicate,
416419
Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) {
420+
return onRawStatus(toIntPredicate(statusPredicate), exceptionFunction);
421+
}
422+
423+
private static IntPredicate toIntPredicate(Predicate<HttpStatus> predicate) {
424+
return value -> {
425+
HttpStatus status = HttpStatus.resolve(value);
426+
return (status != null) && predicate.test(status);
427+
};
428+
}
429+
430+
@Override
431+
public ResponseSpec onRawStatus(IntPredicate statusCodePredicate,
432+
Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) {
417433

418434
if (this.statusHandlers.size() == 1 && this.statusHandlers.get(0) == DEFAULT_STATUS_HANDLER) {
419435
this.statusHandlers.clear();
420436
}
421-
this.statusHandlers.add(new StatusHandler(statusPredicate,
437+
this.statusHandlers.add(new StatusHandler(statusCodePredicate,
422438
(clientResponse, request) -> exceptionFunction.apply(clientResponse)));
423439

424440
return this;
@@ -451,27 +467,23 @@ public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> elementType) {
451467
private <T extends Publisher<?>> T handleBody(ClientResponse response,
452468
T bodyPublisher, Function<Mono<? extends Throwable>, T> errorFunction) {
453469

454-
if (HttpStatus.resolve(response.rawStatusCode()) != null) {
455-
for (StatusHandler handler : this.statusHandlers) {
456-
if (handler.test(response.statusCode())) {
457-
HttpRequest request = this.requestSupplier.get();
458-
Mono<? extends Throwable> exMono;
459-
try {
460-
exMono = handler.apply(response, request);
461-
exMono = exMono.flatMap(ex -> drainBody(response, ex));
462-
exMono = exMono.onErrorResume(ex -> drainBody(response, ex));
463-
}
464-
catch (Throwable ex2) {
465-
exMono = drainBody(response, ex2);
466-
}
467-
return errorFunction.apply(exMono);
470+
int statusCode = response.rawStatusCode();
471+
for (StatusHandler handler : this.statusHandlers) {
472+
if (handler.test(statusCode)) {
473+
HttpRequest request = this.requestSupplier.get();
474+
Mono<? extends Throwable> exMono;
475+
try {
476+
exMono = handler.apply(response, request);
477+
exMono = exMono.flatMap(ex -> drainBody(response, ex));
478+
exMono = exMono.onErrorResume(ex -> drainBody(response, ex));
468479
}
480+
catch (Throwable ex2) {
481+
exMono = drainBody(response, ex2);
482+
}
483+
return errorFunction.apply(exMono);
469484
}
470-
return bodyPublisher;
471-
}
472-
else {
473-
return errorFunction.apply(createResponseException(response, this.requestSupplier.get()));
474485
}
486+
return bodyPublisher;
475487
}
476488

477489
@SuppressWarnings("unchecked")
@@ -520,11 +532,11 @@ private static Mono<WebClientResponseException> createResponseException(
520532

521533
private static class StatusHandler {
522534

523-
private final Predicate<HttpStatus> predicate;
535+
private final IntPredicate predicate;
524536

525537
private final BiFunction<ClientResponse, HttpRequest, Mono<? extends Throwable>> exceptionFunction;
526538

527-
public StatusHandler(Predicate<HttpStatus> predicate,
539+
public StatusHandler(IntPredicate predicate,
528540
BiFunction<ClientResponse, HttpRequest, Mono<? extends Throwable>> exceptionFunction) {
529541

530542
Assert.notNull(predicate, "Predicate must not be null");
@@ -533,13 +545,15 @@ public StatusHandler(Predicate<HttpStatus> predicate,
533545
this.exceptionFunction = exceptionFunction;
534546
}
535547

536-
public boolean test(HttpStatus status) {
548+
public boolean test(int status) {
537549
return this.predicate.test(status);
538550
}
539551

540552
public Mono<? extends Throwable> apply(ClientResponse response, HttpRequest request) {
541553
return this.exceptionFunction.apply(response, request);
542554
}
555+
556+
543557
}
544558
}
545559

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424
import java.util.function.Consumer;
2525
import java.util.function.Function;
26+
import java.util.function.IntPredicate;
2627
import java.util.function.Predicate;
2728

2829
import org.reactivestreams.Publisher;
@@ -591,7 +592,7 @@ interface ResponseSpec {
591592
* Register a custom error function that gets invoked when the given {@link HttpStatus}
592593
* predicate applies. The exception returned from the function will be returned from
593594
* {@link #bodyToMono(Class)} and {@link #bodyToFlux(Class)}.
594-
* <p>By default, an error handler is register that throws a
595+
* <p>By default, an error handler is registered that throws a
595596
* {@link WebClientResponseException} when the response status code is 4xx or 5xx.
596597
* @param statusPredicate a predicate that indicates whether {@code exceptionFunction}
597598
* applies
@@ -604,6 +605,24 @@ interface ResponseSpec {
604605
ResponseSpec onStatus(Predicate<HttpStatus> statusPredicate,
605606
Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction);
606607

608+
/**
609+
* Register a custom error function that gets invoked when the given raw status code
610+
* predicate applies. The exception returned from the function will be returned from
611+
* {@link #bodyToMono(Class)} and {@link #bodyToFlux(Class)}.
612+
* <p>By default, an error handler is registered that throws a
613+
* {@link WebClientResponseException} when the response status code is 4xx or 5xx.
614+
* @param statusCodePredicate a predicate of the raw status code that indicates
615+
* whether {@code exceptionFunction} applies.
616+
* <p><strong>NOTE:</strong> if the response is expected to have content,
617+
* the exceptionFunction should consume it. If not, the content will be
618+
* automatically drained to ensure resources are released.
619+
* @param exceptionFunction the function that returns the exception
620+
* @return this builder
621+
* @since 5.1.9
622+
*/
623+
ResponseSpec onRawStatus(IntPredicate statusCodePredicate,
624+
Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction);
625+
607626
/**
608627
* Extract the body to a {@code Mono}. By default, if the response has status code 4xx or
609628
* 5xx, the {@code Mono} will contain a {@link WebClientException}. This can be overridden

spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -56,7 +56,11 @@
5656
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
5757
import org.springframework.http.codec.Pojo;
5858

59-
import static org.junit.Assert.*;
59+
import static org.junit.Assert.assertEquals;
60+
import static org.junit.Assert.assertNotNull;
61+
import static org.junit.Assert.assertNull;
62+
import static org.junit.Assert.assertThat;
63+
import static org.junit.Assert.assertTrue;
6064

6165
/**
6266
* Integration tests using an {@link ExchangeFunction} through {@link WebClient}.
@@ -606,6 +610,28 @@ public void shouldApplyCustomStatusHandler() {
606610
});
607611
}
608612

613+
@Test
614+
public void shouldApplyCustomRawStatusHandler() {
615+
prepareResponse(response -> response.setResponseCode(500)
616+
.setHeader("Content-Type", "text/plain").setBody("Internal Server error"));
617+
618+
Mono<String> result = this.webClient.get()
619+
.uri("/greeting?name=Spring")
620+
.retrieve()
621+
.onRawStatus(value -> value >= 500 && value < 600, response -> Mono.just(new MyException("500 error!")))
622+
.bodyToMono(String.class);
623+
624+
StepVerifier.create(result)
625+
.expectError(MyException.class)
626+
.verify(Duration.ofSeconds(3));
627+
628+
expectRequestCount(1);
629+
expectRequest(request -> {
630+
assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
631+
assertEquals("/greeting?name=Spring", request.getPath());
632+
});
633+
}
634+
609635
@Test
610636
public void shouldApplyCustomStatusHandlerParameterizedTypeReference() {
611637
prepareResponse(response -> response.setResponseCode(500)

0 commit comments

Comments
 (0)