Skip to content

Commit

Permalink
✨ Built-in buffering support in RestClient
Browse files Browse the repository at this point in the history
  • Loading branch information
TAKETODAY committed Jan 28, 2025
1 parent ce86e80 commit 4f733e5
Show file tree
Hide file tree
Showing 14 changed files with 183 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 - 2024 the original author or authors.
* Copyright 2017 - 2025 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 Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 - 2024 the original author or authors.
* Copyright 2017 - 2025 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 Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 - 2024 the original author or authors.
* Copyright 2017 - 2025 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 @@ -19,8 +19,11 @@

import java.io.IOException;
import java.net.URI;
import java.util.function.Predicate;

import infra.http.HttpMethod;
import infra.http.HttpRequest;
import infra.lang.Nullable;

/**
* Wrapper for a {@link ClientHttpRequestFactory} that buffers
Expand All @@ -35,19 +38,33 @@
*/
public class BufferingClientHttpRequestFactory extends ClientHttpRequestFactoryWrapper {

private final Predicate<HttpRequest> bufferingPredicate;

/**
* Create a buffering wrapper for the given {@link ClientHttpRequestFactory}.
*
* @param requestFactory the target request factory to wrap
*/
public BufferingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory) {
this(requestFactory, null);
}

/**
* Constructor variant with an additional predicate to decide whether to
* buffer the response.
*
* @since 5.0
*/
public BufferingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory,
@Nullable Predicate<HttpRequest> bufferingPredicate) {
super(requestFactory);
this.bufferingPredicate = bufferingPredicate != null ? bufferingPredicate : request -> true;
}

@Override
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) throws IOException {
ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod);
if (shouldBuffer(uri, httpMethod)) {
if (shouldBuffer(request)) {
return new BufferingClientHttpRequestWrapper(request);
}
else {
Expand All @@ -61,12 +78,11 @@ protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, Client
* <p>The default implementation returns {@code true} for all URIs and methods.
* Subclasses can override this method to change this behavior.
*
* @param uri the URI
* @param httpMethod the method
* @param request the request
* @return {@code true} if the exchange should be buffered; {@code false} otherwise
*/
protected boolean shouldBuffer(URI uri, HttpMethod httpMethod) {
return true;
protected boolean shouldBuffer(HttpRequest request) {
return this.bufferingPredicate.test(request);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.function.Predicate;

import infra.http.HttpHeaders;
import infra.http.HttpMethod;
Expand All @@ -43,13 +44,16 @@ final class InterceptingClientHttpRequest extends AbstractBufferingClientHttpReq

private final List<ClientHttpRequestInterceptor> interceptors;

private final Predicate<HttpRequest> bufferingPredicate;

InterceptingClientHttpRequest(ClientHttpRequestFactory requestFactory,
List<ClientHttpRequestInterceptor> interceptors, URI uri, HttpMethod method) {
List<ClientHttpRequestInterceptor> interceptors, URI uri, HttpMethod method, Predicate<HttpRequest> bufferingPredicate) {

this.requestFactory = requestFactory;
this.interceptors = interceptors;
this.method = method;
this.uri = uri;
this.bufferingPredicate = bufferingPredicate;
}

@Override
Expand Down Expand Up @@ -103,7 +107,7 @@ public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOExc
}

private boolean shouldBufferResponse(HttpRequest request) {
return false;
return bufferingPredicate.test(request);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 - 2023 the original author or authors.
* Copyright 2017 - 2025 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,16 +12,18 @@
* 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.http.client;

import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

import infra.http.HttpMethod;
import infra.http.HttpRequest;
import infra.lang.Nullable;

/**
Expand All @@ -38,22 +40,38 @@ public class InterceptingClientHttpRequestFactory extends ClientHttpRequestFacto

private final List<ClientHttpRequestInterceptor> interceptors;

private final Predicate<HttpRequest> bufferingPredicate;

/**
* Create a new instance of the {@code InterceptingClientHttpRequestFactory} with the given parameters.
*
* @param requestFactory the request factory to wrap
* @param interceptors the interceptors that are to be applied (can be {@code null})
*/
public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory,
@Nullable List<ClientHttpRequestInterceptor> interceptors) {
@Nullable List<ClientHttpRequestInterceptor> interceptors) {

this(requestFactory, interceptors, null);
}

/**
* Constructor variant with an additional predicate to decide whether to
* buffer the response.
*
* @since 5.0
*/
public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory,
@Nullable List<ClientHttpRequestInterceptor> interceptors,
@Nullable Predicate<HttpRequest> bufferingPredicate) {

super(requestFactory);
this.interceptors = interceptors != null ? interceptors : Collections.emptyList();
this.bufferingPredicate = bufferingPredicate != null ? bufferingPredicate : req -> false;
}

@Override
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod, bufferingPredicate);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 - 2024 the original author or authors.
* Copyright 2017 - 2025 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 @@ -21,14 +21,18 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

import infra.core.annotation.AnnotationAwareOrderComparator;
import infra.http.HttpMethod;
import infra.http.HttpRequest;
import infra.http.client.BufferingClientHttpRequestFactory;
import infra.http.client.ClientHttpRequest;
import infra.http.client.ClientHttpRequestFactory;
import infra.http.client.ClientHttpRequestInitializer;
import infra.http.client.JdkClientHttpRequestFactory;
import infra.lang.Assert;
import infra.lang.Nullable;
import infra.logging.Logger;
import infra.logging.LoggerFactory;

Expand All @@ -54,9 +58,12 @@ public abstract class HttpAccessor {
/** Logger available to subclasses. */
protected final Logger logger = LoggerFactory.getLogger(getClass());

private final ArrayList<ClientHttpRequestInitializer> httpRequestInitializers = new ArrayList<>();

private ClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory();

private final ArrayList<ClientHttpRequestInitializer> httpRequestInitializers = new ArrayList<>();
@Nullable
private Predicate<HttpRequest> bufferingPredicate;

/**
* Set the request factory that this accessor uses for obtaining client request handles.
Expand All @@ -78,7 +85,8 @@ public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
* Return the request factory that this accessor uses for obtaining client request handles.
*/
public ClientHttpRequestFactory getRequestFactory() {
return this.requestFactory;
return this.bufferingPredicate != null ? new BufferingClientHttpRequestFactory(
this.requestFactory, this.bufferingPredicate) : this.requestFactory;
}

/**
Expand All @@ -87,7 +95,6 @@ public ClientHttpRequestFactory getRequestFactory() {
* {@linkplain AnnotationAwareOrderComparator#sort(List) order}.
*/
public void setHttpRequestInitializers(List<ClientHttpRequestInitializer> requestInitializers) {

if (this.httpRequestInitializers != requestInitializers) {
this.httpRequestInitializers.clear();
this.httpRequestInitializers.addAll(requestInitializers);
Expand All @@ -109,6 +116,28 @@ public List<ClientHttpRequestInitializer> getHttpRequestInitializers() {
return this.httpRequestInitializers;
}

/**
* Enable buffering of request and response, aggregating all content before
* it is sent, and making it possible to read the response body repeatedly.
*
* @param predicate to determine whether to buffer for the given request
* @since 5.0
*/
public void setBufferingPredicate(@Nullable Predicate<HttpRequest> predicate) {
this.bufferingPredicate = predicate;
}

/**
* Return the {@link #setBufferingPredicate(Predicate) configured} predicate
* to determine whether to buffer request and response content.
*
* @since 5.0
*/
@Nullable
public Predicate<HttpRequest> getBufferingPredicate() {
return this.bufferingPredicate;
}

/**
* Create a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 - 2023 the original author or authors.
* Copyright 2017 - 2025 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.http.client.support;
Expand Down Expand Up @@ -125,7 +125,8 @@ public ClientHttpRequestFactory getRequestFactory() {
synchronized(this) {
factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
factory = new InterceptingClientHttpRequestFactory(
super.getRequestFactory(), interceptors, getBufferingPredicate());
this.interceptingRequestFactory = factory;
}
}
Expand Down
14 changes: 12 additions & 2 deletions today-web/src/main/java/infra/web/client/DefaultRestClient.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017 - 2024 the original author or authors.
* Copyright 2017 - 2025 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 Down Expand Up @@ -44,6 +44,7 @@
import infra.http.MediaType;
import infra.http.ResponseEntity;
import infra.http.StreamingHttpOutputMessage;
import infra.http.client.BufferingClientHttpRequestFactory;
import infra.http.client.ClientHttpRequest;
import infra.http.client.ClientHttpRequestFactory;
import infra.http.client.ClientHttpRequestInitializer;
Expand Down Expand Up @@ -110,13 +111,17 @@ final class DefaultRestClient implements RestClient {

private final boolean detectEmptyMessageBody;

@Nullable
private final Predicate<HttpRequest> bufferingPredicate;

DefaultRestClient(ClientHttpRequestFactory clientRequestFactory,
@Nullable List<ClientHttpRequestInterceptor> interceptors,
@Nullable List<ClientHttpRequestInitializer> initializers,
UriBuilderFactory uriBuilderFactory, @Nullable HttpHeaders defaultHeaders,
@Nullable MultiValueMap<String, String> defaultCookies,
@Nullable Consumer<RequestHeadersSpec<?>> defaultRequest,
@Nullable List<ResponseErrorHandler> statusHandlers,
@Nullable Predicate<HttpRequest> bufferingPredicate,
List<HttpMessageConverter<?>> messageConverters, DefaultRestClientBuilder builder,
boolean ignoreStatusHandlers, boolean detectEmptyMessageBody) {

Expand All @@ -128,6 +133,7 @@ final class DefaultRestClient implements RestClient {
this.defaultCookies = defaultCookies;
this.defaultRequest = defaultRequest;
this.defaultStatusHandlers = statusHandlers;
this.bufferingPredicate = bufferingPredicate;
this.messageConverters = messageConverters;
this.builder = builder;
this.defaultStatusHandler = StatusHandler.defaultHandler(messageConverters);
Expand Down Expand Up @@ -603,10 +609,14 @@ private ClientHttpRequest createRequest(URI uri) throws ResourceAccessException
if (interceptors != null) {
factory = interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(clientRequestFactory, interceptors);
factory = new InterceptingClientHttpRequestFactory(
clientRequestFactory, interceptors, bufferingPredicate);
interceptingRequestFactory = factory;
}
}
else if (bufferingPredicate != null) {
factory = new BufferingClientHttpRequestFactory(clientRequestFactory, bufferingPredicate);
}
else {
factory = clientRequestFactory;
}
Expand Down
Loading

0 comments on commit 4f733e5

Please sign in to comment.