feign icon indicating copy to clipboard operation
feign copied to clipboard

Unable to send empty JSON object with `@Body`

Open otbutz opened this issue 1 year ago • 3 comments

Both approaches don't seem to work using feign 12.3 with okhttp and GSON encoder/decoder:

@Headers({"Accept: application/json", "Content-Type: application/json"})
public interface Api {

    @RequestLine("POST /empty")
    @Body("%7B%7D")
    void postEncoded();

    @RequestLine("POST /empty")
    @Body("{}")
    void postUnencoded();
}

The first sends it verbatim, which can't be parsed by the server, and the second triggers the expansion logic, creating an exception:

java.lang.IllegalArgumentException: an expression is required.
	at feign.template.Expressions.create(Expressions.java:68) ~[feign-core-12.3.jar:?]
	at feign.template.Template.parseFragment(Template.java:218) ~[feign-core-12.3.jar:?]
	at feign.template.Template.parseTemplate(Template.java:202) ~[feign-core-12.3.jar:?]
	at feign.template.Template.<init>(Template.java:61) ~[feign-core-12.3.jar:?]
	at feign.template.BodyTemplate.<init>(BodyTemplate.java:54) ~[feign-core-12.3.jar:?]
	at feign.template.BodyTemplate.create(BodyTemplate.java:50) ~[feign-core-12.3.jar:?]
	at feign.RequestTemplate.bodyTemplate(RequestTemplate.java:909) ~[feign-core-12.3.jar:?]
	at feign.Contract$Default.lambda$new$2(Contract.java:285) ~[feign-core-12.3.jar:?]
	at feign.DeclarativeContract$GuardedAnnotationProcessor.process(DeclarativeContract.java:253) ~[feign-core-12.3.jar:?]
	at feign.DeclarativeContract.lambda$processAnnotationOnMethod$7(DeclarativeContract.java:93) ~[feign-core-12.3.jar:?]
	at java.util.ArrayList.forEach(ArrayList.java:1511) ~[?:?]
	at feign.DeclarativeContract.processAnnotationOnMethod(DeclarativeContract.java:93) ~[feign-core-12.3.jar:?]
	at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:110) ~[feign-core-12.3.jar:?]
	at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:65) ~[feign-core-12.3.jar:?]
	at feign.DeclarativeContract.parseAndValidateMetadata(DeclarativeContract.java:38) ~[feign-core-12.3.jar:?]
	at feign.ReflectiveFeign$ParseHandlersByName.apply(ReflectiveFeign.java:134) ~[feign-core-12.3.jar:?]
	at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:56) ~[feign-core-12.3.jar:?]
	at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:48) ~[feign-core-12.3.jar:?]
	at feign.Feign$Builder.target(Feign.java:196) ~[feign-core-12.3.jar:?]
	at feign.Feign$Builder.target(Feign.java:192) ~[feign-core-12.3.jar:?]

otbutz avatar May 10 '23 13:05 otbutz

According to the javadoc, I would have expected the first variant to work?

https://github.com/OpenFeign/feign/blob/ae16dda47ee7fcb84d4d1cc843842fb7314598bd/core/src/main/java/feign/Body.java#L33

otbutz avatar May 10 '23 13:05 otbutz

The first method will post the pct-encoded values verbatim, as you've discovered, because the we do not decode already encoded data. The second will not work as well, since the body will be parsed like an expression.

You may need to look into providing a custom Encoder instance that can generate the empty JSON document instead of using the @Body annotation. Review the documentation on the @Body annotation for more hints. You may need to explicitly set a Content-Type header.

The first sends it verbatim, which can't be parsed by the server

The request should be parsed by a server that accepts pct-encoding, which should be every HTTP compliant server out there, so this may be something to look into.

kdavisk6 avatar May 24 '23 14:05 kdavisk6

The first method will post the pct-encoded values verbatim, as you've discovered, because the we do not decode already encoded data. The second will not work as well, since the body will be parsed like an expression.

You may need to look into providing a custom Encoder instance that can generate the empty JSON document instead of using the @Body annotation.

So there is no way to simply provide a non-expression based JSON document using the @Body annotation?

You may need to explicitly set a Content-Type header.

The @Headers annotation is present at the type level:

@Headers({"Accept: application/json", "Content-Type: application/json"})
public interface Api {

The request should be parsed by a server that accepts pct-encoding, which should be every HTTP compliant server out there, so this may be something to look into.

At least the API i'm calling doesn't support this and I can't change that. But isn't this limited to application/x-www-form-urlencoded ?

It would be nice to have an optional parameter for the @Body annotation to force expression parsing. e.g

@Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
void json(@Param("user_name") String user, @Param("password") String password);

@Body("%7B\"user_name\": \"denominator\", \"password\": \"secret\"%7D", forceExpression = true)
void json(@Param("user_name") String user, @Param("password") String password);

otbutz avatar May 24 '23 14:05 otbutz