Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jersey: @Consumes doesn't quite work when content-type is not set

I'm trying to figure out how @Consumes works here.

I have a simplified resource that looks like the below, and I only want this resource to consume "application/vnd.myApp+xml".

@Path("/app")
@Consumes("application/vnd.myApp+xml")
@Produces("application/vnd.myApp+xml")
public class AppResource {
    @POST
    public Response postStuff() {
        ...
    }
}

I have the following testcases:-

public class AppResourceTest extends JerseyTest {
    @Test
    public void testApp() {
        // #1: Works fine
        ClientResponse response = resource().path("app")
                    .accept("application/vnd.myApp+xml")
                    .post(ClientResponse.class);

        ...

        // #2: Throws a 415 Unsupported Media Type
        ClientResponse response = resource().path("app")
                    .accept("application/vnd.myApp+xml")
                    .type("text/plain")
                    .post(ClientResponse.class);

        ...

        // #3: Works fine
        ClientResponse response = resource().path("app")
                    .accept("application/vnd.myApp+xml")
                    .type("application/vnd.myApp+xml")
                    .post(ClientResponse.class);

        ...
    }
}

From the 3 tests above, #2 and #3 work as expected.

As for #1, if I don't set the content-type, why doesn't it throw a 415 too?

like image 559
limc Avatar asked Sep 26 '12 16:09

limc


3 Answers

Based on @Consumes api (http://jsr311.java.net/nonav/releases/1.0/javax/ws/rs/Consumes.html) and the HTTP type spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1) coupled with the behavior you are seeing I think it is safe to conclude the following Jersey implementation:

If the Content-Type is NOT set by the client Jersey does NOT default but allows it to pass through any/all @Consumes annotions.

When multiple @Consumes {different types} are set for the URI and the client has NOT set the Content-Type then Jersey will default to the first @Consumes annotation or first type in a list of acceptable types.

When the Accepts header value is set Jersey will find the best fitting method to execute. If multiple methods are a best fit it will default to the first defined.

In conclusion the @Consumes only acts as a filter if and ONLY if the client sets the Content-Type otherwise Jersey will attempt to find the best fitting match. This does match the HTTP spec:

Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body. If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource. If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream".

If the goal is to have the @Consumes to act as a white list then a servlet filter could be used to default the Content-Type on requests where none is set.

like image 148
jthiesse Avatar answered Oct 23 '22 14:10

jthiesse


You should specify the type- for example:

ClientResponse res =
    service.path("accounts")
        .type("application/vnd.dsu.account+json")
        .post(ClientResponse.class,ent);
like image 28
Sergei Avatar answered Oct 23 '22 12:10

Sergei


Based on the docs it seems using @Consumes at the class level does not explicitly override method level definitions (which default to */*), so it stands to reason it may be working in an additive manner...

Have you tried applying the same @Consumes on the method definition?

like image 2
Alex Avatar answered Oct 23 '22 14:10

Alex