Compare commits

..
Author SHA1 Message Date
buildmaster 01f4808035 Update SNAPSHOT to 2.1.5.RELEASE 2020-01-31 21:52:25 +00:00
Ryan Baxter a85294a835 Add documentation about early initialization errors. Fixes #221 2020-01-29 10:57:58 -05:00
Ryan Baxter 73a75fd8b2 Add appendix.adoc. Fixes #255 2020-01-29 10:38:13 -05:00
Olga Maciaszek-SharmaandAhmad Mozafarnia 80d2067134 Merge fix 255 to 21x (#273)
* Fix bug in SpringEncoder (#225)

This commit fixes a bug in the SpringEncoder that causes the bodyType to
be ignored and not passed to generic message converters.

Fixes gh-225

* Fix after cherry-pick.

* Add Ahmad Mozafarnia to authors.

* Refactor tests.

Co-authored-by: Ahmad Mozafarnia <ahmad.mozafarnia@gmail.com>
2020-01-09 17:59:15 +01:00
buildmaster f97cbb9a73 Bumping versions to 2.1.5.BUILD-SNAPSHOT after release 2019-11-18 15:11:21 +00:00
buildmaster de39e67e18 Going back to snapshots 2019-11-18 15:11:21 +00:00
9 changed files with 205 additions and 64 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.1.4.RELEASE</version>
<version>2.1.5.RELEASE</version>
</parent>
<artifactId>spring-cloud-openfeign-docs</artifactId>
<packaging>pom</packaging>
+14
View File
@@ -0,0 +1,14 @@
:numbered!:
[appendix]
[[common-application-properties]]
== Common application properties
include::_attributes.adoc[]
Various properties can be specified inside your `application.properties` file, inside your `application.yml` file, or as command line switches.
This appendix provides a list of common {project-full-name} properties and references to the underlying classes that consume them.
NOTE: Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list.
Also, you can define your own properties.
include::_configprops.adoc[]
@@ -487,3 +487,16 @@ public class DemoTemplate {
String demoEndpoint(@SpringQueryMap Params params);
}
----
=== Troubleshooting
==== Early Initialization Errors
Depending on how you are using your Feign clients you may see initialization errors when starting your application.
To work around this problem you can use an `ObjectProvider` when autowiring your client.
[source,java,indent=0]
----
@Autowired
ObjectProvider<TestFeginClient> testFeginClient;
----
+4 -4
View File
@@ -4,14 +4,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.1.4.RELEASE</version>
<version>2.1.5.RELEASE</version>
<packaging>pom</packaging>
<name>Spring Cloud OpenFeign</name>
<description>Spring Cloud OpenFeign</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>2.1.9.RELEASE</version>
<version>2.1.10.RELEASE</version>
<relativePath/>
</parent>
<scm>
@@ -26,8 +26,8 @@
<properties>
<main.basedir>${basedir}</main.basedir>
<jackson.version>2.7.3</jackson.version>
<spring-cloud-commons.version>2.1.4.RELEASE</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.1.4.RELEASE</spring-cloud-netflix.version>
<spring-cloud-commons.version>2.1.5.RELEASE</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.1.5.RELEASE</spring-cloud-netflix.version>
<!-- Plugin versions -->
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.1.4.RELEASE</version>
<version>2.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-openfeign-core</artifactId>
@@ -35,10 +35,12 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.encoding.HttpEncoding;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
import org.springframework.web.multipart.MultipartFile;
@@ -49,6 +51,7 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead
/**
* @author Spencer Gibb
* @author Scien Jus
* @author Ahmad Mozafarnia
*/
public class SpringEncoder implements Encoder {
@@ -67,8 +70,8 @@ public class SpringEncoder implements Encoder {
throws EncodeException {
// template.body(conversionService.convert(object, String.class));
if (requestBody != null) {
Class<?> requestType = requestBody.getClass();
Collection<String> contentTypes = request.headers().get("Content-Type");
Collection<String> contentTypes = request.headers()
.get(HttpEncoding.CONTENT_TYPE);
MediaType requestContentType = null;
if (contentTypes != null && !contentTypes.isEmpty()) {
@@ -89,31 +92,24 @@ public class SpringEncoder implements Encoder {
}
}
for (HttpMessageConverter<?> messageConverter : this.messageConverters
for (HttpMessageConverter messageConverter : this.messageConverters
.getObject().getConverters()) {
if (messageConverter.canWrite(requestType, requestContentType)) {
if (log.isDebugEnabled()) {
if (requestContentType != null) {
log.debug("Writing [" + requestBody + "] as \""
+ requestContentType + "\" using [" + messageConverter
+ "]");
}
else {
log.debug("Writing [" + requestBody + "] using ["
+ messageConverter + "]");
}
FeignOutputMessage outputMessage;
try {
if (messageConverter instanceof GenericHttpMessageConverter) {
outputMessage = checkAndWrite(requestBody, bodyType,
requestContentType,
(GenericHttpMessageConverter) messageConverter, request);
}
FeignOutputMessage outputMessage = new FeignOutputMessage(request);
try {
@SuppressWarnings("unchecked")
HttpMessageConverter<Object> copy = (HttpMessageConverter<Object>) messageConverter;
copy.write(requestBody, requestContentType, outputMessage);
}
catch (IOException ex) {
throw new EncodeException("Error converting request body", ex);
else {
outputMessage = checkAndWrite(requestBody, requestContentType,
messageConverter, request);
}
}
catch (IOException ex) {
throw new EncodeException("Error converting request body", ex);
}
if (outputMessage != null) {
// clear headers
request.headers(null);
// converters can modify headers, so update the request
@@ -139,7 +135,7 @@ public class SpringEncoder implements Encoder {
}
}
String message = "Could not write request: no suitable HttpMessageConverter "
+ "found for request type [" + requestType.getName() + "]";
+ "found for request type [" + requestBody.getClass().getName() + "]";
if (requestContentType != null) {
message += " and content type [" + requestContentType + "]";
}
@@ -147,6 +143,49 @@ public class SpringEncoder implements Encoder {
}
}
@SuppressWarnings("unchecked")
private FeignOutputMessage checkAndWrite(Object body, MediaType contentType,
HttpMessageConverter converter, RequestTemplate request) throws IOException {
if (converter.canWrite(body.getClass(), contentType)) {
logBeforeWrite(body, contentType, converter);
FeignOutputMessage outputMessage = new FeignOutputMessage(request);
converter.write(body, contentType, outputMessage);
return outputMessage;
}
else {
return null;
}
}
@SuppressWarnings("unchecked")
private FeignOutputMessage checkAndWrite(Object body, Type genericType,
MediaType contentType, GenericHttpMessageConverter converter,
RequestTemplate request) throws IOException {
if (converter.canWrite(genericType, body.getClass(), contentType)) {
logBeforeWrite(body, contentType, converter);
FeignOutputMessage outputMessage = new FeignOutputMessage(request);
converter.write(body, genericType, contentType, outputMessage);
return outputMessage;
}
else {
return null;
}
}
private void logBeforeWrite(Object requestBody, MediaType requestContentType,
HttpMessageConverter messageConverter) {
if (log.isDebugEnabled()) {
if (requestContentType != null) {
log.debug("Writing [" + requestBody + "] as \"" + requestContentType
+ "\" using [" + messageConverter + "]");
}
else {
log.debug(
"Writing [" + requestBody + "] using [" + messageConverter + "]");
}
}
}
private final class FeignOutputMessage implements HttpOutputMessage {
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@@ -16,10 +16,11 @@
package org.springframework.cloud.openfeign.support;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import feign.RequestTemplate;
@@ -27,7 +28,6 @@ import feign.codec.EncodeException;
import feign.codec.Encoder;
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;
@@ -35,12 +35,15 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.cloud.openfeign.encoding.HttpEncoding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ParameterizedTypeReference;
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.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
@@ -60,6 +63,7 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
/**
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
* @author Ahmad Mozafarnia
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringEncoderTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
@@ -74,6 +78,10 @@ public class SpringEncoderTests {
@Qualifier("myHttpMessageConverter")
private HttpMessageConverter<?> myConverter;
@Autowired
@Qualifier("myGenericHttpMessageConverter")
private GenericHttpMessageConverter<?> myGenericConverter;
@Test
public void testCustomHttpMessageConverter() {
Encoder encoder = this.context.getInstance("foo", Encoder.class);
@@ -93,7 +101,35 @@ public class SpringEncoderTests {
assertThat(request.requestCharset()).as("request charset is null").isNotNull();
assertThat(request.requestCharset()).as("request charset is wrong")
.isEqualTo(Charset.forName("UTF-8"));
.isEqualTo(StandardCharsets.UTF_8);
}
// gh-225
@Test
public void testCustomGenericHttpMessageConverter() {
Encoder encoder = this.context.getInstance("foo", Encoder.class);
assertThat(encoder).isNotNull();
RequestTemplate request = new RequestTemplate();
ParameterizedTypeReference<List<String>> stringListType = new ParameterizedTypeReference<List<String>>() {
};
request.header(HttpEncoding.CONTENT_TYPE, "application/mygenerictype");
encoder.encode(Collections.singletonList("hi"), stringListType.getType(),
request);
Collection<String> contentTypeHeader = request.headers().get("Content-Type");
assertThat(contentTypeHeader).as("missing content type header").isNotNull();
assertThat(contentTypeHeader.isEmpty()).as("missing content type header")
.isFalse();
String header = contentTypeHeader.iterator().next();
assertThat(header).as("content type header is wrong")
.isEqualTo("application/mygenerictype");
assertThat(request.requestCharset()).as("request charset is null").isNotNull();
assertThat(request.requestCharset()).as("request charset is wrong")
.isEqualTo(StandardCharsets.UTF_8);
}
@Test
@@ -178,6 +214,11 @@ public class SpringEncoderTests {
return new MyHttpMessageConverter();
}
@Bean
GenericHttpMessageConverter<?> myGenericHttpMessageConverter() {
return new MyGenericHttpMessageConverter();
}
private static class MyHttpMessageConverter
extends AbstractGenericHttpMessageConverter<Object> {
@@ -203,20 +244,77 @@ public class SpringEncoderTests {
@Override
protected void writeInternal(Object o, Type type,
HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
throws HttpMessageNotWritableException {
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throws HttpMessageNotReadableException {
return null;
}
@Override
public Object read(Type type, Class<?> contextClass,
HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throws HttpMessageNotReadableException {
return null;
}
}
private static class MyGenericHttpMessageConverter
extends AbstractGenericHttpMessageConverter<Object> {
MyGenericHttpMessageConverter() {
super(new MediaType("application", "mygenerictype"));
}
private boolean isStringList(Type type) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return parameterizedType.getRawType() == List.class
&& parameterizedType
.getActualTypeArguments()[0] == String.class;
}
else {
return false;
}
}
@Override
protected boolean supports(Class<?> clazz) {
return clazz == List.class;
}
@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return canWrite(mediaType) && isStringList(type);
}
@Override
public boolean canRead(Type type, Class<?> contextClass,
MediaType mediaType) {
return canRead(mediaType) && isStringList(type);
}
@Override
protected void writeInternal(Object o, Type type,
HttpOutputMessage outputMessage)
throws HttpMessageNotWritableException {
}
@Override
public Object read(Type type, Class<?> contextClass,
HttpInputMessage inputMessage)
throws HttpMessageNotReadableException {
return null;
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
throws HttpMessageNotReadableException {
return null;
}
@@ -224,27 +322,4 @@ public class SpringEncoderTests {
}
class MediaTypeMatcher implements ArgumentMatcher<MediaType> {
private MediaType mediaType;
MediaTypeMatcher(String type, String subtype) {
this.mediaType = new MediaType(type, subtype);
}
@Override
public boolean matches(MediaType argument) {
return this.mediaType.equals(argument);
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("MediaTypeMatcher{");
sb.append("mediaType=").append(this.mediaType);
sb.append('}');
return sb.toString();
}
}
}
+2 -2
View File
@@ -6,11 +6,11 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>2.1.9.RELEASE</version>
<version>2.1.10.RELEASE</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<version>2.1.5.RELEASE</version>
<packaging>pom</packaging>
<name>spring-cloud-openfeign-dependencies</name>
<description>Spring Cloud OpenFeign Dependencies</description>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.1.4.RELEASE</version>
<version>2.1.5.RELEASE</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-starter-openfeign</artifactId>