Skip to content

Commit 7a0c242

Browse files
committed
Add client-side exception hierarchy
The `WebClient` has a new exception hierarchy: * `WebClientException` is the root of all exceptions thrown by the `WebClient` * `WebClientResponseException` are all exceptions associated with specific HTTP response status codes * `WebClientErrorException` and `WebServerErrorException` are respectively for 4xx and 5xx HTTP status codes `ResponseExtractor` implementations are adapted to optionally throw exceptions if it's impossible to extract the relevant parts of the response (e.g. extracting the response body if the response is a 404). This commit also introduces `ResponseErrorHandler`s that take care of the whole exception mapping infrastructure. Since `WebClientResponseException`s provide the status, headers and response body, we also need a dedicated mechanism to extract information from the response body at that level. The `BodyExtractors` are responsible for extracting that information from the exception, given they are provided with all the information they need; in that case, message decoders are required. To convey all this new information downstream, the `WebClient` now wraps the message converters and response error handler instances into a dedicated `WebClientConfig` object.
1 parent fe79219 commit 7a0c242

20 files changed

+1052
-190
lines changed

spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@
2222
import org.springframework.http.HttpMethod;
2323

2424
import reactor.core.publisher.Mono;
25+
import reactor.io.netty.http.HttpException;
26+
import reactor.io.netty.http.HttpInbound;
2527

2628
/**
2729
* Reactor-Netty implementation of {@link ClientHttpConnector}
2830
*
2931
* @author Brian Clozel
32+
* @see reactor.io.netty.http.HttpClient
3033
* @since 5.0
3134
*/
3235
public class ReactorClientHttpConnector implements ClientHttpConnector {
@@ -38,7 +41,10 @@ public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
3841
return reactor.io.netty.http.HttpClient.create(uri.getHost(), uri.getPort())
3942
.request(io.netty.handler.codec.http.HttpMethod.valueOf(method.name()),
4043
uri.toString(),
41-
httpOutbound -> requestCallback.apply(new ReactorClientHttpRequest(method, uri, httpOutbound)))
44+
httpClientRequest -> requestCallback
45+
.apply(new ReactorClientHttpRequest(method, uri, httpClientRequest)))
46+
.cast(HttpInbound.class)
47+
.otherwise(HttpException.class, exc -> Mono.just(exc.getChannel()))
4248
.map(httpInbound -> new ReactorClientHttpResponse(httpInbound));
4349
}
4450
}

spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import java.util.Collection;
2020

2121
import reactor.core.publisher.Flux;
22-
import reactor.io.netty.http.HttpClientResponse;
22+
import reactor.io.netty.http.HttpInbound;
2323

