Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
anno1985 opened this issue Oct 16, 2019 · 8 comments

Comments

@anno1985
Copy link

anno1985 commented Oct 16, 2019

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
Copy link
Author

anno1985 commented Oct 17, 2019

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
Copy link

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
Copy link

I hope this request has not been forgotten.

@getaceres
Copy link

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
Copy link

sean-huni commented Jan 19, 2022

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
Copy link

I hope this request has not been forgotten.

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

@upfluentpatrickpilch
Copy link

@yandinizzup I suspect the root cause for you is #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
Copy link

in kotlin var saved my day. Thanks @upfluentpatrickpilch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants