Feign: Need explanation on Feign use case to pass values transparently

Created on 11 Jun 2019  ยท  3Comments  ยท  Source: OpenFeign/feign

Hello,

I'm sorry but I may be misusing the issue tracker, but I think I need some explanation about the intended usage for Feign.
I'm in the process of replacing a legacy RPC system and I started to use Feign to communicate over http.
Problem is that things seemed to work but all of a sudden I realized calls were not correctly processed in some case.
For exemple, if a string I want to pass to the server contains the semicolon character ";" then Feign splits it into multiple values for the same pathVariable (see Template.COLLECTION_DELIMITER).

Here is my spring controller (server side)

@PutMapping(saveUserParameter)
    public void saveUserParameter(@RequestParam("application") String application,
        @RequestParam("context") String context,
        @RequestParam("parameter") String parameter,
        @RequestParam("value") String value)

Here is my Feign annotated interfacer (client-side)

@RequestLine(PUT + saveUserParameter + saveUserParameterParameters)
    void saveUserParameter(@Param("application") String application,
        @Param("context") String context, 
        @Param("parameter") String parameter,
        @Param(value = "value") String value);

In the end, I have the feeling that what I intended to do ("transparently passing any data containing any value without having to care") is not a good use case for Feign (or maybe http in general). I cannot just define my interface and hope it will work on the java side whatever the value being passed.

Is my analysis correct ?
What would you recommend ? Would using a DTO object wrapping all the String parameters in a single object and passed in the body would be sufficient to "just pass any content in the strings and ensure it would work" ?

documentation question

Most helpful comment

@fmarot

Feign relies on URI Templates to manage how a given URL should be constructed, how values should be included, and how those values should be encoded. You have a few options available to meet, what I think, is your original question, How to use Feign to send varying number of parameters without the need to declare each one?

The first step is to explicitly declare the base uri for the request. This must be done using the appropriate annotation on the interface method. From your example, I'm going to assume that you are using the Spring Cloud variation:

public class Api {
   @PutMapping("/path")
   void saveParameters(@RequestParam("param") String parameter);
}

Request Parameters (Query String)

