Skip to content

Commit

Permalink
✨ 检测 content-length = 0 的结果 可配置化
Browse files Browse the repository at this point in the history
  • Loading branch information
TAKETODAY committed Dec 28, 2024
1 parent fc92014 commit 39b6fe4
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 36 deletions.
26 changes: 18 additions & 8 deletions today-web/src/main/java/infra/web/client/DefaultRestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ final class DefaultRestClient implements RestClient {

private final boolean ignoreStatusHandlers;

private final boolean detectEmptyMessageBody;

DefaultRestClient(ClientHttpRequestFactory clientRequestFactory,
@Nullable List<ClientHttpRequestInterceptor> interceptors,
@Nullable List<ClientHttpRequestInitializer> initializers,
Expand All @@ -116,7 +118,7 @@ final class DefaultRestClient implements RestClient {
@Nullable Consumer<RequestHeadersSpec<?>> defaultRequest,
@Nullable List<ResponseErrorHandler> statusHandlers,
List<HttpMessageConverter<?>> messageConverters, DefaultRestClientBuilder builder,
boolean ignoreStatusHandlers) {
boolean ignoreStatusHandlers, boolean detectEmptyMessageBody) {

this.clientRequestFactory = clientRequestFactory;
this.initializers = initializers;
Expand All @@ -130,6 +132,7 @@ final class DefaultRestClient implements RestClient {
this.builder = builder;
this.defaultStatusHandler = StatusHandler.defaultHandler(messageConverters);
this.ignoreStatusHandlers = ignoreStatusHandlers;
this.detectEmptyMessageBody = detectEmptyMessageBody;
}

@Override
Expand Down Expand Up @@ -191,13 +194,17 @@ public Builder mutate() {
private <T> T readWithMessageConverters(ClientHttpResponse clientResponse, @Nullable ResponseConsumer callback, Type bodyType, Class<T> bodyClass) {
MediaType contentType = getContentType(clientResponse);

try (var responseWrapper = new IntrospectingClientHttpResponse(clientResponse)) {
try {
if (callback != null) {
callback.accept(clientResponse);
}

if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
if (detectEmptyMessageBody) {
var responseWrapper = new IntrospectingClientHttpResponse(clientResponse);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
clientResponse = responseWrapper;
}

for (HttpMessageConverter<?> hmc : this.messageConverters) {
Expand All @@ -206,24 +213,27 @@ private <T> T readWithMessageConverters(ClientHttpResponse clientResponse, @Null
if (logger.isDebugEnabled()) {
logger.debug("Reading to [{}]", ResolvableType.forType(bodyType));
}
return (T) ghmc.read(bodyType, null, responseWrapper);
return (T) ghmc.read(bodyType, null, clientResponse);
}
}
else if (hmc.canRead(bodyClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading to [{}] as \"{}\"", bodyClass.getName(), contentType);
}
return (T) hmc.read((Class) bodyClass, responseWrapper);
return (T) hmc.read((Class) bodyClass, clientResponse);
}
}
throw new UnknownContentTypeException(bodyType, contentType,
responseWrapper.getStatusCode(), responseWrapper.getStatusText(),
responseWrapper.getHeaders(), RestClientUtils.getBody(responseWrapper));
clientResponse.getStatusCode(), clientResponse.getStatusText(),
clientResponse.getHeaders(), RestClientUtils.getBody(clientResponse));
}
catch (IOException | HttpMessageNotReadableException ex) {
throw new RestClientException("Error while extracting response for type [%s] and content type [%s]"
.formatted(ResolvableType.forType(bodyType), contentType), ex);
}
finally {
clientResponse.close();
}
}

private static MediaType getContentType(ClientHttpResponse clientResponse) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ final class DefaultRestClientBuilder implements RestClient.Builder {

private boolean ignoreStatus = false;

private boolean detectEmptyMessageBody = true;

public DefaultRestClientBuilder() {

}
Expand Down Expand Up @@ -317,6 +319,12 @@ public RestClient.Builder ignoreStatus(boolean ignoreStatus) {
return this;
}

@Override
public RestClient.Builder detectEmptyMessageBody(boolean detectEmptyBody) {
this.detectEmptyMessageBody = detectEmptyBody;
return this;
}

private RestClient.Builder defaultStatusHandlerInternal(ResponseErrorHandler statusHandler) {
if (this.statusHandlers == null) {
this.statusHandlers = new ArrayList<>();
Expand Down Expand Up @@ -445,7 +453,7 @@ public RestClient build() {
return new DefaultRestClient(requestFactory,
this.interceptors, this.initializers, uriBuilderFactory,
defaultHeaders, defaultCookies, this.defaultRequest, this.statusHandlers,
messageConverters, new DefaultRestClientBuilder(this), ignoreStatus);
messageConverters, new DefaultRestClientBuilder(this), ignoreStatus, detectEmptyMessageBody);
}

private ClientHttpRequestFactory initRequestFactory() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@
import infra.http.converter.HttpMessageConverter;
import infra.http.converter.HttpMessageNotReadableException;
import infra.lang.Assert;
import infra.lang.Constant;
import infra.lang.Nullable;
import infra.logging.Logger;
import infra.logging.LoggerFactory;
import infra.util.FileCopyUtils;

/**
* Response extractor that uses the given {@linkplain HttpMessageConverter entity converters}
Expand All @@ -56,6 +54,9 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {

private final List<HttpMessageConverter<?>> messageConverters;

// @since 5.0
private final boolean detectEmptyMessageBody;

/**
* Create a new instance of the {@code HttpMessageConverterExtractor} with the given response
* type and message converters. The given converters must support the response type.
Expand All @@ -69,44 +70,65 @@ public HttpMessageConverterExtractor(Class<T> responseType, List<HttpMessageConv
* type and message converters. The given converters must support the response type.
*/
public HttpMessageConverterExtractor(Type responseType, List<HttpMessageConverter<?>> messageConverters) {
this(responseType, messageConverters, LoggerFactory.getLogger(HttpMessageConverterExtractor.class));
this(responseType, messageConverters, true);
}

/**
* Creates a new instance of the {@code HttpMessageConverterExtractor} with the given response
* type and message converters. The given converters must support the response type.
*
* @since 5.0
*/
public HttpMessageConverterExtractor(Type responseType, List<HttpMessageConverter<?>> messageConverters, boolean detectEmptyMessageBody) {
this(responseType, messageConverters, LoggerFactory.getLogger(HttpMessageConverterExtractor.class), detectEmptyMessageBody);
}

@SuppressWarnings("unchecked")
public HttpMessageConverterExtractor(Type responseType, List<HttpMessageConverter<?>> messageConverters, Logger logger) {
this(responseType, messageConverters, logger, true);
}

/**
* @since 5.0
*/
@SuppressWarnings("unchecked")
public HttpMessageConverterExtractor(Type responseType, List<HttpMessageConverter<?>> messageConverters, Logger logger, boolean detectEmptyMessageBody) {
Assert.notNull(responseType, "'responseType' is required");
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
Assert.noNullElements(messageConverters, "'messageConverters' must not contain null elements");
this.logger = logger;
this.responseType = responseType;
this.messageConverters = messageConverters;
this.detectEmptyMessageBody = detectEmptyMessageBody;
this.responseClass = (responseType instanceof Class ? (Class<T>) responseType : null);
}

@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public T extractData(ClientHttpResponse response) throws IOException {
IntrospectingClientHttpResponse responseWrapper = new IntrospectingClientHttpResponse(response);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
if (detectEmptyMessageBody) {
var responseWrapper = new IntrospectingClientHttpResponse(response);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
response = responseWrapper;
}

MediaType contentType = getContentType(responseWrapper);
MediaType contentType = getContentType(response);
try {
for (HttpMessageConverter<?> messageConverter : messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter<?> genericConverter) {
if (genericConverter.canRead(responseType, null, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading to [{}]", ResolvableType.forType(responseType));
}
return (T) genericConverter.read(responseType, null, responseWrapper);
return (T) genericConverter.read(responseType, null, response);
}
}
else if (responseClass != null && messageConverter.canRead(responseClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading to [{}] as \"{}\"", responseClass.getName(), contentType);
}
return (T) messageConverter.read((Class) responseClass, responseWrapper);
return (T) messageConverter.read((Class) responseClass, response);
}
}
}
Expand All @@ -115,9 +137,8 @@ else if (responseClass != null && messageConverter.canRead(responseClass, conten
.formatted(responseType, contentType), ex);
}

throw new UnknownContentTypeException(responseType, contentType,
responseWrapper.getRawStatusCode(), responseWrapper.getStatusText(),
responseWrapper.getHeaders(), getResponseBody(responseWrapper));
throw new UnknownContentTypeException(responseType, contentType, response.getRawStatusCode(),
response.getStatusText(), response.getHeaders(), RestClientUtils.getBody(response));
}

/**
Expand All @@ -138,13 +159,4 @@ protected MediaType getContentType(ClientHttpResponse response) {
return contentType;
}

private static byte[] getResponseBody(ClientHttpResponse response) {
try {
return FileCopyUtils.copyToByteArray(response.getBody());
}
catch (IOException ex) {
// ignore
}
return Constant.EMPTY_BYTES;
}
}
10 changes: 10 additions & 0 deletions today-web/src/main/java/infra/web/client/RestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,16 @@ default Builder ignoreStatus() {
*/
Builder ignoreStatus(boolean ignoreStatus);

/**
* Detect empty message body
*
* @param detectEmptyBody detect empty message body
* @return this builder
* @see IntrospectingClientHttpResponse#hasMessageBody()
* @since 5.0
*/
Builder detectEmptyMessageBody(boolean detectEmptyBody);

/**
* Add the given request interceptor to the end of the interceptor chain.
*
Expand Down
10 changes: 5 additions & 5 deletions today-web/src/main/java/infra/web/client/RestClientUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 - 2023 the original author or authors.
* Copyright 2017 - 2024 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -12,7 +12,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see [http://www.gnu.org/licenses/]
* along with this program. If not, see [https://www.gnu.org/licenses/]
*/

package infra.web.client;
Expand All @@ -24,6 +24,7 @@
import infra.http.HttpInputMessage;
import infra.http.HttpMessage;
import infra.http.MediaType;
import infra.lang.Constant;
import infra.lang.Nullable;
import infra.util.FileCopyUtils;

Expand All @@ -40,9 +41,8 @@ public static byte[] getBody(HttpInputMessage message) {
try {
return FileCopyUtils.copyToByteArray(message.getBody());
}
catch (IOException ignore) {
}
return new byte[0];
catch (IOException ignore) { }
return Constant.EMPTY_BYTES;
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,24 @@ void ignoreStatus() {
.isFalse();
}

@Test
void detectEmptyMessageBody() {
RestClient restClient = RestClient.builder()
.build();

assertThat(fieldValue("detectEmptyMessageBody", restClient))
.asInstanceOf(InstanceOfAssertFactories.BOOLEAN)
.isTrue();

restClient = RestClient.builder()
.detectEmptyMessageBody(false)
.build();

assertThat(fieldValue("detectEmptyMessageBody", restClient))
.asInstanceOf(InstanceOfAssertFactories.BOOLEAN)
.isFalse();
}

@Nullable
private static Object fieldValue(String name, DefaultRestClientBuilder instance) {
try {
Expand Down

0 comments on commit 39b6fe4

Please sign in to comment.