Re-order methods and polishing

In StatusHandler and DefaultConvertibleClientHttpResponse.

See gh-35391
This commit is contained in:
rstoyanchev 2025-08-29 14:50:52 +03:00
parent a585beac49
commit 21e6d7392d
2 changed files with 112 additions and 83 deletions

View File

@ -775,7 +775,7 @@ final class DefaultRestClient implements RestClient {
DefaultResponseSpec(RequestHeadersSpec<?> requestHeadersSpec) {
this.requestHeadersSpec = requestHeadersSpec;
this.statusHandlers.addAll(DefaultRestClient.this.defaultStatusHandlers);
this.statusHandlers.add(StatusHandler.defaultHandler(DefaultRestClient.this.messageConverters));
this.statusHandlers.add(StatusHandler.createDefaultStatusHandler(DefaultRestClient.this.messageConverters));
this.defaultStatusHandlerCount = this.statusHandlers.size();
}
@ -886,7 +886,10 @@ final class DefaultRestClient implements RestClient {
return this.requestHeadersSpec.exchange(exchangeFunction);
}
private <T> @Nullable T readBody(HttpRequest request, ClientHttpResponse response, Type bodyType, Class<T> bodyClass, @Nullable Map<String, Object> hints) {
private <T> @Nullable T readBody(
HttpRequest request, ClientHttpResponse response, Type bodyType, Class<T> bodyClass,
@Nullable Map<String, Object> hints) {
return DefaultRestClient.this.readWithMessageConverters(
response, () -> applyStatusHandlers(request, response), bodyType, bodyClass, hints);
@ -922,28 +925,6 @@ final class DefaultRestClient implements RestClient {
this.hints = hints;
}
@Override
public <T> @Nullable T bodyTo(Class<T> bodyType) {
return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType, this.hints);
}
@Override
public <T> @Nullable T bodyTo(ParameterizedTypeReference<T> bodyType) {
Type type = bodyType.getType();
Class<T> bodyClass = bodyClass(type);
return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass, this.hints);
}
@Override
public InputStream getBody() throws IOException {
return this.delegate.getBody();
}
@Override
public HttpHeaders getHeaders() {
return this.delegate.getHeaders();
}
@Override
public HttpStatusCode getStatusCode() throws IOException {
return this.delegate.getStatusCode();
@ -954,10 +935,32 @@ final class DefaultRestClient implements RestClient {
return this.delegate.getStatusText();
}
@Override
public HttpHeaders getHeaders() {
return this.delegate.getHeaders();
}
@Override
public InputStream getBody() throws IOException {
return this.delegate.getBody();
}
@Override
public void close() {
this.delegate.close();
}
@Override
public <T> @Nullable T bodyTo(Class<T> bodyType) {
return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType, this.hints);
}
@Override
public <T> @Nullable T bodyTo(ParameterizedTypeReference<T> bodyType) {
Type type = bodyType.getType();
Class<T> bodyClass = bodyClass(type);
return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass, this.hints);
}
}
}

View File