2424
import org.springframework.core.io.buffer.DataBuffer;
2525
import org.springframework.core.io.buffer.NettyDataBufferFactory;
@@ -34,16 +34,16 @@
3434
* {@link ClientHttpResponse} implementation for the Reactor-Netty HTTP client
3535
*
3636
* @author Brian Clozel
37-
* @since 5.0
3837
* @see reactor.io.netty.http.HttpClient
38+
* @since 5.0
3939
*/
4040
public class ReactorClientHttpResponse implements ClientHttpResponse {
4141

4242
private final NettyDataBufferFactory dataBufferFactory;
4343

44-
private final HttpClientResponse response;
44+
private final HttpInbound response;
4545

46-
public ReactorClientHttpResponse(HttpClientResponse response) {
46+
public ReactorClientHttpResponse(HttpInbound response) {
4747
this.response = response;
4848
this.dataBufferFactory = new NettyDataBufferFactory(response.delegate().alloc());
4949
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.client.reactive;
18+
19+
import java.util.List;
20+
21+
import org.springframework.http.client.reactive.ClientHttpResponse;
22+
import org.springframework.http.converter.reactive.HttpMessageConverter;
23+
24+
/**
25+
* A {@code BodyExtractor} extracts the content of a raw {@link ClientHttpResponse},
26+
* decoding the response body and using a target composition API.
27+
*
28+
* <p>See static factory methods in {@link ResponseExtractors}
29+
* and {@link org.springframework.web.client.reactive.support.RxJava1ResponseExtractors}.
30+
*
31+
* @author Brian Clozel
32+
* @since 5.0
33+
*/
34+
public interface BodyExtractor<T> {
35+
36+
/**
37+
* Extract content from the response body
38+
* @param clientResponse the raw HTTP response
39+
* @param messageConverters the message converters that decode the response body
40+
* @return the relevant content
41+
*/
42+
T extract(ClientHttpResponse clientResponse, List<HttpMessageConverter<?>> messageConverters);
43+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.client.reactive;
18+
19+
import java.util.List;
20+
21+
import org.springframework.http.HttpStatus;
22+
import org.springframework.http.client.reactive.ClientHttpResponse;
23+
import org.springframework.http.converter.reactive.HttpMessageConverter;
24+
25+
/**
26+
* Default implementation of the {@link ResponseErrorHandler} interface
27+
* that throws {@link WebClientErrorException}s for HTTP 4xx responses
28+
* and {@link WebServerErrorException}s for HTTP 5xx responses.
29+
*
30+
* @author Brian Clozel
31+
* @since 5.0
32+
*/
33+
public class DefaultResponseErrorHandler implements ResponseErrorHandler {
34+
35+
@Override
36+
public void handleError(ClientHttpResponse response, List<HttpMessageConverter<?>> messageConverters) {
37+
HttpStatus responseStatus = response.getStatusCode();
38+
if (responseStatus.is4xxClientError()) {
39+
throw new WebClientErrorException(response, messageConverters);
40+
}
41+
if (responseStatus.is5xxServerError()) {
42+
throw new WebServerErrorException(response, messageConverters);
43+
}
44+
}
45+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.client.reactive;
18+
19+
import java.util.List;
20+
21+
import org.springframework.http.client.reactive.ClientHttpResponse;
22+
import org.springframework.http.converter.reactive.HttpMessageConverter;
23+
24+
/**
25+
* Strategy interface used by the {@link WebClient} to handle
26+
* errors in {@link ClientHttpResponse}s if needed.
27+
*
28+
* @author Brian Clozel
29+
* @see DefaultResponseErrorHandler
30+
* @since 5.0
31+
*/
32+
public interface ResponseErrorHandler {
33+
34+
/**
35+
* Handle the error in the given response.
36+
* Implementations will typically inspect the {@link ClientHttpResponse#getStatusCode() HttpStatus}
37+
* of the response and throw {@link WebClientException}s in case of errors.
38+
*/
39+
void handleError(ClientHttpResponse response, List<HttpMessageConverter<?>> messageConverters);
40+
41+
}

spring-web/src/main/java/org/springframework/web/client/reactive/ResponseExtractor.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,28 @@
1616

1717
package org.springframework.web.client.reactive;
1818

19-
import java.util.List;
20-
2119
import reactor.core.publisher.Mono;
2220

2321
import org.springframework.http.client.reactive.ClientHttpResponse;
24-
import org.springframework.http.converter.reactive.HttpMessageConverter;
2522

2623
/**
2724
* A {@code ResponseExtractor} extracts the relevant part of a
2825
* raw {@link org.springframework.http.client.reactive.ClientHttpResponse},
2926
* optionally decoding the response body and using a target composition API.
3027
*
31-
* <p>See static factory methods in {@link ResponseExtractors}.
28+
* <p>See static factory methods in {@link ResponseExtractors} and
29+
* {@link org.springframework.web.client.reactive.support.RxJava1ResponseExtractors}.
3230
*
3331
* @author Brian Clozel
3432
* @since 5.0
3533
*/
3634
public interface ResponseExtractor<T> {
3735

38-
T extract(Mono<ClientHttpResponse> clientResponse, List<HttpMessageConverter<?>> messageConverters);
36+
/**
37+
* Extract content from the response
38+
* @param clientResponse the raw HTTP response
39+
* @param webClientConfig the {@link WebClient} configuration information
40+
* @return the relevant part of the response
41+
*/
42+
T extract(Mono<ClientHttpResponse> clientResponse, WebClientConfig webClientConfig);
3943
}

0 commit comments

Comments
 (0)