OpenFeign / feign-form

Open Feign form encoder
Apache License 2.0
291 stars 81 forks source link

Running @FeignClient with application/x-www-form-urlencoded throws IllegalStateException: Method has too many Body parameters #79

Open anno1985 opened 5 years ago

anno1985 commented 5 years ago

Is it possible to use feign-form's support for application/x-www-form-urlencoded forms with Spring Cloud's @EnableFeignClients/@FeignClient setup?

Currently, I am getting the following exception when starting the ApplicationContext:

java.lang.IllegalStateException: Method has too many Body parameters: public abstract java.lang.String com.example.feigntest.MyClient.foo(java.lang.String,java.lang.String,java.lang.String)

To me, that is an indication that loading the feign-form support didn't quite work.

I'm using org.springframework.cloud:spring-cloud-starter-openfeign, org.springframework.boot:spring-boot-starter and for dependencyManagement org.springframework.cloud:spring-cloud-dependencies:Greenwich.SR3, all with a org.springframework.boot:spring-boot-starter-parent:2.1.9.RELEASE parent.

My setup, running against a simple standalone wiremock with --print-all-network-traffic flag and a single simple mock:

@EnableFeignClients
@SpringBootApplication
public class FeignTestApplication implements CommandLineRunner {
    @Autowired private MyApp myApp;
    public static void main(String[] args) { SpringApplication.run(FeignTestApplication.class, args); }
    @Override public void run(String... args) { myApp.run(); }
}

@Component
public class MyApp {
    @Autowired private MyClient myClient;
    public void run() { String result = myClient.foo("a", "b", "c"); }
}

@FeignClient(name="myClient", url = "http://localhost:8080", configuration = MyClientConfig.class)
public interface MyClient {
    @RequestMapping(method = RequestMethod.POST, value = "/foo")
    @Headers("Content-Type: application/x-www-form-urlencoded")
    public String foo(@Param("p1") String p1, @Param("p2") String p2, @Param("p3") String p3);
}

public class MyClientConfig { // NB: is @Configuration required here?
    @Autowired private ObjectFactory<HttpMessageConverters> messageConverters;
    @Bean public Encoder feignFormEncoder () { return new SpringFormEncoder(new SpringEncoder(messageConverters)); }
}

When I remove the Spring Cloud feign annotations (@EnableFeignClients, @FeignClient) and replace both the autowired myClient property in MyApp with private MyClient myClient = Feign.builder().encoder(new FormEncoder()).target(MyClient.class, "http://localhost:8080"); and the @RequestMapping annotation in MyClient with @RequestLine("POST /foo"), it all works.

So, I know I haven't gone completely wrong. I'm basically just wondering if feign-forms support for application/x-www-form-urlencoded forms can be made to work with Spring Cloud's @EnableFeignClients/@FeignClient magic (and I've made a mistake somewhere), or if the exception is to be expected?

Also (and probably somewhat unrelated), would I have to use the @Configuration annotation on the MyClientConfig class if I used the @FeignClient annotation and wanted to refine the config programatically?

anno1985 commented 5 years ago

NB: Looks like the following does the trick, without the @Headers annotation:

@RequestMapping(method = RequestMethod.POST, value = "/foo", consumes = "application/x-www-form-urlencoded")
public String foo(MyPojo payload);

with a wrapper whose properties match the params from before:

public class MyPojo {
    public MyPojo(String p1, String p2, String p3) {
        this.p1 = p1;
        this.p2 = p2;
        this.p3 = p3;
    }
    public String p1, p2, p3;

Log:

2019-10-17 11:05:04.269 Incoming bytes: POST /foo HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
User-Agent: Java/1.8.0_212
Host: localhost:8080
Connection: keep-alive
Content-Length: 14

2019-10-17 11:05:04.270 Incoming bytes: p1=a&p2=b&p3=c

Oh, and btw: the @Configuration is not required.

yandinizzup commented 4 years ago

I tried to use following code, but I got the same error when I was using LinkedHasMap.

I tried to use FormEncoder Configuration, but when I try to do a normal request, in other Client he tries to encode my request without set configuration in Client properties.

@FeignClient(name = "Authentication", url = "\${url.sts}")
interface Authentication {
    @PostMapping(consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
            value = ["/api/oauth/token"],
            produces = ["application/json"])
    fun auth(forms: Headers): Auth
}

Headers class:

class Headers() {

    @JsonProperty("grant_type")
    val grantType: String = GRANT_TYPE_STS
    @JsonProperty("x-flow-ID")
    val flowId = UUID.randomUUID().toString()
    @JsonProperty("x-correlationID")
    val correlationId = UUID.randomUUID().toString()
    @JsonProperty("client_id")
    var clientId: String = Strings.EMPTY
    @JsonProperty("client_secret")
    var clientSecret: String = Strings.EMPTY

    constructor(clientId: String, clientSecret: String): this(){
        this.clientId = clientId
        this.clientSecret = clientSecret
    }

    companion object {
        const val GRANT_TYPE_STS = "client_credentials"
    }
}

Produces class:

class Auth() {
    @JsonProperty("access_token")
    var accessToken: String = Strings.EMPTY

    @JsonProperty("token_type")
    var tokenType: String = Strings.EMPTY

    @JsonProperty("expires_in")
    var expiresIn: Int = 0

    @JsonProperty("refresh_token")
    var refreshToken: String = Strings.EMPTY

    var scope: String = Strings.EMPTY

    var active: Boolean = true
}

If someone knows any solution without use Encoder bean I'll be thankfull

sean-huni commented 3 years ago

I hope this request has not been forgotten.

getaceres commented 3 years ago

I found this issue when using a Feign client in my Spring Boot project. I've tried to create a POJO with the parameters but I need them to be translated. For example, the property clientId needs to be "client_id". I've tried putting @JsonProperty and @Param annotations in each field but they get ignored.

sean-huni commented 2 years ago

I found this issue when using a Feign client in my Spring Boot project. I've tried to create a POJO with the parameters but I need them to be translated. For example, the property clientId needs to be "client_id". I've tried putting @JsonProperty and @param annotations in each field but they get ignored.

If you're using Gson you should use the @SerializedName("access_token") property instead of the @JsonProperty.

sean-huni commented 2 years ago

I hope this request has not been forgotten.

I figured it out using SpringFeign with some hectic java-based configuration.

upfluentpatrickpilch commented 2 years ago

@yandinizzup I suspect the root cause for you is https://github.com/OpenFeign/feign-form/issues/77, which unfortunately isn't getting any attention.

Spring boot correctly wired up the form encoder for me, and I didn't need to explicitly do anything for it in my configuration(s). But because vals have a backing field marked with final, I had to work around feign-form's bug by making my properties mutable (removing the final modifier):

class FormBody(
    @FormProperty("the_name")
    var theName: String
)
jongsun112 commented 1 year ago

in kotlin var saved my day. Thanks @upfluentpatrickpilch