How to POST form-url-encoded data with Spring Cloud Feign
Asked Answered
V

9

31

Using spring-mvc annotations:

  • How can I define an @FeignClient that can POST form-url-encoded?
Verdellverderer answered 4/3, 2016 at 18:8 Comment(0)
E
29

Use FormEncoder for Feign:

And your Feign configuration can look like this:

class CoreFeignConfiguration {
  @Autowired
  private ObjectFactory<HttpMessageConverters> messageConverters

  @Bean
  @Primary
  @Scope(SCOPE_PROTOTYPE)
  Encoder feignFormEncoder() {
      new FormEncoder(new SpringEncoder(this.messageConverters))
  }
}

Then, the client can be mapped like this:

@FeignClient(name = 'client', url = 'localhost:9080', path ='/rest',
    configuration = CoreFeignConfiguration)
interface CoreClient {
    @RequestMapping(value = '/business', method = POST, 
                 consumes = MediaType.APPLICATION_FORM_URLENCODED)
    @Headers('Content-Type: application/x-www-form-urlencoded')
    void activate(Map<String, ?> formParams)
}
Elitism answered 5/7, 2017 at 10:57 Comment(5)
Take care of this line Map<String, ?> formParams, the question mark is required.Superhuman
For those who don't recognise groovy - this is in groovy thus not "return", ";" etc :)Elitism
Not really useful, it requires my pojo to have @FormProperty but doesn't check super classes, I can't provide 20 form properties separately into client call.Semiology
You don't need extra headers annotation, feign automatically place it with the consumes config.Disavow
I used this approach and Content-Type is overridden by "application/json". Any suggestions?Horripilation
H
26

Full Java code with a simplified version of kazuar solution, works with Spring Boot:

import java.util.Map;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;

@FeignClient(name = "srv", url = "http://s.com")
public interface Client {

    @PostMapping(value = "/form", consumes = APPLICATION_FORM_URLENCODED_VALUE)
    void login(@RequestBody Map<String, ?> form);

    class Configuration {
        @Bean
        Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> converters) {
            return new SpringFormEncoder(new SpringEncoder(converters));
        }
    }
}

Dependency:

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Hallucination answered 23/2, 2019 at 13:12 Comment(7)
It might be unrelated but worth mentioning. If you are expecting a JSON response. You may want to configure the @Bean for Decoder return new GsonDecoder();Boeotia
No, there is already Jackson decoder configured in spring boot.Hallucination
Yes, that is right. a default JSONDecoder is already configured. However, I had enabled gson as default converter and using a customized version @Bean Gson upbeatGson() { return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES).create();} - hence I mentioned. Otherwise, if the JSONDecoder - default version works, there is no need.Boeotia
Also please note that params annotated with @RequestParam still go to the URL, only @RequestBody Map<String, ?> seems to be working.Earshot
The Map<String, ?> do work, and putting an POJO entity with the proper fields there works as well.Incision
in your @FeignClient annotation just missed configuration = yourClass.Configuration.classAtkins
As @HenriqueSchmitt said, I had to set @FeignClient(configuration = Client.Configuration.class) for this to work. The answer should be editedSackett
A
11

Just to complement accepted answer, one can also use POJO instead of Map<String, ?> in order to pass form parameters to feign client:

@FeignClient(configuration = CustomConfig.class)
interface Client {

    @PostMapping(
        path = "/some/path", 
        consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    void postComment(CommentFormDto formDto);
    ...
}
...
@Configuration
class CustomConfig {
    @Bean
    Encoder formEncoder() {
        return new feign.form.FormEncoder();
    }
}
...

class CommentFormDto {

    private static String willNotBeSerialized;

    private final Integer alsoWillNotBeSerialized;

    @feign.form.FormProperty("author_id")
    private Long authorId;

    private String message;

    @feign.form.FormProperty("ids[]")
    private List<Long> ids;
    
    /* getters and setters omitted for brevity */  
}

That will result in request with body that looks something like this:

author_id=42&message=somemessage&ids[]=1&ids[]=2

@FormProperty annotation allows to set custom field names; please note that static or final fields of POJO, along with inherited ones, will not be serialized as form content.

for Kotlin:

import org.springframework.http.MediaType

@FeignClient(configuration = [CustomConfig::class])
interface Client {

