Skip to content

Commit 6db1aa6

Browse files
authored
DATAES-588 - Add HttpClientConfigCallback for non-reactive setup.
Original PR: spring-projects#548
1 parent 6bfeade commit 6db1aa6

File tree

7 files changed

+115
-44
lines changed

7 files changed

+115
-44
lines changed

src/main/asciidoc/reference/elasticsearch-clients.adoc

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -150,36 +150,46 @@ Client behaviour can be changed via the `ClientConfiguration` that allows to set
150150
[source,java]
151151
----
152152
HttpHeaders httpHeaders = new HttpHeaders();
153-
httpHeaders.add("some-header", "on every request") <1>
153+
httpHeaders.add("some-header", "on every request") <.>
154154
155155
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
156-
.connectedTo("localhost:9200", "localhost:9291") <2>
157-
.useSsl() <3>
158-
.withProxy("localhost:8888") <4>
159-
.withPathPrefix("ela") <5>
160-
.withConnectTimeout(Duration.ofSeconds(5)) <6>
161-
.withSocketTimeout(Duration.ofSeconds(3)) <7>
162-
.withDefaultHeaders(defaultHeaders) <8>
163-
.withBasicAuth(username, password) <9>
164-
.withHeaders(() -> { <10>
156+
.connectedTo("localhost:9200", "localhost:9291") <.>
157+
.useSsl() <.>
158+
.withProxy("localhost:8888") <.>
159+
.withPathPrefix("ela") <.>
160+
.withConnectTimeout(Duration.ofSeconds(5)) <.>
161+
.withSocketTimeout(Duration.ofSeconds(3)) <.>
162+
.withDefaultHeaders(defaultHeaders) <.>
163+
.withBasicAuth(username, password) <.>
164+
.withHeaders(() -> { <.>
165165
HttpHeaders headers = new HttpHeaders();
166166
headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
167167
return headers;
168168
})
169+
.withWebClientConfigurer(webClient -> { <.>
170+
//...
171+
return webClient;
172+
})
173+
.withHttpClientConfigurer(clientBuilder -> { <.>
174+
//...
175+
return clientBuilder;
176+
})
169177
. // ... other options
170178
.build();
171179
172180
----
173-
<1> Define default headers, if they need to be customized
174-
<2> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
175-
<3> Optionally enable SSL.
176-
<4> Optionally set a proxy.
177-
<5> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
178-
<6> Set the connection timeout. Default is 10 sec.
179-
<7> Set the socket timeout. Default is 5 sec.
180-
<8> Optionally set headers.
181-
<9> Add basic authentication.
182-
<10> A `Supplier<Header>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
181+
<.> Define default headers, if they need to be customized
182+
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
183+
<.> Optionally enable SSL.
184+
<.> Optionally set a proxy.
185+
<.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
186+
<.> Set the connection timeout. Default is 10 sec.
187+
<.> Set the socket timeout. Default is 5 sec.
188+
<.> Optionally set headers.
189+
<.> Add basic authentication.
190+
<.> A `Supplier<Header>` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header.
191+
<.> for reactive setup a function configuring the `WebClient`
192+
<.> for non-reactive setup a function configuring the REST client
183193
====
184194

185195
IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens. If this is used in the reactive setup, the supplier function *must not* block!

src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import javax.net.ssl.HostnameVerifier;
2727
import javax.net.ssl.SSLContext;
2828

29+
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
2930
import org.springframework.http.HttpHeaders;
3031
import org.springframework.lang.Nullable;
3132
import org.springframework.web.reactive.function.client.WebClient;
@@ -171,6 +172,12 @@ static ClientConfiguration create(InetSocketAddress socketAddress) {
171172
*/
172173
Function<WebClient, WebClient> getWebClientConfigurer();
173174

175+
/**
176+
* @return the client configuration callback.
177+
* @since 4.2
178+
*/
179+
HttpClientConfigCallback getHttpClientConfigurer();
180+
174181
/**
175182
* @return the supplier for custom headers.
176183
*/
@@ -341,13 +348,22 @@ default TerminalClientConfigurationBuilder withSocketTimeout(long millis) {
341348
*/
342349
TerminalClientConfigurationBuilder withWebClientConfigurer(Function<WebClient, WebClient> webClientConfigurer);
343350

351+
/**
352+
* Register a {HttpClientConfigCallback} to configure the non-reactive REST client.
353+
*
354+
* @param httpClientConfigurer configuration callback, must not be null.
355+
* @return the {@link TerminalClientConfigurationBuilder}.
356+
* @since 4.2
357+
*/
358+
TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer);
359+
344360
/**
345361
* set a supplier for custom headers. This is invoked for every HTTP request to Elasticsearch to retrieve headers
346362
* that should be sent with the request. A common use case is passing in authentication headers that may change.
347363
* <br/>
348364
* Note: When used in a reactive environment, the calling of {@link Supplier#get()} function must not do any
349365
* blocking operations. It may return {@literal null}.
350-
*
366+
*
351367
* @param headers supplier function for headers, must not be {@literal null}
352368
* @return the {@link TerminalClientConfigurationBuilder}.
353369
* @since 4.0

src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import javax.net.ssl.HostnameVerifier;
2828
import javax.net.ssl.SSLContext;
2929

30+
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
3031
import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint;
3132
import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder;
3233
import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder;
@@ -61,6 +62,7 @@ class ClientConfigurationBuilder
6162
private @Nullable String proxy;
6263
private Function<WebClient, WebClient> webClientConfigurer = Function.identity();
6364
private Supplier<HttpHeaders> headersSupplier = () -> HttpHeaders.EMPTY;
65+
private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
6466

6567
/*
6668
* (non-Javadoc)
@@ -207,6 +209,15 @@ public TerminalClientConfigurationBuilder withWebClientConfigurer(
207209
return this;
208210
}
209211

212+
@Override
213+
public TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer) {
214+
215+
Assert.notNull(httpClientConfigurer, "httpClientConfigurer must not be null");
216+
217+
this.httpClientConfigurer = httpClientConfigurer;
218+
return this;
219+
}
220+
210221
@Override
211222
public TerminalClientConfigurationBuilder withHeaders(Supplier<HttpHeaders> headers) {
212223

@@ -231,7 +242,7 @@ public ClientConfiguration build() {
231242
}
232243

233244
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
234-
hostnameVerifier, proxy, webClientConfigurer, headersSupplier);
245+
hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, headersSupplier);
235246
}
236247

237248
private static InetSocketAddress parse(String hostAndPort) {

src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import javax.net.ssl.HostnameVerifier;
2828
import javax.net.ssl.SSLContext;
2929

30+
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
3031
import org.springframework.http.HttpHeaders;
3132
import org.springframework.lang.Nullable;
3233
import org.springframework.web.reactive.function.client.WebClient;
@@ -52,12 +53,14 @@ class DefaultClientConfiguration implements ClientConfiguration {
5253
private final @Nullable HostnameVerifier hostnameVerifier;
5354
private final @Nullable String proxy;
5455
private final Function<WebClient, WebClient> webClientConfigurer;
56+
private final HttpClientConfigCallback httpClientConfigurer;
5557
private final Supplier<HttpHeaders> headersSupplier;
5658

5759
DefaultClientConfiguration(List<InetSocketAddress> hosts, HttpHeaders headers, boolean useSsl,
5860
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout, @Nullable String pathPrefix,
5961
@Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy,
60-
Function<WebClient, WebClient> webClientConfigurer, Supplier<HttpHeaders> headersSupplier) {
62+
Function<WebClient, WebClient> webClientConfigurer, HttpClientConfigCallback httpClientConfigurer,
63+
Supplier<HttpHeaders> headersSupplier) {
6164

6265
this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts));
6366
this.headers = new HttpHeaders(headers);
@@ -69,6 +72,7 @@ class DefaultClientConfiguration implements ClientConfiguration {
6972
this.hostnameVerifier = hostnameVerifier;
7073
this.proxy = proxy;
7174
this.webClientConfigurer = webClientConfigurer;
75+
this.httpClientConfigurer = httpClientConfigurer;
7276
this.headersSupplier = headersSupplier;
7377
}
7478

@@ -123,6 +127,11 @@ public Function<WebClient, WebClient> getWebClientConfigurer() {
123127
return webClientConfigurer;
124128
}
125129

130+
@Override
131+
public HttpClientConfigCallback getHttpClientConfigurer() {
132+
return httpClientConfigurer;
133+
}
134+
126135
@Override
127136
public Supplier<HttpHeaders> getHeadersSupplier() {
128137
return headersSupplier;

src/main/java/org/springframework/data/elasticsearch/client/RestClients.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ public static ElasticsearchRestClient create(ClientConfiguration clientConfigura
119119

120120
clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy);
121121

122+
clientBuilder = clientConfiguration.getHttpClientConfigurer().customizeHttpClient(clientBuilder);
123+
122124
return clientBuilder;
123125
});
124126

src/test/java/org/springframework/data/elasticsearch/client/ClientConfigurationUnitTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import javax.net.ssl.SSLContext;
2626

2727
import org.apache.http.conn.ssl.NoopHostnameVerifier;
28+
import org.elasticsearch.client.RestClientBuilder;
29+
import org.junit.jupiter.api.DisplayName;
2830
import org.junit.jupiter.api.Test;
2931
import org.springframework.http.HttpHeaders;
3032
import org.springframework.web.reactive.function.client.WebClient;
@@ -165,6 +167,20 @@ void shouldUseConfiguredWebClientConfigurer() {
165167
assertThat(clientConfiguration.getWebClientConfigurer()).isEqualTo(webClientConfigurer);
166168
}
167169

170+
@Test // DATAES-588
171+
@DisplayName("should use configured httpClientConfigurer")
172+
void shouldUseConfiguredHttpClientConfigurer() {
173+
174+
RestClientBuilder.HttpClientConfigCallback callback = httpClientBuilder -> httpClientBuilder;
175+
176+
ClientConfiguration clientConfiguration = ClientConfiguration.builder() //
177+
.connectedTo("foo", "bar") //
178+
.withHttpClientConfigurer(callback) //
179+
.build();
180+
181+
assertThat(clientConfiguration.getHttpClientConfigurer()).isEqualTo(callback);
182+
}
183+
168184
private static String buildBasicAuth(String username, String password) {
169185

170186
HttpHeaders headers = new HttpHeaders();

src/test/java/org/springframework/data/elasticsearch/client/RestClientsTest.java

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static com.github.tomakehurst.wiremock.client.WireMock.*;
44
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
5+
import static org.assertj.core.api.Assertions.*;
56

67
import java.io.IOException;
78
import java.util.Arrays;
@@ -10,6 +11,7 @@
1011
import java.util.stream.Stream;
1112

1213
import org.elasticsearch.client.RequestOptions;
14+
import org.elasticsearch.client.RestClientBuilder;
1315
import org.elasticsearch.client.RestHighLevelClient;
1416
import org.junit.jupiter.api.Disabled;
1517
import org.junit.jupiter.api.DisplayName;
@@ -62,10 +64,10 @@ void shouldUseConfiguredProxy(ClientUnderTestFactory clientUnderTestFactory) thr
6264
});
6365
}
6466

65-
@ParameterizedTest // DATAES-801
67+
@ParameterizedTest // DATAES-801, DATAES-588
6668
@MethodSource("clientUnderTestFactorySource")
67-
@DisplayName("should set all required headers")
68-
void shouldSetAllRequiredHeaders(ClientUnderTestFactory clientUnderTestFactory) {
69+
@DisplayName("should configure client and set all required headers")
70+
void shouldConfigureClientAndSetAllRequiredHeaders(ClientUnderTestFactory clientUnderTestFactory) {
6971
wireMockServer(server -> {
7072

7173
WireMock.configureFor(server.port());
@@ -78,6 +80,12 @@ void shouldSetAllRequiredHeaders(ClientUnderTestFactory clientUnderTestFactory)
7880
defaultHeaders.add("def2", "def2-1");
7981

8082
AtomicInteger supplierCount = new AtomicInteger(1);
83+
AtomicInteger clientConfigurerCount = new AtomicInteger(0);
84+
85+
RestClientBuilder.HttpClientConfigCallback configCallback = httpClientBuilder -> {
86+
clientConfigurerCount.incrementAndGet();
87+
return httpClientBuilder;
88+
};
8189

8290
ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder();
8391
ClientConfiguration clientConfiguration = configurationBuilder //
@@ -89,22 +97,29 @@ void shouldSetAllRequiredHeaders(ClientUnderTestFactory clientUnderTestFactory)
8997
httpHeaders.add("supplied", "val0");
9098
httpHeaders.add("supplied", "val" + supplierCount.getAndIncrement());
9199
return httpHeaders;
92-
}).build();
100+
}) //
101+
.withHttpClientConfigurer(configCallback).build();
93102

94103
ClientUnderTest clientUnderTest = clientUnderTestFactory.create(clientConfiguration);
95104

96105
// do several calls to check that the headerSupplier provided values are set
97106
for (int i = 1; i <= 3; i++) {
98107
clientUnderTest.ping();
99108

100-
verify(headRequestedFor(urlEqualTo("/")).withHeader("Authorization", new AnythingPattern()) //
109+
verify(headRequestedFor(urlEqualTo("/")) //
110+
.withHeader("Authorization", new AnythingPattern()) //
101111
.withHeader("def1", new EqualToPattern("def1-1")) //
102112
.withHeader("def1", new EqualToPattern("def1-2")) //
103113
.withHeader("def2", new EqualToPattern("def2-1")) //
104114
.withHeader("supplied", new EqualToPattern("val0")) //
105115
.withHeader("supplied", new EqualToPattern("val" + i)) //
106116
);
107117
}
118+
119+
// clientConfigurer is only used in non-reactive setup
120+
if (!(clientUnderTestFactory instanceof ReactiveElasticsearchClientUnderTestFactory)) {
121+
assertThat(clientConfigurerCount).hasValue(1);
122+
}
108123
});
109124
}
110125

@@ -148,9 +163,9 @@ private void wireMockServer(WiremockConsumer consumer) {
148163
*/
149164
interface ClientUnderTest {
150165
/**
151-
* Pings the configured server.
166+
* Pings the configured server. Must use a HEAD request to "/".
152167
*
153-
* @return
168+
* @return true if successful
154169
*/
155170
boolean ping() throws Exception;
156171
}
@@ -182,13 +197,7 @@ protected String getDisplayName() {
182197
@Override
183198
ClientUnderTest create(ClientConfiguration clientConfiguration) {
184199
RestHighLevelClient client = RestClients.create(clientConfiguration).rest();
185-
return new ClientUnderTest() {
186-
187-
@Override
188-
public boolean ping() throws Exception {
189-
return client.ping(RequestOptions.DEFAULT);
190-
}
191-
};
200+
return () -> client.ping(RequestOptions.DEFAULT);
192201
}
193202

194203
}
@@ -206,12 +215,7 @@ protected String getDisplayName() {
206215
@Override
207216
ClientUnderTest create(ClientConfiguration clientConfiguration) {
208217
ReactiveElasticsearchClient client = ReactiveRestClients.create(clientConfiguration);
209-
return new ClientUnderTest() {
210-
@Override
211-
public boolean ping() throws Exception {
212-
return client.ping().block();
213-
}
214-
};
218+
return () -> client.ping().block();
215219
}
216220
}
217221

@@ -221,6 +225,9 @@ public boolean ping() throws Exception {
221225
* @return stream of factories
222226
*/
223227
static Stream<ClientUnderTestFactory> clientUnderTestFactorySource() {
224-
return Stream.of(new RestClientUnderTestFactory(), new ReactiveElasticsearchClientUnderTestFactory());
228+
return Stream.of( //
229+
new RestClientUnderTestFactory(), //
230+
new ReactiveElasticsearchClientUnderTestFactory() //
231+
);
225232
}
226233
}

0 commit comments

Comments
 (0)