diff --git a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java index c1321fcf4..f5f3bc669 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.http.HttpHeaders; * * @author Mark Paluch * @author Peter-Josef Meisch + * @author Huw Ayling-Miller * @since 3.2 */ public interface ClientConfiguration { @@ -135,6 +136,14 @@ public interface ClientConfiguration { */ Duration getSocketTimeout(); + /** + * Returns the path prefix that should be prepended to HTTP(s) requests for Elasticsearch behind a proxy. + * + * @return the path prefix. + * @since 4.0 + */ + String getPathPrefix(); + /** * @author Christoph Strobl */ @@ -267,6 +276,15 @@ public interface ClientConfiguration { */ TerminalClientConfigurationBuilder withBasicAuth(String username, String password); + /** + * Configure the path prefix that will be prepended to any HTTP(s) requests + * + * @param pathPrefix the pathPrefix. + * @return the {@link TerminalClientConfigurationBuilder} + * @since 4.0 + */ + TerminalClientConfigurationBuilder withPathPrefix(String pathPrefix); + /** * Build the {@link ClientConfiguration} object. * diff --git a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java index a17c84f78..60dfd20e2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java @@ -37,6 +37,7 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Mark Paluch * @author Peter-Josef Meisch + * @author Huw Ayling-Miller * @since 3.2 */ class ClientConfigurationBuilder @@ -50,6 +51,7 @@ class ClientConfigurationBuilder private Duration soTimeout = Duration.ofSeconds(5); private String username; private String password; + private String pathPrefix; /* * (non-Javadoc) @@ -156,6 +158,14 @@ class ClientConfigurationBuilder return this; } + @Override + public TerminalClientConfigurationBuilder withPathPrefix(String pathPrefix) { + + this.pathPrefix = pathPrefix; + + return this; + } + /* * (non-Javadoc) * @see org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithOptionalDefaultHeaders#build() @@ -171,7 +181,7 @@ class ClientConfigurationBuilder } return new DefaultClientConfiguration(this.hosts, this.headers, this.useSsl, this.sslContext, this.soTimeout, - this.connectTimeout); + this.connectTimeout, this.pathPrefix); } private static InetSocketAddress parse(String hostAndPort) { diff --git a/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java index 2898c5ac5..cf6b6ba28 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java @@ -32,6 +32,7 @@ import org.springframework.lang.Nullable; * * @author Mark Paluch * @author Christoph Strobl + * @author Huw Ayling-Miller * @since 3.2 */ class DefaultClientConfiguration implements ClientConfiguration { @@ -42,9 +43,10 @@ class DefaultClientConfiguration implements ClientConfiguration { private final @Nullable SSLContext sslContext; private final Duration soTimeout; private final Duration connectTimeout; + private final String pathPrefix; DefaultClientConfiguration(List hosts, HttpHeaders headers, boolean useSsl, - @Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout) { + @Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout, @Nullable String pathPrefix) { this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts)); this.headers = new HttpHeaders(headers); @@ -52,6 +54,7 @@ class DefaultClientConfiguration implements ClientConfiguration { this.sslContext = sslContext; this.soTimeout = soTimeout; this.connectTimeout = connectTimeout; + this.pathPrefix = pathPrefix; } /* @@ -108,4 +111,13 @@ class DefaultClientConfiguration implements ClientConfiguration { return this.soTimeout; } + /* + * (non-Javadoc) + * @see org.springframework.data.elasticsearch.client.ClientConfiguration#getPathPrefix() + */ + @Override + public String getPathPrefix() { + return this.pathPrefix; + } + } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/RestClients.java b/src/main/java/org/springframework/data/elasticsearch/client/RestClients.java index 979e5cb32..8e3bd0c48 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/RestClients.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/RestClients.java @@ -52,6 +52,7 @@ import org.springframework.util.Assert; * * @author Christoph Strobl * @author Mark Paluch + * @author Huw Ayling-Miller * @since 3.2 */ public final class RestClients { @@ -75,6 +76,11 @@ public final class RestClients { HttpHost[] httpHosts = formattedHosts(clientConfiguration.getEndpoints(), clientConfiguration.useSsl()).stream() .map(HttpHost::create).toArray(HttpHost[]::new); RestClientBuilder builder = RestClient.builder(httpHosts); + + if (clientConfiguration.getPathPrefix() != null) { + builder.setPathPrefix(clientConfiguration.getPathPrefix()); + } + HttpHeaders headers = clientConfiguration.getDefaultHeaders(); if (!headers.isEmpty()) { diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java index b93ef14f7..8978eff23 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java @@ -123,6 +123,7 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe * @author Christoph Strobl * @author Mark Paluch * @author Peter-Josef Meisch + * @author Huw Ayling-Miller * @since 3.2 * @see ClientConfiguration * @see ReactiveRestClients @@ -217,6 +218,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient); WebClientProvider provider = WebClientProvider.create(scheme, connector); + if (clientConfiguration.getPathPrefix() != null) { + provider = provider.withPathPrefix(clientConfiguration.getPathPrefix()); + } + return provider.withDefaultHeaders(clientConfiguration.getDefaultHeaders()); } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultWebClientProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultWebClientProvider.java index 65c4f6e03..68cedd3ed 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultWebClientProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultWebClientProvider.java @@ -32,6 +32,7 @@ import org.springframework.web.reactive.function.client.WebClient.Builder; * * @author Mark Paluch * @author Christoph Strobl + * @author Huw Ayling-Miller * @since 3.2 */ class DefaultWebClientProvider implements WebClientProvider { @@ -42,6 +43,7 @@ class DefaultWebClientProvider implements WebClientProvider { private final @Nullable ClientHttpConnector connector; private final Consumer errorListener; private final HttpHeaders headers; + private final String pathPrefix; /** * Create new {@link DefaultWebClientProvider} with empty {@link HttpHeaders} and no-op {@literal error listener}. @@ -50,7 +52,7 @@ class DefaultWebClientProvider implements WebClientProvider { * @param connector can be {@literal null}. */ DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector) { - this(scheme, connector, e -> {}, HttpHeaders.EMPTY); + this(scheme, connector, e -> {}, HttpHeaders.EMPTY, null); } /** @@ -62,7 +64,7 @@ class DefaultWebClientProvider implements WebClientProvider { * @param headers must not be {@literal null}. */ private DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector, - Consumer errorListener, HttpHeaders headers) { + Consumer errorListener, HttpHeaders headers, @Nullable String pathPrefix) { Assert.notNull(scheme, "Scheme must not be null! A common scheme would be 'http'."); Assert.notNull(errorListener, "ErrorListener must not be null! You may want use a no-op one 'e -> {}' instead."); @@ -73,6 +75,7 @@ class DefaultWebClientProvider implements WebClientProvider { this.connector = connector; this.errorListener = errorListener; this.headers = headers; + this.pathPrefix = pathPrefix; } /* @@ -109,7 +112,7 @@ class DefaultWebClientProvider implements WebClientProvider { merged.addAll(this.headers); merged.addAll(headers); - return new DefaultWebClientProvider(this.scheme, this.connector, errorListener, merged); + return new DefaultWebClientProvider(this.scheme, this.connector, errorListener, merged, this.pathPrefix); } /* @@ -121,6 +124,15 @@ class DefaultWebClientProvider implements WebClientProvider { return this.errorListener; } + /* + * (non-Javadoc) + * @see org.springframework.data.elasticsearch.client.reactive.WebClientProvider#getPathPrefix() + */ + @Override + public String getPathPrefix() { + return pathPrefix; + } + /* * (non-Javadoc) * @see org.springframework.data.elasticsearch.client.reactive.WebClientProvider#withErrorListener(java.util.function.Consumer) @@ -131,7 +143,18 @@ class DefaultWebClientProvider implements WebClientProvider { Assert.notNull(errorListener, "Error listener must not be null."); Consumer listener = this.errorListener.andThen(errorListener); - return new DefaultWebClientProvider(this.scheme, this.connector, listener, this.headers); + return new DefaultWebClientProvider(this.scheme, this.connector, listener, this.headers, this.pathPrefix); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.elasticsearch.client.reactive.WebClientProvider#withPathPrefix(java.lang.String) + */ + @Override + public WebClientProvider withPathPrefix(String pathPrefix) { + Assert.notNull(pathPrefix, "pathPrefix must not be null."); + + return new DefaultWebClientProvider(this.scheme, this.connector, this.errorListener, this.headers, pathPrefix); } protected WebClient createWebClientForSocketAddress(InetSocketAddress socketAddress) { @@ -142,7 +165,8 @@ class DefaultWebClientProvider implements WebClientProvider { builder = builder.clientConnector(connector); } - String baseUrl = String.format("%s://%s:%d", this.scheme, socketAddress.getHostString(), socketAddress.getPort()); + String baseUrl = String.format("%s://%s:%d%s", this.scheme, socketAddress.getHostString(), socketAddress.getPort(), + pathPrefix == null ? "" : "/" + pathPrefix); return builder.baseUrl(baseUrl).filter((request, next) -> next.exchange(request).doOnError(errorListener)).build(); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java index ead42f3fe..5c325f7f5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java @@ -34,6 +34,7 @@ import org.springframework.web.reactive.function.client.WebClient; * * @author Christoph Strobl * @author Mark Paluch + * @author Huw Ayling-Miller * @since 3.2 */ public interface WebClientProvider { @@ -96,6 +97,14 @@ public interface WebClientProvider { */ Consumer getErrorListener(); + /** + * Obtain the {@link String pathPrefix} to be used. + * + * @return the pathPrefix if set. + * @since 4.0 + */ + String getPathPrefix(); + /** * Create a new instance of {@link WebClientProvider} applying the given headers by default. * @@ -111,4 +120,13 @@ public interface WebClientProvider { * @return new instance of {@link WebClientProvider}. */ WebClientProvider withErrorListener(Consumer errorListener); + + /** + * Create a new instance of {@link WebClientProvider} where HTTP requests are called with the given path prefix. + * + * @param pathPrefix Path prefix to add to requests + * @return new instance of {@link WebClientProvider} + * @since 4.0 + */ + WebClientProvider withPathPrefix(String pathPrefix); } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/ClientConfigurationUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/client/ClientConfigurationUnitTests.java index ae278eeff..d9cee06ed 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/ClientConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/ClientConfigurationUnitTests.java @@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders; * * @author Mark Paluch * @author Peter-Josef Meisch + * @author Huw Ayling-Miller */ public class ClientConfigurationUnitTests { @@ -42,7 +43,7 @@ public class ClientConfigurationUnitTests { assertThat(clientConfiguration.getEndpoints()).containsOnly(InetSocketAddress.createUnresolved("localhost", 9200)); } - @Test // DATAES-488, DATAES-504 + @Test // DATAES-488, DATAES-504, DATAES-650 public void shouldCreateCustomizedConfiguration() { HttpHeaders headers = new HttpHeaders(); @@ -52,7 +53,8 @@ public class ClientConfigurationUnitTests { .connectedTo("foo", "bar") // .usingSsl() // .withDefaultHeaders(headers) // - .withConnectTimeout(Duration.ofDays(1)).withSocketTimeout(Duration.ofDays(2)).build(); + .withConnectTimeout(Duration.ofDays(1)).withSocketTimeout(Duration.ofDays(2)) // + .withPathPrefix("myPathPrefix").build(); assertThat(clientConfiguration.getEndpoints()).containsOnly(InetSocketAddress.createUnresolved("foo", 9200), InetSocketAddress.createUnresolved("bar", 9200)); @@ -60,6 +62,7 @@ public class ClientConfigurationUnitTests { assertThat(clientConfiguration.getDefaultHeaders().get("foo")).containsOnly("bar"); assertThat(clientConfiguration.getConnectTimeout()).isEqualTo(Duration.ofDays(1)); assertThat(clientConfiguration.getSocketTimeout()).isEqualTo(Duration.ofDays(2)); + assertThat(clientConfiguration.getPathPrefix()).isEqualTo("myPathPrefix"); } @Test // DATAES-488, DATAES-504 diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveMockClientTestsUtils.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveMockClientTestsUtils.java index 080d09707..d17e14492 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveMockClientTestsUtils.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveMockClientTestsUtils.java @@ -263,6 +263,16 @@ public class ReactiveMockClientTestsUtils { public WebClientProvider withErrorListener(Consumer errorListener) { throw new UnsupportedOperationException(); } + + @Override + public String getPathPrefix() { + return null; + } + + @Override + public WebClientProvider withPathPrefix(String pathPrefix) { + throw new UnsupportedOperationException(); + } public Send when(String host) { InetSocketAddress inetSocketAddress = getInetSocketAddress(host);