OWIN 中文文件

風靈使發表於2018-07-08

OWIN:為 dotnet 開放的 web 服務介面

1 概述

本文對用於定義 OWINOWIN 是 .NET web 服務和 web應用程式之間的一個標準介面。OWIN的目標是用於服務與應用程式之間解耦【譯者注:使兩者間沒有強關聯,或者說相互不依賴】,並且成為一種開放規範,從而激勵.NET web 開發工具開源社群。

OWIN 是根據委託型別定義的,這兒沒有被稱作 OWIN.dll或類似的程式集【譯者注:我的理解是這用於強調 OWIN是規範(或協議),而不是具體實現】。在宿主或應用程式中實現 OWIN規範不會使專案引入依賴。

在本文中,C# ActionFunc語法被用於一些委託型別的定義。然而,這個委託型別

可以被 F# native functionsCLR interfacesnamed delegates同等表示【譯者注:由於不瞭解其它技術,所以使用了原文,但其表達的內容為委託型別在其它語言或技術中可以類似的定義,在此僅只是用C# 舉例】,這是經過精心設計的。在實現 OWIN規範時,選擇一種(合適)委託表示使得其為你和你的堆疊工作。

以下關鍵詞"MUST"、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY"和"OPTIONAL” 按照 [RFC2119]{.underline}中的描述來理解。【譯者注:RFC 是計算機相關的各種說明】

2 定義

本文涉及到以下軟體角色:

2.1 服務

HTTP 服務直接與客戶端連線,接著使用 OWIN語義處理請求。服務可能需要適配層(將請求)轉換為 OWIN(識別的)語義。【譯者注:後文中"服務"如果沒有另外說明,都是這個意思】

2.2 Web 框架

OWIN (管道)頂部的自包含(獨立的)元件,它對外暴露物件模型或者是API,從而使得應用程式可以使用它來便利的

處理請求,Web Framework 可能需要適配層將 OWIN語義轉換(為它識別的內容)。

2.3 Web 應用程式

一個具體的 Web 應用程式,可能構建在 Web Framework (Web框架)的頂部,它使用 OWIN 相容的服務來執行。

2.4 中介軟體

在服務(server)和應用程式之間構成的管道中的元件,元件出於某個目的檢查、路由或者修改請求和響應報文

2.5 宿主

應用程式和服務執行的程式,主要負責應用程式【OWIN整體】的啟動(和關閉)。某些服務同時也是宿主【服務同時實現了宿主的功能】。

3 處理請求

一般來說,服務呼叫應用程式(提供環境字典引數,環境字典包含請求和響應的頭和內容);應用程式返回一個響應或指出錯誤。

3.1 應用程式委託

OWIN 中主要的介面稱為應用程式委託或 AppFunc,應用程式委託使用IDictionary<string, object> 環境(字典),並且在處理完成時返回一個Task

//環境字典
using AppFunc = Func<IDictionary<string, object>,Task>; //方法完成時返回的任務【譯者注:Task是一種型別,後文中"任務"一般是該含義】

應用程式必須在最終完成時返回一個任務或丟擲異常。

3.2 環境(字典)

環境字典儲存關於請求、響應和(任何與服務狀態有關)的資訊。服務的職責是在請求和響應最初呼叫時提供它們的(報文)頭集合和(報文)體流。

應用程式接下來使用響應資料填充合適的欄位,然後寫響應體,最後在完成時返回響應。

(1)環境字典必須非空且可更改,而且必須包含下表中列出的鍵列表。

(2)鍵的比較(相等或不等)必須使用 StringComparer.Ordinal

(3)鍵對應的值必須非空,除非另有說明。

除了這些增加的鍵,宿主、服務、中介軟體和應用程式等可以向環境字典中新增任意與請求或響應有關的資料。

增加鍵的準則和常用鍵列表可以在 CommonKeys addendum 文件中找到。

3.2.1 請求資料

