天天看點

Content Negotation在Nancy的實作和使用

什麼是Content Negotation呢?翻譯成中文的話就是"内容協商"。當然,如果不清楚HTTP規範(RFC 2616)的話,可以對這個翻譯也是一頭霧水。

先來看看RFC 2616對其的定義是

The process of selecting the best representation for a given response when there are multiple representations available.
Content Negotation在Nancy的實作和使用

這句話是什麼意思呢?可以簡單的了解為:當存在多個不同的表現形式時,對給定的響應選擇一個最好的表現形式的過程

其涉及到相關的請求封包頭部有下面幾個:

Accept:響應可接收的Media Type,如"application/json"等

Accept-Charset:可接收的字元集,如"UTF-8"等

Accept-Encoding:可接收的内容編碼,如"gzip"等

Accept-Language:優先選用的自然語言,如"en-us等

本文主要用到的是Accept這個請求封包頭!

注:RFC 2616在2014年被拆分成了6個單獨的協定!具體可以參見下面的兩個連結:

http://www.w3.org/Protocols/rfc2616/rfc2616.html

https://tools.ietf.org/html/rfc2616

可能看了前面一節的背景介紹,大家可能會一臉懵逼,似乎跟本文的主題并不沾邊,但是多了解一點相關的知識也是必不可少的

這樣我們也可以知道Content Negotation存在的意義。才能對它的使用有一個更精确的定位。

我們都知道Nancy是基于Http協定開發的一個輕量級的Web架構,是以它的内部必然會涉及到Content Negotation的實作。

其實在Nancy的實作和在Web Api的實作可以說是大同小異,如果大家看過這兩者這一塊的實作,應該也會有同樣的感覺。

下面主要介紹Nancy 2.0.0-clinteastwood(基于dotNet Core)。

對于一個web應用程式而言,它的起點一定是路由,Nancy自然也是不會例外。先來看起點!起點是位于Nancy.Route這個命名空間下面的DefaultRouteInvoker。

裡面有一個Invoke的方法是每個路由都會執行的!

除去在執行路由的Invoke方法抛出異常的情況,其他的都是會走NegotiateResponse這個方法!!

進而也就到了本文要講的重點了。既然每個正常的請求都能要經過它的洗禮,有什麼理由不簡單的了解一下呢?

一切的開始都是源于IResponseNegotiator這個接口,這個接口也十分的簡單,就一個方法的定義。

正如我們所知,幾乎每一個子產品,Nancy内部都會有一個預設的實作,正常情況下,都是以Default開頭的方法,關于内容協商這一塊的自然也會有其對應的預設實作。

這個預設實作位于Nancy.Responses.Negotiation這個命名空間下面!

從上面接口的方法簽名可以看出,處理請求時,都需要傳遞目前路由處理的結果和目前的上下文。

這個上下文,其實在整個Nancy架構中占據着舉足輕重的地位,與之類似的有HttpContext等。

目前路由處理的結果可謂是多種多樣,隻要是正常執行了一個請求裡面的return,這個return的内容就是路由的處理結果。

下面通過幾個簡單的例子介紹一下這些處理結果。

這幾個例子中,我們比較常用到的應該是Response.AsJson、View和Respnse.AsRedirect這3個。

NegotiateResponse方法的第一個參數routeResult不單單包含上面提到的正常的響應資訊,

還有一些錯誤類的資訊,如404、500等,這個時候routeResult就會是一個Nancy.ErrorHandling.DefaultStatusCodeHandler.DefaultStatusCodeHandlerResult對象了。

這個對象承載了我們的各種錯誤類的響應。

下面來看看具體做了什麼内容!

在這個方法中執行的第一步就是先判斷我們在Module中傳回的結果是不是一個Response對象,

如果是一個Response對象就直接将這個對象傳回了。具體的片段代碼如下:

這個時候可能就會有這樣的一個疑問,什麼樣的傳回結果是一個Response對象,什麼樣的傳回結果不是呢?

Response.AsXXX 這一類的傳回結果就屬于一個Response對象,這些以As開頭的都是一些傳回Response對象的擴充方法。

Negotiator對象 這一類的傳回結果就不是Response對象,是以這一類傳回結果是還要繼續下面的層層審判!

到這裡已經過濾掉了一部分"不屬于"Content Negotation處理的請求了!需要注意的是View是屬于Negotiator對象這一類的!

第二步是拿到NegotiationContext這個上下文

第三步就是處理Accept這個請求頭的内容了

開始這一步的内容之前要先來簡單了解一下Accept:

Accept首部字段可以通知伺服器,使用者代理能夠處理的媒體類型及媒體類型的相對優先級。具體的使用形式為:type/subtype,當然也可以一次指定多種媒體類型。

如果想給顯示的媒體類型添加優先級,那麼就要使用q因子來額外表示該媒體類型的優先級(權重值),具體使用形式為:type/subtype;q=0.8。

這個權重的取值範圍是0~1(可精确到小數點後三位),最大值為1,并且當沒有指定權重的時候,預設的權重就是為1。

是以,當伺服器提供了多種不同的内容時,就會先傳回權重最高的那個媒體類型。

下面拿一個具體的例子來看一下:

當我們通路部落格園時,浏覽器的Accept頭為text/html, application/xhtml+xml, image/jxr, */*

這就表明浏覽器想告訴伺服器,“我支援這些媒體類型,你最好傳回這些Media Type的資料給我。”

Content Negotation在Nancy的實作和使用

當伺服器處理好了之後,就會在響應頭中的Content-Type表現出要展示什麼的内容。

OK,了解完畢,下面來看看Nancy是怎麼處理的。

要處理Accept,肯定會有一個定義,從我們上面的了解中,也知道這肯定會是一個集合,每個集合的項包含兩個内容:Media Type和權重值。下面來驗證一下

Nancy把集合的項定義成了元組(省去定義一個類那麼麻煩),元組的第一個元素就是Media Type,第二個就是這個Media Type對應的權重值。

需要注意的是在get的時候,根據權重值對Media Type做了一個降序,後面的處理就直接是按照權重高的優先處理

請求的相關資訊都是會記錄在Nancy上下文的Request屬性中,是以想要處理Accept,NancyContext肯定是必不可少的。

這一步主要的處理是把Nancy上下文中的Accept資訊強制轉化成一個友善後續處理的集合對象。

前面的這三步可以說是鋪墊,後面的處理才是重頭戲。

第四步,擷取合适的Media Type

Nancy是如何來處理這一塊的呢

首先是取到合法的Media Type:

目前negotiationContext的PermissableMediaRanges屬性如果包含 */*這個Media Type,就直接把權重大于0的Media Type傳回

這裡也可以間接說傳回的是Accept的所有内容,應該不會有人那麼無聊弄個負數或者其他吧?

大部分情況下,權重大于0的就是合法的媒體類型。

拿到合法的媒體類型之後,還要根據媒體類型去拿到對應的内容。如:application/json ,傳回一個序列化的Person對象,這個Person對象就是對應的内容。

還要對媒體類型處理,最後傳回一個CompatibleHeader集合。

第五步,判斷是否有合适的媒體類型,如果沒有就直接傳回406。

從這一步也得知,當用戶端向伺服器請求一種伺服器無法處理的媒體類型時,就會傳回406(Not Acceptable)!

第六步,建立目前請求的Response對象

在Nancy中,請求的最後都是以Response對象的形式呈現在我們面前,是以在建立好一個Response之前 ,Negotiate是屬于不完善的!

下面看看是如何建立Response對象的:

首先是用NegotiateResponse方法建立了一個Response對象

在NegotiateResponse方法中,通過周遊前面得到的媒體類型集合。

根據每一個媒體類型去拿到對應的一個優先級清單

最後在優先級清單中根據 MediaRange , mediaRangeModel , NancyContext 這三個來判斷能否生成一個Respone對象

如果能生成就傳回上面建立好的這個Response對象,不能就隻好傳回null了。

由于這裡的Response對象還是有可能為空,是以當其為空的時候,還是應該要向上面那樣處理成406

後面就是處理一些響應頭部的資訊并最終傳回這個Response。

下面是完整的NegotiateResponse方法:

上面大緻履了一下相應的實作

對于它的大緻實作,有了一定的了解,下面來看看具體是要怎麼用

平時我們如果用Negotiate的話,基本都是用的Negotiator的擴充方法,輸入Negotiator後,可以看到一堆擴充方法,這堆擴充方法就是我們經常用到的。

Content Negotation在Nancy的實作和使用

我們先嘗試用Negotiate處理一個MIME Type為application/json的請求!

下面是具體的示例代碼:

定義了一個匿名對象,并通過WithMdeiaRangeModel這個擴充方法來處理MIME Type和這個匿名對象。

此時,我們希望能夠得到結果是對匿名對象進行json序列化後結果,和Response.AsJson得到的應該是基本一緻的。

