天天看點

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。