天天看点

Java RESTful Web Service实战(第2版) 2.6 内容协商

<b>2.6 内容协商</b>

一个资源可以有不同格式的表述,表述(即响应实体)的内容是人类可识别的信息,服务器很难使用一种表述来适应所有用户。conneg(http content negotiation,内容协商)是指在服务器提供的多种表述中,为特定的请求选择最好的一种表述的处理过程。那么什么是最好,又怎样做到最好呢?服务器和客户端/浏览器之间往复通信来协商用于交换数据的内容格式等信息,达成一致即为最好。内容协商定义在rfc2616的第12节(http://www.w3.org/protocols/rfc2616/rfc2616-sec12.html)。

客户端/浏览器通过使用 http accept、accept-charset、accept-language和accept-encoding头来定义接收头的信息,将其所期待的格式或mime类型告知服务器,服务器根据协商算法,返回客户端/浏览器可接受的数据信息。内容协商不只是数据格式协商,还包括语言、编码、字符集等信息。accept用于数据类型协商;accept-language用于语言协商;accept-charset用于字符集协商;accept-encoding用于压缩算法协商。

jax-rs2对内容协商的支持,是通过@produces实现的,其他协商没有从架构上提供支持,可以通过编码从请求头中获取信息并处理。

阅读指南

本节示例源代码地址:https://github.com/feuyeux/jax-rs2-guide-ii/tree/master/2.simple-service-3。

相关包:com.example.conneg。

<b>2.6.1 @produces注解</b>

注解@produces用于定义方法的响应实体的数据类型,可以定义一个或多个,同时可以为每种类型定义质量因素(qualityfactor)。质量因素是取值范围从0到1的小数值。如果不定义质量因素,那么该类型的质量因素默认为1。我们将结合示例深入了解@produces注解对媒体类型的影响,示例代码如下。

@path("conneg-resource")

public class connegresource {

@get

@path("{id}")

  //关注点1:媒体类型为xml

@produces(mediatype.application_xml)

public book getjaxbbook(@pathparam("id") final long bookid) {

return new book(bookid);

  }

  //关注点2:媒体类型为json

@produces(mediatype.application_json)

public book getjsonbook(@pathparam("id") final long bookid) {

}

在这段代码中,getjaxbbook()和getjsonbook()是同等质量因素、资源地址相同的两个get方法,一个定义响应实体格式为xml,一个定义响应实体格式为json,见关注点1和2。那么对同一个资源的访问,jax-rs2该如何选择处理方法呢?如果请求中明确定义可接受的数据类型为两者之一,处理方法应该是定义相应数据类型的方法。如果两者都定义了,处理方法应该是质量因素高的方法。如果两者都定义,而且数据类型的质量因素是相等的或者没有定义accept,xml的方法会被优先选择。

客户端明确表述格式为xml,jersey通过内容协商,会选择getjaxbbook()作为相应的资源方法来处理该请求。其测试代码如下所示。

webtarget path =

target("conneg-resource").path("123");

builder request =

path.request(mediatype.application_xml_type);

book book = request.get(book.class);

1 &gt; get

http://localhost:9998/conneg-resource/123

1 &gt; accept: application/xml

2 &lt; content-type: application/xml

&lt;?xml version="1.0"

encoding="utf-8" standalone="yes"?&gt;&lt;book

bookid="123"/&gt;

接下来,测试一个稍微复杂的内容协商。客户端明确表述格式的质量因素json高于xml,jersey会选择资源方法getjsonbook()来处理请求。示例代码如下所示。

webtarget path = target("conneg-resource").path("123");

builder request = path.request();

request.header("accept",

"application/xml;q=0.1,application/json;q=0.2");

...1 &gt; get

http://localhost:9998/conneg-resource/123 1 &gt; accept: application/xml;q=0.1,application/json;q=0.2

2 &lt; content-type: application/json {"bookid":123}

现在我们清楚了两个同等方法的场景,再来看一个方法中多种数据类型的场景。示例代码如下。

...java

@produces({ "application/json;

qs=.9", "application/xml; qs=.5" })

@path("book/{id}")

public book

getbook(@pathparam("id") final long bookid) {

在这段代码中,getbook()方法定义了xml和json两种表述数据类型,xml的质量因素是0.5(0可以省略),json的是0.9。

因此,可以推断,如果客户端请求中,明确接收的数据类型是两者之一,响应实体使用指定类型。如果没有定义或者两者都定义且json的质量因素大于或者等于xml,则返回json类型。还有一种用例是,两者都定义但json的质量因素小于xml,该如何处理请求方法呢?答案是:内容协商的结果按照客户端的喜好选择响应实体的数据类型,即选择xml格式。

其测试代码如下所示,客户端明确表述格式xml优于json,虽然服务器端定义的资源方法中json的质量因素高,但jersey会根据客户端的喜好,选择了xml格式作为表述的格式返回。

target("conneg-resource").path("book").path("123");

"application/xml;q=0.7,application/json;q=0.2");

http://localhost:9998/conneg-resource/book/123

1 &gt; accept:

application/xml;q=0.7,application/json;q=0.2

<b>2.6.2 @consumes注解</b>

注解@consumes用于定义方法的请求实体的数据类型,和@produces不同的是,@consumes的数据类型的定义只用于jax-rs2匹配请求处理的方法,不做内容协商使用。如果匹配不到,服务器会返回http状态码415(unsupported media type),示例代码如下。

@post

//关注点1:@consumes注解定义了xml和json两种格式

@consumes({mediatype.application_xml,mediatype.application_json})

public book getentity(book book) {

logger.debug(book.getbookname());

return book;

final builder request = target(path).request();

//关注点2

final book result = request.post(

entity.entity(book,

mediatype.application_xml), book.class);

在这段代码中,getentity()方法定义了@consumes媒体类型为xml格式和json格式,见关注点1;那么,在客户端请求中,如果请求实体的数据类型定义是两者之一,该方法会被选择为处理请求的方法,否则查找是否有定义为相应数据类型的方法,如果没有抛出javax.ws.rs.notsupportedexception异常,则使用该方法处理请求,见关注点2。