OkHttp and Feign produce "method GET must not have a request body."
See original GitHub issueImagine that we have searchable api which accepts “request” and “pageable” like /api/1.0/search?sort=id,desc&size=20000&name=feign&version=10.3.0 In that case I can’t use “@QueryMap”, because it should present only on one parameter.
This is our REST API interface:
@Headers(value = ["Accept: application/json", "Content-Type: application/json"])
interface ControllerApi {
@RequestLine("GET /api/1.0/search?{request}&{pageable}")
fun someEndpoint(
@Param(
value = "request",
expander = GenericDataClassExpander::class
) request: SearchRequestDto,
@Param(
value = "pageable",
expander = PageableExpander::class) pageable: Pageable
)
This is our custom DTO definition:
data class SearchRequestDto(
@field:ApiModelProperty("search by name contains")
@field:JsonProperty("name")
var name: String? = null,
@field:ApiModelProperty("search by version contains")
@field:JsonProperty("version")
var version: String? = null
)
Pageable is Spring commonly used interface.
These are the expanders that we are using to generate the final request:
class PageableExpander : Param.Expander {
override fun expand(value: Any): String {
val pageable = value as? Pageable
?: throw IllegalStateException("Error while expanding Pageable")
val page = pageable.pageNumber
val size = pageable.pageSize
val sort = pageable.sort
var res = ""
res += "page=$page&"
res += "size=$size&"
if (sort != null) {
res += "sort=$sort"
}
return res
}
}
class GenericDataClassExpander : Param.Expander {
override fun expand(value: Any): String {
val res = value::class.declaredMemberProperties.fold("") { acc, field ->
field as KProperty1<Any, *>
val fieldValue = field.get(value)
if (fieldValue != null) {
acc + "${field.name}=$fieldValue" + "&"
} else {
acc
}
}
return res.removeSuffix("&")
}
}
The problem that we faced is that while using this API, Feign is unable to construct a proper QueryTemplate, since the CollectionFormat is supposed to be something like ?[key]=[value], e.g. key1={value1}&key2={value2}. However, we would like to omit having keys and just include already expanded values like ?{expanded-value}.
Therefore, if we do not follow the standart CollectionFormat ?[key]=[value], the QueryTemplate gets constructed with empty templateChunks, so the getVariables method returns empty array. Then, in feign.Contract we could see this:
if (!data.template().hasRequestVariable(name)) {
data.formParams().add(name);
}
In our case when getVariables is empty, it will add our query parameters to form parameters and therefore to the body of RequestTemplate, which is not what we want. When trying to run this request via the OkHttpClient, we will receive an exception that “method GET must not have a request body.”
This behavior seems strange to me, since it basically violates the standards. And this is obviously not the behavior what I want when I generate the custom request line myself with expanders.
Maybe you could consider adding a simple condition to prevent adding any form params for the GET request like this?
HttpMethod method = HttpMethod.valueOf(data.template().getMethod());
if (method != HttpMethod.GET || !data.template().hasRequestVariable(name)) {
data.formParams().add(name);
}
I would love to hear any response of this, but please be aware that I cannot change the API that I am using and I have to stick with it.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:11
- Comments:13 (7 by maintainers)
@kdavisk6
Hi Kevin,
Yes, I checked the
@QueryMap
as I wrote in the begging. And I also wrote, “that I cannot change the API” because “feign api” it’s an interface first of all and I would like to use it for Inheritance and for consistency between web-controller and api to this controller.So basically I have
And I would not prefer either use “inconsistency way” where is my controller and api to that controller not inherited like this
or “bad arguments for controller way” like this:
But I still can’t understand why request parameters with expanders should be in forms parameters(in body of request when it’s request parameters)
I’ve considered a few other options but I think the best answer for this issue has been merged and released in #1144. With regards to the naming issue, I think that #1019 will provide the right balance of support and flexibility. I’m going to close this issue and ask that any additional comments be placed in #1019