I checked the documentation of feign-form. It supports:
interface SomeApi {
// Group all parameters within a POJO
@RequestLine("POST /user")
@Headers("Content-Type: application/x-www-form-urlencoded")
void addUser (User user);
}
But the following code does not work properly:
@FeignClient(name = "test", url = "http://localhost:8080")
public interface TestFeign {
@PostMapping(value = "/test", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
String test(TestEntity test);
}
Exception:
Caused by: feign.codec.EncodeException: Could not write request: no suitable HttpMessageConverter found for request type [com.example.demo.TestEntity] and content type [application/x-www-form-urlencoded]
But using SpringFormEncoder as Encoder can solve this problem:
@Configuration(proxyBeanMethods = false)
public class FormSupportConfig {
@Bean
public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
I found that the code that caused the problem came from here:
https://github.com/spring-cloud/spring-cloud-openfeign/blob/58b0c7866c3675f8b6cece0746948dcf98f4ef64/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java#L98-L101
Here only the type of mutipart is checked and the type of urlencoded is ignored.
But this type will be processed by the parent class FormEncoder of SpringFormEncoder.
Comment From: cbezmen
Hi @Junzzzz ,
Did you check @SpringQueryMap annotation?
https://cloud.spring.io/spring-cloud-openfeign/reference/html/#feign-querymap-support
Comment From: Junzzzz
Thanks for your reply @cbezmen
I tried this annotation, and its function is to splice the parameters into the URL instead of putting them in the Request Body.
Actually:
[TestFeign#test] ---> POST http://localhost:8080/test?bar=456&foo=123 HTTP/1.1
[TestFeign#test] Content-Type: application/x-www-form-urlencoded
[TestFeign#test] ---> END HTTP (0-byte body)
Expect:
[TestFeign#test] ---> POST http://localhost:8080/test HTTP/1.1
[TestFeign#test] Content-Length: 15
[TestFeign#test] Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[TestFeign#test]
[TestFeign#test] bar=456&foo=123
[TestFeign#test] ---> END HTTP (15-byte body)
Comment From: cbezmen
Hi @Junzzzz
I don't know why you don't use 'Content-Type: application/json' and want to use 'Content-Type: application/x-www-form-urlencoded'. In your problem Spring encode can't find possible converter because You didn't put any annotation to your input. So It will think like @RequestBody and try to parse your object as body. In addition to this It can't find right converter so it throws "Could not write request: no suitable HttpMessageConverter found for request"
Anyway I figured out your request and example code should be like this. I think you should put @RequestPart annotation to your code and it will work like charm.
@FeignClient
public interface TestClient {
@PostMapping(value = "/user", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
User test(@RequestPart User test); // You should put @RequestPart annotation here
}
@RestController
@Slf4j
public class TestController {
@PostMapping(value = "/user", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public User test(User test) {
log.info(test);
return test;
}
}
@Data
public class User {
private String name;
}
// Just call you
User user = new User();
user.setName("Can");
testClient.test(user);
Comment From: Junzzzz
Thank you for your enthusiastic help @cbezmen
I tried the following code, but still can't find a suitable HttpMessageConverter
@EnableFeignClients
@SpringBootApplication
public class DemoApplication {
@Autowired
TestClient testClient;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public ApplicationRunner test() {
return args -> {
User user = new User();
user.setName("Can");
testClient.test(user);
};
}
@FeignClient(name = "test", url = "http://localhost:8080")
public interface TestClient {
@PostMapping(value = "/user", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
User test(@RequestPart User test); // You should put @RequestPart annotation here
}
@RestController
@Slf4j
public static class TestController {
@PostMapping(value = "/user", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public User test(User test) {
log.info(String.valueOf(test));
return test;
}
}
@Data
public static class User {
private String name;
}
}
I looked through some documents and couldn't find a suitable annotation. Later, I found that SpringFormEncoder contains support for this type, and it doesn't need any annotations. However, this type is ignored in SpringEncoder. I guess it may be ignored when encapsulating, so I submitted this issue.
As in the first comment, I already have a solution, just mention this problem.
By the way, I use feign as an HTTP request tool because it is very concise to write requests. However, the requested API is relatively old, so this type has to be used.
I don’t know if Google Translate clearly expresses what I mean, sorry.
Comment From: cbezmen
Hey @Junzzzz, Sorry for late reply because of weekend.
I think I figured out your problem. You can checkout this link. https://stackoverflow.com/questions/35803093/how-to-post-form-url-encoded-data-with-spring-cloud-feign
I also commit my test repo to https://github.com/cbezmen/junzzzz.
import feign.codec.Encoder;
import feign.form.FormEncoder;
import feign.form.spring.SpringFormEncoder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.support.FeignEncoderProperties;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableFeignClients
@Slf4j
public class JunzzzzApplication {
public static void main(String[] args) {
SpringApplication.run(JunzzzzApplication.class, args);
}
@Autowired
private TestClient testClient;
@Bean
public ApplicationRunner test() {
return args -> {
User user = new User();
user.setName("Can");
User test = testClient.test(user);
log.info("{}", test);
};
}
@FeignClient(name = "test", url = "http://localhost:8080", configuration = CoreFeignConfiguration.class)
public interface TestClient {
@PostMapping(value = "/user", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
User test(User test);
}
class CoreFeignConfiguration {
@Bean
Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters, FeignEncoderProperties feignEncoderProperties) {
return new FormEncoder(new SpringEncoder(new SpringFormEncoder(), messageConverters, feignEncoderProperties));
}
}
@RestController
@Slf4j
public static class TestController {
@PostMapping(value = "/user", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public User test(User test) {
log.info(String.valueOf(test));
return test;
}
}
@Data
public static class User {
private String name;
}
}
Comment From: cbezmen
Hey @OlgaMaciaszek,
His code not working because of the code below. It will automatically support if we add MediaType.APPLICATION_FORM_URLENCODED_VALUE. Do you think it is logical? I can add and test it if you want?
https://github.com/spring-cloud/spring-cloud-openfeign/blob/280291ec93111fc690e8479c607c8615e61c5194/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java#L210
private boolean isMultipartType(MediaType requestContentType) {
return Arrays.asList(MediaType.MULTIPART_FORM_DATA, MediaType.MULTIPART_MIXED, MediaType.MULTIPART_RELATED)
.contains(requestContentType);
}
Comment From: OlgaMaciaszek
@cbezmen yes, makes sense. Could you please verify if everything works correctly with that change and submit a PR?