Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with accept-parameters when developing a jax-rs application

Tags:

java

rest

jax-rs

In order to deal with different versions of a content-type i am trying to use the accept-parameters of the "Accept*" headers (RFC 2616).

Accept: application/vnd.mycompany.mytype;version=2 , application/vnd.mycompany.mytype;version=1;q=0.1

The problem is that Jax-RS annotations do not support Accept-parameters...

@GET
@Produces("application/vnd.test;version=1")
public Response test1() {
    return Response.ok("Version 1", "application/vnd.test").build();
}

@GET
@Produces("application/vnd.test;version=2")
public Response test2() {
    return Response.ok("Version 2", "application/vnd.test").build();
}

Results in a media type conflict exception:

Producing media type conflict. The resource methods public javax.ws.rs.core.Response test.resources.TestResource.test2() and public javax.ws.rs.core.Response test.resources.TestResource.test1() can produce the same media type

Maybe, this exception only related to my JAX-RS framework (Jersey), but i'm afraid this is due to the JSR311 which is not explicit about accept-parameters.

By now, i'm using content-types which holds the version within their names, but i found this solution pretty uggly.

@GET
@Produces("application/vnd.test-v1")
public Response test() {
    return Response.ok("Version 1", "application/vnd.test-v1").build();
}

Do you have any ideas about how to deal with accept-parameters ?

EDIT

I think i wasn't clear enough. I want to automatically route the request to specific methods. These methods are versioned and correspond to a specific version of the returned content-type. JAX-RS current implementation prevents me to use accept-parameters to route the request (to the corresponding method).

greenkode suggest that i manage the version accept-parameter within a dispatching method (using @HeaderParam("Accept")). This solution would end-up in re-writing the content negociation logic which is embeded in the framework (and described in JSR 311).

What can i do to use both accept-parameter and content-negociation logic from JAX-RS ?

Maybe a solution is to use another framework (I only worked with Jersey by Now). But i don't know which one.

like image 936
Arno Avatar asked Apr 14 '13 10:04

Arno


1 Answers

The JAX-RS specification does not explicitly state anything about ignoring Accept header parameters. But the only parameter for which handling is definitely defined is quality (q). This is a possible area for improvement as it seems to have lead to ambiguity (or outright bugginess) in the Jersey implementation. The current version of Jersey (1.17) does not take Accept header parameters into consideration when matching incoming requests to resource methods, which is why you are getting the error:

SEVERE: Producing media type conflict. The resource methods ...

For the resource:

@GET
@Produces("application/vnd.test;version=1")
public Response test1() {
    return Response.ok("Version 1", "application/vnd.test").build();
}

@GET
@Produces("application/vnd.test;version=2")
public Response test2() {
    return Response.ok("Version 2", "application/vnd.test").build();
}

It would appear that Jersey performs a 'uniqueness' check based on the Accept header 'type/subtype', totally omitting any parameters. This can be confirmed by testing with various pairs of headers on the 'matching' resource methods:

Resource 1             Resource 2
----------------------------------------
text/html;q=0.4       text/html;q=0.8
text/html             text/html;q=0.2
text/html             text/html;qs=1.4
text/html;qs=1.4      text/html;qs=1.8
text/html;level=1     text/html;level=2
text/html;foo=bleh    text/html;bar=23

All fail with the same error. If the assumption were made that only the quality parameter is ever sent, then it would make sense to only match on 'type/subtype', because this kind of request is nonsensical:

Accept: text/html;q=0.8, text/html;q=0.4, text/html

Aka, quality parameters only make sense when you are dealing with a mix of possible content types. However, this sort of limited matching fails when non-quality parameters or additional parameters are being sent:

Accept: text/html;version=4.0;q=0.8, text/html;version=3.2;q=0.4

So what are the possible solutions?

  • Intercept the high level request based off 'type/subtype', then route to more appropriate method (you've indicated you do not want to do this)
  • Modify your expected headers. For example 'application/vnd.mycompany.mytype+v2' and 'application/vnd.mycompany.mytype+v1'. No other changes would be required and you could keep on using Jersey
  • Switch frameworks. RESTEasy happens to handle your scenario with ease.

With RESTEasy, and resource:

@Path("/content/version")
public class ContentVersionResource {

    @GET
    @Produces("application/vnd.test;version=1")
    public Response test1() {
        return Response.ok("Version 1", "application/vnd.test").build();
    }

    @GET
    @Produces("application/vnd.test;version=2")
    public Response test2() {
        return Response.ok("Version 2", "application/vnd.test").build();
    }
}

A successful match is made with the following Accept header:

Accept: application/vnd.test;version=1;q=0.3, application/vnd.test;version=2;q=0.5
Response: Version 2

And this:

Accept: application/vnd.test;version=1;q=0.5, application/vnd.test;version=2;q=0.3
Response: Version 1

You can download and test with this sample project. Git, Maven and JBoss 7.x required

like image 106
Perception Avatar answered Sep 27 '22 18:09

Perception