當然,這個時候我們在浏覽器打開這個URL時,結果并不是我們所期望的那樣!

Content Negotation在Nancy的實作和使用

不管三七二十一,來看看這個擴充方法做了一些什麼操作!

幡然醒悟,它是往我們的目前NegotiationContext的PermissableMediaRanges添加了application/json這個媒體類型!

并且此時PermissableMediaRanges集合就包含了兩個對象:一個是*/*,一個是appliaction/json

Content Negotation在Nancy的實作和使用

我們用了一種錯誤的方式來請求這個URL!!因為我們是直接用浏覽器打開的,而這個時候預設的Accept頭是

它并不包含我們所接收請求的application/json,是以它在生成Response對象的時候會抛出異常,然後就看到那個500的錯誤頁面了。

這個時候我們應該借助工具來探讨,可以使用Fiddler、Postman和Charles等工具。

這裡我用的是Fiddler,當我們在Composer中添加Accept請求頭後,就能正常傳回我們想要的結果了。

Content Negotation在Nancy的實作和使用

不知道大家是否有留意到這樣子傳回和用Response.AsJson這樣傳回有什麼差別?

當然,這種寫法是隻能處理application/json的請求,并不能處理其他MIME Type的請求!

下面我們繼續改進一下,讓這個請求可以同時接收處理application/xml和text/html這兩種MIME Type

下面來看看Accept為application/xml的試試:

Content Negotation在Nancy的實作和使用

可以看到,它并沒有傳回我們想要的結果,但是請求卻是成功的!這裡的問題出在我們定義的那個匿名對象!

這裡預設處理的序列化XML的方法是不支援匿名對象的,具體可以參考Nancy對XML處理的方法。

修改匿名對象為實體對象後,它就能把資料正常傳回給我們了。

Content Negotation在Nancy的實作和使用

可以看到,我們剛才的改造已經能夠同時支援json和xml了!對于前面提到的匿名類的問題,如果有需要可以實作一個支援匿名類的序列化方法以達到對匿名類的适配。

前面我們直接在浏覽器打開這個URL時,提示我們500錯誤,現在改進後再來看看能否傳回一個正常頁面給浏覽器!

這個時候我們并沒有編寫對應的視圖,是以得到的必然還是500錯誤(ViewNotFound)。下面就要處理這個錯誤。

我們在根目錄添加一個person.html檔案,并設定它的Copy to Output Directory屬性為Copy always

我們的頁面比較簡單,就把剛才實體對象的内容展示一下。此時再運作就可以發現頁面已經能正常顯示了!

Content Negotation在Nancy的實作和使用

其實還有一種比較直接的方法也是用Content Negotation實作的!

不知道大家是否記得在建立一個WEB API項目後,生成的valuecontroller,裡面的方法都是直接傳回一個數組或字元串。

在Nancy中也可以直接傳回這樣的一個對象!!來看看下面的這個例子:

上面的示例代碼中也給出了一種等價的寫法,最終它是給NegotiationContext的DefaultModel屬性指派為這個對象。

這樣直接傳回一個對象的寫法,似乎就沒有那麼靈活,我想到的一個用來形容的詞就是"任人宰割"

而用Negotiate就可以适當的加上一些控制,畢竟有那麼多的擴充方法可以用。如果覺得不夠用,那就自己加擴充,加到自己滿意為止。

好比說,現在某個api隻對MIME類型為application/json和appliaction/xml的請求進行處理,其他的一概不理。

這個時候,正常有效的做法就是直接用WithMediaRangeModel這個擴充方法

這樣的寫法并沒有什麼問題,但是并不那麼簡潔,這個時候我們就可以通過寫擴充來讓它變得簡潔一些。

使用的時候:

這樣是不是很友善和簡潔呢?

内容協商的作用可大可小,如果能多加利用,或許能成為一把利刃。

本文簡單的分析了一下内容協商在Nancy中是如何實作的,以及我們平時的開發中是如何使用的。

當然其中有許多相關的細節在文中也沒有特别展現出來,如果園友們覺得與這一塊密切相關且有必要說明的

可以在評論中指出,也可以私信給我,便于我在後期增加上去。

同樣用一張思維導圖概括本文:

Content Negotation在Nancy的實作和使用

本文轉自xsster51CTO部落格,原文連結:http://blog.51cto.com/12945177/1929797 ,如需轉載請自行聯系原作者

下一篇: docker入門