Required Key Name Value Description
Yes owin.RequestBody A Stream with the request body, if any. Stream.Null MAY be used as a placeholder if there is no request body. See Request Body.
Yes owin.RequestHeaders An IDictionary<string, string[]> of request headers. See Headers.
Yes owin.RequestMethod A string containing the HTTP request method of the request (e.g., “GET”, “POST”).
Yes owin.RequestPath A string containing the request path. The path MUST be relative to the “root” of the application delegate. See Paths.
Yes owin.RequestPathBase A string containing the portion of the request path corresponding to the “root” of the application delegate; see Paths.
Yes owin.RequestProtocol A string containing the protocol name and version (e.g. “HTTP/1.0” or “HTTP/1.1”).
Yes owin.RequestQueryString A string containing the query string component of the HTTP request URI, without the leading “?” (e.g., “foo=bar&amp;baz=quux”). The value may be an empty string.
Yes owin.RequestScheme A string containing the URI scheme used for the request (e.g., “http”, “https”); see URI Scheme.

3.2.2 響應資料

Required Key Name Value Description
Yes owin.ResponseBody A Stream used to write out the response body, if any. See Response Body.
Yes owin.ResponseHeaders An IDictionary<string, string[]> of response headers. See Headers.
No owin.ResponseStatusCode An optional int containing the HTTP response status code as defined in RFC 2616 section 6.1.1. The default is 200.
No owin.ResponseReasonPhrase An optional string containing the reason phrase associated the given status code. If none is provided then the server SHOULD provide a default as described in RFC 2616 section 6.1.1
No owin.ResponseProtocol An optional string containing the protocol name and version (e.g. “HTTP/1.0” or “HTTP/1.1”). If none is provided then the “owin.RequestProtocol” key’s value is the default.

3.2.3 其它資料

Required Key Name Value Description
Yes owin.CallCancelled A CancellationToken indicating if the request has been canceled/aborted. See Request Lifetime.
Yes owin.Version A string indicating the OWIN version. See Versioning.

【譯者注:以上3小節沒有翻譯,其它 HTTP報文一致,如有不瞭解,請查閱相關內容】

3.3 報文頭

HTTP 請求和響應報文頭都使用型別為 IDictionary<string, string[]>
的物件表示,以下要求在 RFC 2616 section 4.2 中有說明。

(1)字典必須可修改

(2)鍵必須是 HTTP 欄位名稱且沒有冒號或空格,多個單詞間用短橫線(-)連線

(3)鍵的比較(相等或不等)必須使用 StringComparer.OrdinalIgnoreCase

(4)所有鍵值中的字元應該是 ASCII 碼錶中的

(5)返回的值陣列假定是原始資料的拷貝,任何想要修改值陣列的操作必須回溯到頭欄位,手動的使用headers[headerName] = modifiedArray;headers.Remove(header)語法操作。

(6)鍵對應的值都假定是混合的格式,比如 new string[1] {"value, value,value"},new string[3] {"value", "value", "value"}, or
new string[2] {"value, value", "value"}3種格式

(7)服務、應用程式和中間元件不應該拆分或合併非必要的報文頭中的值。雖然上述的3種格式都可以互相轉換,但實際上許多現有的實現只支援某種特定格式,開發人員應當通過選定或假定某種格式來靈活的支援現有的實現格式。

3.4 請求體、100 Continue 和已完成語義

當請求表明有請求體時,服務應當提供使用 owin.RequestBody鍵訪問請求體流。如果期望沒有請求體資料 Stream.Null可以作為佔位符被使用。

當請求 Expect 頭指明客戶端請求 100 Continue,100 Continue
值由服務(決定是否)提供,應用程式禁止設定 owin.ResponseStatusCode
的值為100100 Continue僅只用於中間響應,使用它將會阻止應用程式提供最終響應(比如:200 OK)。在發生應用程式在請求體資料達到前讀取請求體流情況,服務應當代表應用程式傳送100 Continue

(1)應用程式委託在請求完成前不應當結束請求並返回任務,並把請求控制交給服務。一旦 AppFunc完成應用程式不應當繼續從請求流中讀取資料。

(2)應用程式必須通過完成其返回的任務或丟擲異常來發出響應主體完成或失敗的訊號。在任務完成後,應用程式不應當向響應流中寫任何資料。

(3)如果在應用程式委託執行期間服務傳送 owin.CallCancelled
呼叫取消令牌,應用程式不應當嘗試再從請求流讀取資料,而應當迅速的完成應用程式委託任務。

(4)應用程式不應當關閉或釋放給定的(請求)流除非它完成了請求內容的使用。請求流的擁有者(比如服務或中介軟體)必須在應用程式委託任務完成時做必要的清理(工作)。

