Pass headers back to feign request.

Spring HttpMessageConverters can modify headers. This change passes the modified headersr back to the feign request, so the proper headers are sent on the request. fixes gh-977
parent 33efc60d
......@@ -18,6 +18,8 @@ package org.springframework.cloud.netflix.feign.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpHeaders;
......@@ -35,4 +37,14 @@ public class FeignUtils {
return httpHeaders;
}
static Map<String, Collection<String>> getHeaders(HttpHeaders httpHeaders) {
LinkedHashMap<String, Collection<String>> headers = new LinkedHashMap<>();
for (Map.Entry<String, List<String>> entry : httpHeaders.entrySet()) {
headers.put(entry.getKey(), entry.getValue());
}
return headers;
}
}
......@@ -16,8 +16,6 @@
package org.springframework.cloud.netflix.feign.support;
import static org.springframework.cloud.netflix.feign.support.FeignUtils.getHttpHeaders;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
......@@ -25,8 +23,6 @@ import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Collection;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.http.HttpHeaders;
......@@ -37,6 +33,10 @@ import org.springframework.http.converter.HttpMessageConverter;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import lombok.extern.apachecommons.CommonsLog;
import static org.springframework.cloud.netflix.feign.support.FeignUtils.getHeaders;
import static org.springframework.cloud.netflix.feign.support.FeignUtils.getHttpHeaders;
/**
* @author Spencer Gibb
......@@ -89,6 +89,12 @@ public class SpringEncoder implements Encoder {
catch (IOException ex) {
throw new EncodeException("Error converting request body", ex);
}
// clear headers
request.headers(null);
// converters can modify headers, so update the request
// with the modified headers
request.headers(getHeaders(outputMessage.getHeaders()));
request.body(outputMessage.getOutputStream().toByteArray(),
Charset.forName("UTF-8")); // TODO: set charset
return;
......@@ -107,10 +113,10 @@ public class SpringEncoder implements Encoder {
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private final RequestTemplate request;
private final HttpHeaders httpHeaders;
private FeignOutputMessage(RequestTemplate request) {
this.request = request;
httpHeaders = getHttpHeaders(request.headers());
}
@Override
......@@ -120,7 +126,7 @@ public class SpringEncoder implements Encoder {
@Override
public HttpHeaders getHeaders() {
return getHttpHeaders(this.request.headers());
return this.httpHeaders;
}
public ByteArrayOutputStream getOutputStream() {
......
package org.springframework.cloud.netflix.feign.support;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.cloud.netflix.feign.FeignContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractGenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RestController;
import feign.RequestTemplate;
import lombok.Data;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
/**
* @author Spencer Gibb
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringEncoderTests.Application.class)
@WebAppConfiguration
@IntegrationTest({ "server.port=0", "spring.application.name=springencodertest",
"spring.jmx.enabled=true" })
@DirtiesContext
public class SpringEncoderTests {
@Autowired
private FeignContext context;
@Autowired
@Qualifier("myHttpMessageConverter")
private HttpMessageConverter myConverter;
@Test
public void testCustomHttpMessageConverter() {
SpringEncoder encoder = this.context.getInstance("foo", SpringEncoder.class);
assertThat(encoder, is(notNullValue()));
RequestTemplate request = new RequestTemplate();
encoder.encode("hi", MyType.class, request);
Collection<String> contentTypeHeader = request.headers().get("Content-Type");
assertThat("missing content type header", contentTypeHeader, is(notNullValue()));
assertThat("missing content type header", contentTypeHeader.isEmpty(), is(false));
String header = contentTypeHeader.iterator().next();
assertThat("content type header is wrong", header, is("application/mytype"));
}
class MediaTypeMatcher extends ArgumentMatcher<MediaType> {
private MediaType mediaType;
public MediaTypeMatcher(String type, String subtype) {
mediaType = new MediaType(type, subtype);
}
@Override
public boolean matches(Object argument) {
if (argument instanceof MediaType) {
MediaType other = (MediaType) argument;
return mediaType.equals(other);
}
return false;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("MediaTypeMatcher{");
sb.append("mediaType=").append(mediaType);
sb.append('}');
return sb.toString();
}
}
@Data
protected static class MyType {
private String value;
}
protected interface TestClient {
}
@Configuration
@EnableAutoConfiguration
@RestController
protected static class Application implements TestClient {
@Bean
HttpMessageConverter myHttpMessageConverter() {
return new MyHttpMessageConverter();
}
private static class MyHttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
public MyHttpMessageConverter() {
super(new MediaType("application", "mytype"));
}
@Override
protected boolean supports(Class clazz) {
return false;
}
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return true;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return true;
}
@Override
protected void writeInternal(Object o, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
}
@Override
protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public Object read(Type type, Class contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
}
}
}
......@@ -86,7 +86,7 @@ public class FeignHttpClientTests {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop")
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop", consumes = "application/json")
ResponseEntity<Void> patchHello(Hello hello);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment