How to deal with accept-parameters when developing

2019-03-15 17:19发布

问题:

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.

回答1:

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



回答2:

Unless I'm missing something. JAX-RS does support Accept parameters. look at the @Consumes("*/*") annotation. Also, The exception you're getting with media type conflict occurs because you have two GET methods at the same url. annotate the test2() method with @Path("test2"), then send your GET request to url/test2 instead. that should get rid of that error.

EDIT

You can inject the Value of the Accept header using @HeaderParams. Here's a sample of what I did.

@Path("/conneg")
public class ConnNeg {

    @GET
    @Produces("application/vnd.test;version=1")
    public Response test1(@HeaderParam("Accept") String header) {
        System.out.println(header);
        return Response.ok("Version 1", "application/vnd.test").build();
    }
}

passing the request

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

this will print

application/vnd.test;version=2 , application/vnd.test;version=1;q=0.1

You can then handle it manually. Is this what you're looking for?



回答3:

With Jersey framework, the Accept header of the HTTP request declared what is most acceptable. If a resource class is capable of producing more that one MIME media type then the resource method chosen will correspond to the most acceptable media type as declared by the client. In your case, if the accept header is

Accept: application/vnd.mycompany.mytype;version=2

then the method test1() will be invoked.

If it is

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

the latter one will be called.

More than one media type may be declared in the same @Produces declaration, for example:

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

the test(9 method will be called if either of the 2 mediatypes is acceptable. if both are acceptable, the first will be invoked.

Hope it helps!



标签: java rest jax-rs