(5)任何從請求體流中丟擲的異常【】都是致命的,而是應當通過在 AppFunc
中(同步)丟擲(異常)或使用給定的異常致使非同步任務失敗來實現。

3.5 響應體

服務在初始化環境字典是提供 owin.ResponseBody鍵訪問的請求體流。響應頭、狀態、說明片語等在第一次寫入響應體前都是可以修改的。

在第一次寫入時,服務驗證和方式響應頭,應用程式可以選擇緩衝響應資料來延遲響應頭確定。

(1)應用程式必須通過完成其返回的任務或丟擲異常來發出響應主體完成或失敗的訊號。在任務完成後,應用程式不應當向響應流中寫任何資料。

(2)如果在應用程式委託執行期間服務傳送 owin.CallCancelled
呼叫取消令牌,應用程式不應當嘗試再從請求流讀取資料,而應當迅速的完成應用程式委託任務。

(3)應用程式不應當關閉或釋放給定的(請求)流除非它完成了請求內容的使用。請求流的擁有者(比如服務或中介軟體)必須在應用程式委託任務完成時做必要的清理(工作)。應用程式不應當假定給定的流支援多個未完成的非同步寫操作。應用開發者應當在嘗試使用這種方式前驗證服務和所以在使用的中介軟體支援這種模式。

3.6 請求生命週期

請求完整範圍或生命週期受到一些因素的限制,包含客戶端、服務和應用程式委託。在最簡單的場景中,一個請求生命週期結束是在應用程式委託完成和服務正常的結束請求。任何級別的錯誤可能導致請求過早的結束,或者可能在內部處理和允許請求繼續(執行)。

owin.CallCancelled鍵與取消呼叫令牌關聯,當請求(需要)終止時服務使用其作為(終止請求)訊號。如果在AppFunc 任務完成前請求出現錯誤

這個(訊號)應當被觸發。也應當在提供商決定的任何(時間)點觸發。中介軟體可以使用自己的(令牌)來替換它從而提供額外的粒度或功能,但他們應該把新令牌鏈到原始令牌上。

4 應用程式啟動

當宿主程式啟動時,它通過一系列的步驟來設定應用程式。

  1. 宿主建立屬性 IDictionary<string,object>,並且填充屬性中所有啟動資料或功能。

  2. 宿主選擇將被使用的服務,並且提供將屬性集合提供給它,所以宿主可以近似的宣稱擁有任意的功能。

  3. 宿主定位應用程式啟動類,並且提供屬性集合來呼叫啟動程式。

  4. 應用程式讀取或者設定屬性集合中的配置,構造想要的請求處理管道,並且返回應用程式委託結果【返回】。

  5. 宿主使用給定的應用程式委託和屬性集合呼叫服務啟動類,服務自己完成配置,開始接受請求,並且呼叫應用程式委託來處理這些請求。屬性字典被設計為支援宿主、服務、中介軟體或應用程式用於讀取或設定任何配置引數。

(1)啟動屬性字典必須非空、可修改和必須包含下表要求的鍵列表

(2)鍵比較必須使用 StringComparer.Ordinal

(3)鍵對應的值必須非空,除非另有說明

除了這些鍵,宿主、服務、中介軟體和應用程式等可以向屬性字典新增任意與應用程式配置有關的資料。

新增鍵的準則和通用鍵定義列表可以在 CommonKeys addendum 規範中找到。

5.URI 重建

應用程式通常需要重建請求的完整 URI 的能力,這個過程不可能完美,因為 HTTP客戶端通常傳送不完整的 URI 請求,但是 OWIN 為構建近似於(完整) URI意圖提供了選擇。

5.1 URI 方案

HTTP 客戶端通常不會傳送 URI 方案資訊,並且依靠網路配置,OWIN服務也許不能推斷出它的正確值。

在這些情形下,服務也許必須手動的配置或計算出一個值。

(1)服務必須為 owin.RequestScheme 提供一個最佳的猜測值

5.2 主機名稱

HTTP/1.1請求的上下文中,客戶端發出請求的伺服器的名稱通常在請求的主機頭欄位值中表明,儘管它可能使用絕對請求URI 來指定(詳見 2616, sections 5.1.2, 19.6.1.1)。

