Fix Content-Length handling using Netty
VaultClient now uses a ClientHttpRequestInterceptor to force marshaling. Outgoing data is buffered and available as byte array. This allows setting a Content-Length header using netty. Previously, requests were streamed to the TCP channel without knowing the request body size in advance. Vault expects a Content-Length header and rejected requests with a body but without a Content-Length header. Fixes gh-8
This commit is contained in:
@@ -26,6 +26,11 @@ import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.core.VaultTemplate;
|
||||
@@ -62,6 +67,45 @@ public class VaultClient extends VaultAccessor {
|
||||
this(new RestTemplate(), new VaultEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultClient} for a {@link ClientHttpRequestFactory} and {@link VaultEndpoint}.
|
||||
*
|
||||
* @param requestFactory must not be {@literal null}.
|
||||
* @param endpoint must not be {@literal null}.
|
||||
*/
|
||||
public VaultClient(ClientHttpRequestFactory requestFactory, VaultEndpoint endpoint) {
|
||||
|
||||
super(newRestTemplate(requestFactory));
|
||||
|
||||
Assert.notNull(endpoint, "VaultEndpoint must not be null");
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link RestTemplate} using an interceptor given a {@link ClientHttpRequestFactory}. This forces
|
||||
* {@link RestTemplate} to create the body representation instead of streaming the body to the TCP channel. Streaming
|
||||
* the body without knowing the size in advance will skip the {@link HttpHeaders#CONTENT_LENGTH} makes Vault upset.
|
||||
*
|
||||
* @param requestFactory must not be {@literal null}.
|
||||
* @return the {@link RestTemplate}
|
||||
*/
|
||||
private static RestTemplate newRestTemplate(ClientHttpRequestFactory requestFactory) {
|
||||
|
||||
Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate(requestFactory);
|
||||
restTemplate.getInterceptors().add(new ClientHttpRequestInterceptor() {
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
|
||||
throws IOException {
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
});
|
||||
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link VaultClient} for a {@link RestTemplate} and {@link VaultEndpoint}.
|
||||
*
|
||||
@@ -193,6 +237,9 @@ public class VaultClient extends VaultAccessor {
|
||||
public <T, S extends T> VaultResponseEntity<S> exchange(String pathTemplate, HttpMethod method,
|
||||
HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
|
||||
|
||||
Assert.hasText(pathTemplate, "Path template must not be null or empty");
|
||||
Assert.isTrue(!pathTemplate.startsWith("/"), "Path template must not start with a slash (/)");
|
||||
|
||||
URI uri = uriVariables != null ? buildUri(pathTemplate, uriVariables) : getEndpoint().createUri(pathTemplate);
|
||||
|
||||
return exchange(uri, method, requestEntity, responseType);
|
||||
@@ -219,6 +266,9 @@ public class VaultClient extends VaultAccessor {
|
||||
HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables)
|
||||
throws RestClientException {
|
||||
|
||||
Assert.hasText(pathTemplate, "Path template must not be null or empty");
|
||||
Assert.isTrue(!pathTemplate.startsWith("/"), "Path template must not start with a slash (/)");
|
||||
|
||||
URI uri = uriVariables != null ? buildUri(pathTemplate, uriVariables) : getEndpoint().createUri(pathTemplate);
|
||||
|
||||
return exchange(uri, method, requestEntity, responseType);
|
||||
@@ -235,6 +285,9 @@ public class VaultClient extends VaultAccessor {
|
||||
*/
|
||||
public <T> T doWithRestTemplate(String pathTemplate, Map<String, ?> uriVariables, RestTemplateCallback<T> callback) {
|
||||
|
||||
Assert.hasText(pathTemplate, "Path template must not be null or empty");
|
||||
Assert.isTrue(!pathTemplate.startsWith("/"), "Path template must not start with a slash (/)");
|
||||
|
||||
URI uri = uriVariables != null ? buildUri(pathTemplate, uriVariables) : getEndpoint().createUri(pathTemplate);
|
||||
|
||||
return super.doWithRestTemplate(uri, callback);
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.springframework.vault.core.VaultClientFactory;
|
||||
import org.springframework.vault.core.VaultTemplate;
|
||||
import org.springframework.vault.support.ClientOptions;
|
||||
import org.springframework.vault.support.SslConfiguration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Base class for Spring Vault configuration using JavaConfig.
|
||||
@@ -111,10 +110,7 @@ public abstract class AbstractVaultConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
public VaultClient vaultClient() {
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactoryWrapper().getClientHttpRequestFactory());
|
||||
|
||||
return new VaultClient(restTemplate, vaultEndpoint());
|
||||
return new VaultClient(clientHttpRequestFactoryWrapper().getClientHttpRequestFactory(), vaultEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,7 +38,6 @@ import org.springframework.vault.support.SslConfiguration;
|
||||
import org.springframework.vault.support.VaultToken;
|
||||
import org.springframework.vault.util.IntegrationTestSupport;
|
||||
import org.springframework.vault.util.Settings;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ClientCertificateAuthentication}.
|
||||
@@ -76,7 +75,7 @@ public class ClientCertificateAuthenticationIntegrationTests extends Integration
|
||||
|
||||
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory.create(new ClientOptions(),
|
||||
prepareCertAuthenticationMethod());
|
||||
VaultClient vaultClient = new VaultClient(new RestTemplate(clientHttpRequestFactory), new VaultEndpoint());
|
||||
VaultClient vaultClient = new VaultClient(clientHttpRequestFactory, new VaultEndpoint());
|
||||
|
||||
ClientCertificateAuthentication authentication = new ClientCertificateAuthentication(vaultClient);
|
||||
VaultToken login = authentication.login();
|
||||
@@ -90,7 +89,7 @@ public class ClientCertificateAuthenticationIntegrationTests extends Integration
|
||||
|
||||
ClientHttpRequestFactory clientHttpRequestFactory = ClientHttpRequestFactoryFactory.create(new ClientOptions(),
|
||||
Settings.createSslConfiguration());
|
||||
VaultClient vaultClient = new VaultClient(new RestTemplate(clientHttpRequestFactory), new VaultEndpoint());
|
||||
VaultClient vaultClient = new VaultClient(clientHttpRequestFactory, new VaultEndpoint());
|
||||
|
||||
new ClientCertificateAuthentication(vaultClient).login();
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public class VaultTokenTemplateIntegrationTests extends IntegrationTestSupport {
|
||||
return vaultOperations.doWithVault(new VaultOperations.ClientCallback<VaultResponseEntity<String>>() {
|
||||
@Override
|
||||
public VaultResponseEntity<String> doWithVault(VaultClient client) {
|
||||
return client.getForEntity("/auth/token/lookup-self", tokenResponse.getToken(), String.class);
|
||||
return client.getForEntity("auth/token/lookup-self", tokenResponse.getToken(), String.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,16 +15,20 @@
|
||||
*/
|
||||
package org.springframework.vault.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.vault.config.ClientHttpRequestFactoryFactory;
|
||||
import org.springframework.vault.support.ClientOptions;
|
||||
import org.springframework.vault.support.SslConfiguration;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
@@ -70,11 +74,17 @@ public class TestRestTemplateFactory {
|
||||
|
||||
Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null!");
|
||||
|
||||
RestTemplate RestTemplate = new RestTemplate();
|
||||
RestTemplate.setErrorHandler(new DefaultResponseErrorHandler());
|
||||
RestTemplate.setRequestFactory(requestFactory);
|
||||
RestTemplate template = new RestTemplate();
|
||||
template.setRequestFactory(requestFactory);
|
||||
template.getInterceptors().add(new ClientHttpRequestInterceptor() {
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
|
||||
throws IOException {
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
});
|
||||
|
||||
return RestTemplate;
|
||||
return template;
|
||||
}
|
||||
|
||||
private static void initializeClientHttpRequestFactory(SslConfiguration sslConfiguration) throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user