    @PostMapping(
        path = "/some/path", 
        consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
    postComment(CommentFormDto formDto): responseDto
    ...
}
...
import feign.form.FormEncoder

@Configuration
class CustomConfig {
    @Bean
    fun formEncoder(): FormEncoder {
        return FormEncoder()
    }
}
...
import feign.form.FormProperty

data class CommentFormDto (

    @FormProperty("author_id")
    var authorId: Long

    @FormProperty("ids[]")
    var ids: List<Long>

)
Anelace answered 14/12, 2021 at 9:42 Comment(2)
Thank you so much! I've come across this same problem and definitely @FormProperty is what I was looking for! +1Postman
There is little mistake in the answer. It would be @FormProperty instead @FeignProperty, but I can't edit it.Drongo
D
3

Just an additional contribution... It is possible to use a Spring abstraction, the org.springframework.util.MultiValueMap, which has no other dependencies (only spring-cloud-starter-openfeign).

@PostMapping(value = "/your/path/here", 
        consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
YourDtoResponse formUrlEncodedEndpoint(MultiValueMap<String, Object> params);

The syntax of this useful data structure is quite simple, as shown below:

MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("param1", "Value 1");
params.add("param2", 2);
Demarche answered 3/2, 2023 at 20:7 Comment(0)
K
2

This worked for me

@FeignClient(name = "${feign.repository.name}", url = "${feign.repository.url}")
public interface LoginRepository {

     @PostMapping(value = "${feign.repository.endpoint}", consumes = APPLICATION_FORM_URLENCODED_VALUE)
     LoginResponse signIn(@RequestBody Map<String, ?> form);
}

form is Map<String, Object> loginCredentials = new HashMap<>();

Kyte answered 10/10, 2022 at 13:7 Comment(2)
Referenced from MariuszS's answerKyte
Update: used POJO LoginResponse signIn(@RequestBody Request obj); Thanks @AnelaceKyte
C
1

You must use FormEncoder in Feign encoder for url-form-encoded data in POST.

Include the dependency to your app:

Maven:

<dependency>
  <groupId>io.github.openfeign.form</groupId>
  <artifactId>feign-form</artifactId>
  <version>3.8.0</version>
</dependency>

Add FormEncoder to your Feign.Builder like so:

SomeFeign sample  = Feign.builder()
                      .encoder(new FormEncoder(new JacksonEncoder()))
                      .target(SomeFeign.class, "http://sample.test.org");

In the Feign interface

@RequestLine("POST /submit/form")
@Headers("Content-Type: application/x-www-form-urlencoded")
void from (@Param("field1") String field1, @Param("field2") String field2);

Ref for more info: https://github.com/OpenFeign/feign-form

Cerate answered 27/7, 2020 at 17:28 Comment(1)
This method causes a "Java.lang.NoSuchMethodError".Troutman
D
1

Tested in Kotlin: What worked for me was the following:

  1. Create feign configuration:
@Configuration
class FeignFormConfiguration {
    @Bean
    fun multipartFormEncoder(): Encoder {
        return SpringFormEncoder(SpringEncoder {
            HttpMessageConverters(
                RestTemplate().messageConverters
            )
        })
    }
}
  1. In your feign client:
@FeignClient(
    value = "client",
    url = "localhost:9091",
    configuration = [FeignFormConfiguration::class]
)
interface CoreClient {
    @RequestMapping(
        method = [RequestMethod.POST],
        value = ["/"],
        consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE],
        produces = ["application/json"]
    )
    fun callback(@RequestBody form: Map<String, *>): AnyDTO?
}
  1. Consume feign client:
// ----- other code --------
@Autowired
private lateinit var coreClient: CoreClient

fun methodName() {
  coreClient.callback(form)
}

// ----- other code --------

Drome answered 25/8, 2022 at 8:19 Comment(0)
A
0

For the Feign.Builder, mine worked without the JacksonEncoder, just the Feign FormEncoder:

Add FormEncoder to your Feign.Builder:

SomeFeign sample  = Feign.builder()
                  .encoder(new FormEncoder())     <==difference here
                  .target(SomeFeign.class, "http://sample.test.org");

The feign dependencies I added to pom.xml:

<dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-core</artifactId>
        <version>11.8</version>
    </dependency>

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-jackson</artifactId>
        <version>11.8</version>
    </dependency>
    
    <dependency>
        <groupId>io.github.openfeign.form</groupId>
        <artifactId>feign-form</artifactId>
        <version>3.8.0</version>
    </dependency>

Parent in pom.xml is:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.2</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

The feign interface as Ramanan gave:

@RequestLine("POST /submit/form")
@Headers("Content-Type: application/x-www-form-urlencoded")
void from (@Param("field1") String field1, @Param("field2") String field2);
Alien answered 27/12, 2021 at 19:21 Comment(0)
C
0
public class CoreFeignConfiguration {

@Autowired
  private ObjectFactory<HttpMessageConverters> messageConverters;

  @Bean
  @Primary
  @Scope("prototype")
  Encoder feignFormEncoder() {
      return new FormEncoder(new SpringEncoder(this.messageConverters));
  }
  @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
  
  @Bean
  Logger logger() {
    return  new MyLogger();
  }
  private static class MyLogger extends Logger {
        @Override
        protected void log(String s, String s1, Object... objects) {
            System.out.println(String.format(s + s1, objects)); // Change me!
        }
  }

}

In interface @FeignClient(name = "resource-service1", url = "NOT_USED", configuration = CoreFeignConfiguration.class)

public interface TestFc {

@RequestMapping( method=RequestMethod.POST,consumes = "application/x-www-form-urlencoded")
//@Headers("Content-Type: application/x-www-form-urlencoded")
ResponseEntity<String>  test(URI baseUrl,
        Map<String,?> request);

}

Chobot answered 30/3, 2023 at 6:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.