<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 > get
http://localhost:9998/conneg-resource/123
1 > accept: application/xml
2 < content-type: application/xml
<?xml version="1.0"
encoding="utf-8" standalone="yes"?><book
bookid="123"/>
接下來,測試一個稍微複雜的内容協商。用戶端明确表述格式的品質因素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 > get
http://localhost:9998/conneg-resource/123 1 > accept: application/xml;q=0.1,application/json;q=0.2
2 < 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 > 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。