【譯者注:示例:GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1】

服務必須在請求頭字典中為 Host 鍵提供一個值,值的格式必須是<hostname>[:<port>]

這個值應當被宿主通過以下步驟來獲取:

  1. 如果傳入請求的 URL 是絕對 URL,則 Host 鍵對應的值必須是絕對 URLHost 部分。

  2. 如果傳入請求的 URL 不是絕對 URL,則 Host 鍵對應的值必須採用傳入請求頭Host 欄位的值。

  3. 如果傳入請求的 Host 欄位沒有值,或者它的值是空白,則服務必須為 Host
    鍵對應的值提供合理的最佳猜測值。

5.3 路徑

服務需要具有將應用程式委託對映到一些基礎路徑的能力。比如,服務可能有應用程式委託設定響應以"/my-app“開頭的請求,在這種情形下環境字典中”owin.RequestPathBase“的值應當設定為”/my-app"。如果這個服務收到"/my-app/foo“請求,則環境字典中”owin.RequestPath“的值應當設定為”/foo"。

(1)環境字典中"owin.RequestPathBase"的值禁止使用斜杆結尾,並且必須以斜杆開始或者值是String.Empty

(2)環境字典中"owin.RequestPath“的值必須使用斜杆開始,或者當”owin.RequestPathBase"不是String.Empty 時,它可以是 String.Empty

5.4 URI 重構演算法

下面的演算法可以被用於構建近似完整請求 URI:

var uri =(string)Environment["owin.RequestScheme"] +"://" +Headers["Host"].First() +

(string)Environment["owin.RequestPathBase"] +

(string)Environment["owin.RequestPath"];

if(Environment["owin.RequestQueryString"] != "")

{

uri += "?" + (string)Environment["owin.RequestQueryString"];

}

上面的結果可能與客戶端傳送請求使用的 URI不相同;比如,服務可能進行一些重寫來規範請求。

此外,這個主題在 URI Scheme and Hostname sections 中有附加說明。

5.5 百分比編碼

URI 使用百分比編碼來傳輸被允許使用之外的字元。百分比編碼是被用於 URI元件中的8位編碼,這8位編碼通過

UTF-8編碼產生。大多數的 Web 伺服器會在請求路徑中實現百分比編碼(see: RFC2616 section 5.1.2, also 3.2.3),並且 OWIN 遵從這個預設規則。在 OWIN中請求查詢字串以百分比編碼形式呈現。一個百分比編碼查詢字串可以包含’?‘或’='字元,這將導致查詢字串不可理解。

(1)服務必須為"owin.RequestPath" 和 "owin.RequestPathBase"的值提供百分比編碼後的值

(2)服務必須為 "owin.RequestQueryString" 的值提供百分比編碼後的值

6 錯誤處理

儘管這兒有些標準的異常(比如: ArgumentExceptionIOException),可能在正常的請求處理場景中可以預見發生。

但僅只處理這些異常對於建立健壯的服務或應用程式是不夠的。如果服務希望健壯,它應該一致的處理所有型別的異常,這些異常由應用程式委託或報文體委託丟擲或返回。處理機制(比如寫日誌、崩潰、重啟和內部處理等)取決於服務和宿主程式。

6.1 應用程式錯誤

應用程式可能在以下位置產生異常:

(1)應用程式委託執行中丟擲的異常

(2)由應用程式委託的返回任務提供

應用程式應當嘗試處理內部異常,產生一個合適的響應(可能是 500級別)而不是把異常傳遞給服務。

在應用程式提供響應後,服務應當在向底層協議寫響應頭前至少接收到一個向響應(體)流的寫。提供這種方式,如果服務獲取到應用程式委託任務的異常替代(原本)流的寫入。服務仍能產生一個500 級別的響應。

如果服務得到第一個向流的寫操作,它可以安全的假定應用程式在內部就可能的處理了很多的異常,服務可以開始傳送響應。

如果後續接收到異常,服務將視情況處理它。

6.2 服務錯誤

如果服務在請求生命週期中發生錯誤,它應該向 owin.CallCancelled提供取消呼叫令牌。

服務可以任何必要行為來終止請求,但是它應該包容應用程式委託完成的延遲。

相關文章