Using the above example, Feign will append a query string parameter, param to the uri /path. Assuming that parameter=test, the resulting uri will be /path?param=test. This is the most basic case, when using uri parameters. (note: if your use case deals with a request body, I'll cover that shortly).

If your use case has too many uri parameters to realistically declare or the number of uri parameters vary by request, the simplest approach is to use a Map and the QueryMap annotation.

public class Api {
   @PutMapping("/path")
   void saveParameters(@QueryMap Map<String, Object> parameters);
}

Each key/value pair in the parameters map will be expanded into a query parameter set. Simple objects are also supported:

public class Api {
   @PutMapping("/path")
   void saveParameters(@QueryMap Parameters parameters);
}

In this case, the properties of the Parameters object will be expanded into a query parameter set. This is explained in more detail in our documentation.

Request Body

Passing information as part of the request body requires the use of an Encoder. Spring Cloud includes a special encoder, SpringEncoder that uses Spring MVC annotation conversion support. This relies on defining a media type in the mapping annotation, produces

public class Api {
   @PutMapping("/path", produces = "application/json")
   void saveParameters(Parameters parameters);
}

This configuration will inform Spring that Parameters should be converted to JSON before being send to the host. How this encoding occurs and what types are supported are up to the Encoder instance. We have a number of built in encoders and Spring provides a good set too. Most of the major providers such as Jackson, GSON, and SAX have encoders available.

What about semi-colons (;)

This is a limitation of our current implementation. If you are using a semi-colon in a value, you must pct-encode it. I am working on a different uri template implementation that will remove this restriction.

All of this information, including more robust examples, is included our README. I recommend that you read it over, especially the section on Dynamic Query Parameters. I've included links in my response to those areas.

Also, familiarize yourself with the URI Template Specification. It describes how templates can be constructed and their expected results.

I also recommend thinking about how to express the services you are calling like regular functions on an object and not like HTTP requests. This is the most powerful feature of Feign. It allows for clear expression of use of a given service allowing consumers to "pay no attention to the man behind the curtain"

All 3 comments

A bit more explanation about my misunderstanding. I totally do not understand why this bug https://github.com/OpenFeign/feign/issues/235 was closed. OK there is a ling explaining that I can add the "decodeSlash" param. But I do not know exactly what strings will be passed. If the parameter is a 'product name', then I know in advance that the characters to be encoded span the whole range, ie โ€œ;โ€, โ€œ/โ€, โ€œ?โ€, โ€œ:โ€, โ€œ@โ€, โ€œ=โ€ and โ€œ&โ€. So I'd just like Feign to uri-encode whatever String I pass without having to configure anything. Why doesn't it work this way ? For now I have worked around the problem by passing every String in the request body... Not very sexy...

@fmarot

Feign relies on URI Templates to manage how a given URL should be constructed, how values should be included, and how those values should be encoded. You have a few options available to meet, what I think, is your original question, How to use Feign to send varying number of parameters without the need to declare each one?

The first step is to explicitly declare the base uri for the request. This must be done using the appropriate annotation on the interface method. From your example, I'm going to assume that you are using the Spring Cloud variation:

public class Api {
   @PutMapping("/path")
   void saveParameters(@RequestParam("param") String parameter);
}

Request Parameters (Query String)

Using the above example, Feign will append a query string parameter, param to the uri /path. Assuming that parameter=test, the resulting uri will be /path?param=test. This is the most basic case, when using uri parameters. (note: if your use case deals with a request body, I'll cover that shortly).

If your use case has too many uri parameters to realistically declare or the number of uri parameters vary by request, the simplest approach is to use a Map and the QueryMap annotation.

public class Api {
   @PutMapping("/path")
   void saveParameters(@QueryMap Map<String, Object> parameters);
}

Each key/value pair in the parameters map will be expanded into a query parameter set. Simple objects are also supported:

public class Api {
   @PutMapping("/path")
   void saveParameters(@QueryMap Parameters parameters);
}

In this case, the properties of the Parameters object will be expanded into a query parameter set. This is explained in more detail in our documentation.

Request Body

Passing information as part of the request body requires the use of an Encoder. Spring Cloud includes a special encoder, SpringEncoder that uses Spring MVC annotation conversion support. This relies on defining a media type in the mapping annotation, produces

public class Api {
   @PutMapping("/path", produces = "application/json")
   void saveParameters(Parameters parameters);
}

This configuration will inform Spring that Parameters should be converted to JSON before being send to the host. How this encoding occurs and what types are supported are up to the Encoder instance. We have a number of built in encoders and Spring provides a good set too. Most of the major providers such as Jackson, GSON, and SAX have encoders available.

What about semi-colons (;)

This is a limitation of our current implementation. If you are using a semi-colon in a value, you must pct-encode it. I am working on a different uri template implementation that will remove this restriction.

All of this information, including more robust examples, is included our README. I recommend that you read it over, especially the section on Dynamic Query Parameters. I've included links in my response to those areas.

Also, familiarize yourself with the URI Template Specification. It describes how templates can be constructed and their expected results.

I also recommend thinking about how to express the services you are calling like regular functions on an object and not like HTTP requests. This is the most powerful feature of Feign. It allows for clear expression of use of a given service allowing consumers to "pay no attention to the man behind the curtain"

Thanks you very much @kdavisk6 !
Your answer is very valuable to me to understand the philosophy of Feign.
So in the end it seems that only ';' and '/' (semi colons and slashes) may be problematic for me. I've switched all my calls potentially containing such characters to use the request body.
I will have to read the URI Template spec that I didn't know about.

Thanks !

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cdancy picture cdancy  ยท  6Comments

Tal24 picture Tal24  ยท  5Comments

sean-huni picture sean-huni  ยท  3Comments

mr-rajeshrathod picture mr-rajeshrathod  ยท  6Comments

budaimartin picture budaimartin  ยท  4Comments