@ -39,68 +39,122 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
/**
* Used by {@link DefaultRestClient} and {@link DefaultRestClientBuilder}.
* Simple container for an error response Predicate and an error response handler
* to support the status handling mechanism of {@link RestClient.ResponseSpec}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 6.1
*/
final class StatusHandler {
private final ResponsePredicate predicate;
private final RestClient.ResponseSpec.ErrorHandler errorHandler;
private final RestClient.ResponseSpec.ErrorHandler handler;
private StatusHandler(ResponsePredicate predicate, RestClient.ResponseSpec.ErrorHandler errorHandler) {
private StatusHandler(ResponsePredicate predicate, RestClient.ResponseSpec.ErrorHandler handler) {
this.predicate = predicate;
this.errorHandler = errorHandler;
this.handler = handler;
}
public static StatusHandler of(Predicate<HttpStatusCode> predicate,
RestClient.ResponseSpec.ErrorHandler errorHandler) {
/**
* Test whether the response has any errors.
*/
public boolean test(ClientHttpResponse response) throws IOException {
return this.predicate.test(response);
}
/**
* Handle the error in the given response.
* <p>This method is only called when {@link #test(ClientHttpResponse)}
* has returned {@code true}.
ß */
public void handle(HttpRequest request, ClientHttpResponse response) throws IOException {
this.handler.handle(request, response);
}
/**
* Create a StatusHandler from a RestClient {@link RestClient.ResponseSpec.ErrorHandler}.
*/
public static StatusHandler of(
Predicate<HttpStatusCode> predicate, RestClient.ResponseSpec.ErrorHandler errorHandler) {
Assert.notNull(predicate, "Predicate must not be null");
Assert.notNull(errorHandler, "ErrorHandler must not be null");
return new StatusHandler(response -> predicate.test(response.getStatusCode()), errorHandler);
}
/**
* Create a StatusHandler from a {@link ResponseErrorHandler}.
*/
public static StatusHandler fromErrorHandler(ResponseErrorHandler errorHandler) {
Assert.notNull(errorHandler, "ResponseErrorHandler must not be null");
return new StatusHandler(errorHandler::hasError, (request, response) ->
errorHandler.handleError(request.getURI(), request.getMethod(), response));
return new StatusHandler(errorHandler::hasError,
(request, response) -> errorHandler.handleError(request.getURI(), request.getMethod(), response));
}
public static StatusHandler defaultHandler(List<HttpMessageConverter<?>> messageConverters) {
/**
* Create a StatusHandler for default error response handling.
*/
public static StatusHandler createDefaultStatusHandler(List<HttpMessageConverter<?>> converters) {
return new StatusHandler(response -> response.getStatusCode().isError(),
(request, response) -> {
HttpStatusCode statusCode = response.getStatusCode();
String statusText = response.getStatusText();
HttpHeaders headers = response.getHeaders();
byte[] body = RestClientUtils.getBody(response);
Charset charset = RestClientUtils.getCharset(response);
String message = getErrorMessage(statusCode.value(), statusText, body, charset);
RestClientResponseException ex;
if (statusCode.is4xxClientError()) {
ex = HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset);
}
else if (statusCode.is5xxServerError()) {
ex = HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset);
}
else {
ex = new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset);
}
if (!CollectionUtils.isEmpty(messageConverters)) {
ex.setBodyConvertFunction(initBodyConvertFunction(response, body, messageConverters));
}
throw ex;
throw createException(response, converters);
});
}
private static RestClientResponseException createException(
ClientHttpResponse response, List<HttpMessageConverter<?>> converters) throws IOException {
HttpStatusCode statusCode = response.getStatusCode();
String statusText = response.getStatusText();
HttpHeaders headers = response.getHeaders();
byte[] body = RestClientUtils.getBody(response);
Charset charset = RestClientUtils.getCharset(response);
String message = getErrorMessage(statusCode.value(), statusText, body, charset);
RestClientResponseException ex;
if (statusCode.is4xxClientError()) {
ex = HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset);
}
else if (statusCode.is5xxServerError()) {
ex = HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset);
}
else {
ex = new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset);
}
if (!CollectionUtils.isEmpty(converters)) {
ex.setBodyConvertFunction(initBodyConvertFunction(response, body, converters));
}
return ex;
}
private static String getErrorMessage(
int rawStatusCode, String statusText, byte @Nullable [] responseBody, @Nullable Charset charset) {
String preface = rawStatusCode + " " + statusText + ": ";
if (ObjectUtils.isEmpty(responseBody)) {
return preface + "[no body]";
}
charset = (charset != null ? charset : StandardCharsets.UTF_8);
String bodyText = new String(responseBody, charset);
bodyText = LogFormatUtils.formatValue(bodyText, -1, true);
return preface + bodyText;
}
@SuppressWarnings("NullAway")
private static Function<ResolvableType, ? extends @Nullable Object> initBodyConvertFunction(ClientHttpResponse response, byte[] body, List<HttpMessageConverter<?>> messageConverters) {
private static Function<ResolvableType, ? extends @Nullable Object> initBodyConvertFunction(
ClientHttpResponse response, byte[] body, List<HttpMessageConverter<?>> messageConverters) {
Assert.state(!CollectionUtils.isEmpty(messageConverters), "Expected message converters");
return resolvableType -> {
try {
@ -121,34 +175,6 @@ final class StatusHandler {
}
private static String getErrorMessage(int rawStatusCode, String statusText, byte @Nullable [] responseBody,
@Nullable Charset charset) {
String preface = rawStatusCode + " " + statusText + ": ";
if (ObjectUtils.isEmpty(responseBody)) {
return preface + "[no body]";
}
charset = (charset != null ? charset : StandardCharsets.UTF_8);
String bodyText = new String(responseBody, charset);
bodyText = LogFormatUtils.formatValue(bodyText, -1, true);
return preface + bodyText;
}
public boolean test(ClientHttpResponse response) throws IOException {
return this.predicate.test(response);
}
public void handle(HttpRequest request, ClientHttpResponse response) throws IOException {
this.errorHandler.handle(request, response);
}
@FunctionalInterface
private interface ResponsePredicate {