11

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.

4

3 回答 3

16

JAX-RS 规范没有明确声明任何关于忽略 Accept 标头参数的内容。但唯一明确定义处理的参数是质量(q)。这是一个可能需要改进的领域,因为它似乎导致了 Jersey 实现中的模棱两可(或彻底的错误)。当前版本的 Jersey (1.17) 在将传入请求与资源方法匹配时未考虑 Accept 标头参数,这就是您收到错误的原因:

严重:产生媒体类型冲突。资源方法...

对于资源:

@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();
}

Jersey 似乎根据 Accept 标头“类型/子类型”执行“唯一性”检查,完全省略了任何参数。这可以通过在“匹配”资源方法上使用各种标题对进行测试来确认:

资源 1 资源 2
--------------------------------------
文本/html;q=0.4 文本/html;q=0.8
文本/html 文本/html;q=0.2
文本/html 文本/html;qs=1.4
文本/html;qs=1.4 文本/html;qs=1.8
文本/html;级别=1 文本/html;级别=2
文本/html;foo=bleh 文本/html;bar=23

所有都失败并出现相同的错误。如果假设只发送了质量参数,那么只匹配“类型/子类型”是有意义的,因为这种请求是无意义的:

接受:text/html;q=0.8, text/html;q=0.4, text/html

Aka,质量参数只有在您处理可能的内容类型时才有意义。但是,当发送非质量参数或附加参数时,这种有限匹配会失败:

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

那么有哪些可能的解决方案呢?

  • 拦截基于“类型/子类型”的高级请求,然后路由到更合适的方法(您已表明您不想这样做)
  • 修改您预期的标题。例如“application/vnd.mycompany.mytype+v2”和“application/vnd.mycompany.mytype+v1”。无需其他更改,您可以继续使用 Jersey
  • 切换框架。RESTEasy恰好可以轻松处理您的场景。

使用 RESTEasy 和资源:

@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();
    }
}

使用以下 Accept 标头进行成功匹配:

接受:application/vnd.test;version=1;q=0.3, application/vnd.test;version=2;q=0.5
响应:版本 2

和这个:

接受:application/vnd.test;version=1;q=0.5, application/vnd.test;version=2;q=0.3
响应:版本 1

您可以下载并使用此示例项目进行测试。需要 Git、Maven 和 JBoss 7.x

于 2013-04-17T02:57:06.983 回答
2

除非我错过了什么。JAX-RS 确实支持 Accept 参数。看@Consumes("*/*")注释。此外,发生媒体类型冲突的异常是因为您GET在同一个 url 有两个方法。用 注释 test2() 方法@Path("test2"),然后将您的GET请求发送到 url/test2 。那应该摆脱那个错误。

编辑

您可以使用注入Accept标头的值@HeaderParams。这是我所做的一个示例。

@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();
    }
}

传递请求

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

这将打印

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

然后,您可以手动处理它。这是你要找的吗?

于 2013-04-16T11:26:42.440 回答
1

使用 Jersey 框架,HTTP 请求的 Accept 标头声明什么是最可接受的。如果资源类能够生成一种以上的 MIME 媒体类型,则选择的资源方法将对应于客户端声明的最可接受的媒体类型。在您的情况下,如果接受标头是

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

然后方法 test1() 将被调用。

如果是

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

后一个将被调用。

可以在同一个 @Produces 声明中声明多个媒体类型,例如:

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

如果两种媒体类型中的任何一种都可接受,则将调用 test(9 方法。如果两者都可接受,则将调用第一种。

希望能帮助到你!

于 2013-04-16T11:55:14.650 回答