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:
Mark Paluch
2016-09-10 10:33:36 +02:00
parent d8e2c22ae1
commit 22aa877f2f
5 changed files with 72 additions and 14 deletions

View File

@@ -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);

View File

@@ -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());
}
/**

View File

@@ -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();
}

View File

@@ -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);
}
});
}

View File

@@ -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 {