ASP.NET Core靜態檔案中介軟體[2]: 條件請求以提升效能
通過呼叫IApplicationBuilder介面的UseStaticFiles擴充套件方法註冊的StaticFileMiddleware中介軟體旨在處理針對檔案的請求。對於StaticFileMiddleware中介軟體處理請求的邏輯,大部分讀者都應該想得到:根據請求的地址找到目標檔案的路徑,然後利用註冊的IContentTypeProvider物件解析出與檔案內容相匹配的媒體型別,後者將其作為響應報頭Content-Type的值。StaticFileMiddleware中介軟體最終利用IFileProvider物件讀取檔案的內容,並將其作為響應報文的主體。
實際上,這個中介軟體在處理請求時所做的事情比前面的演示例項多,比如針對條件請求(Conditional Request)和區間請求(Range Request)的處理就沒有體現在上面演示的例項中。條件請求就是客戶端在傳送GET請求獲取某種資源時,會利用請求報頭攜帶一些條件。服務端處理器在接收到這樣的請求之後,會提取這些條件並驗證目標資源當前的狀態是否滿足客戶端指定的條件。只有在這些條件滿足的情況下,目標資源的內容才會真正響應給客戶端。[更多關於ASP.NET Core的文章請點這裡]
目錄
一、HTTP條件請求
二、預設響應
三、If-Modified-Since & If-None-Match
四、If-Unmodified-Since & If-Match
一、HTTP條件請求
HTTP條件請求作為一項標準記錄在HTTP規範中。一般來說,一個GET請求在目標資源存在的情況下會返回一個狀態碼為“200 OK”的響應,目標資源的內容將直接存放在響應報文的主體部分。如果資源的內容不會輕易改變,那麼我們希望客戶端(如瀏覽器)在本地快取獲取的資源。對於針對同一資源的後續請求來說,如果資源內容不曾改變,那麼資源內容就無須再次作為網路荷載予以響應。這就是條件請求需要解決的一個典型場景。
確定資源是否發生變化可以採用兩種策略。第一種就是讓資源的提供者記錄最後一次更新資源的時間,資源的荷載內容(Payload)和這個時間戳將一併作為響應提供給作為請求傳送者的客戶端。客戶端在快取資源內容時也會儲存這個時間戳。等到下次需要針對同一資源傳送請求時,它會將這個時間戳一併傳送出去,此時服務端就可以根據這個時間戳判斷目標資源在上次響應之後是否被修改過,然後做出針對性的響應。第二種是針對資源的內容生成一個“標籤”,標籤的一致性體現了資源內容的一致性,在HTTP規範中將這個標籤稱為ETag(Entity Tag)。
下面從HTTP請求和響應報文的層面對條件請求進行詳細介紹。對於HTTP請求來說,快取資源攜帶的最後修改時間戳和ETag分別儲存在名為If-Modified-Since與If-None-Match的報頭中。報頭名稱體現的含義如下:只有目標資源在指定的時間之後被修改(If-Modified-Since)或者目前資源的狀態與提供的ETag不匹配(If-None-Match)的情況下才會返回資源的荷載內容。
當服務端接收到針對某個資源的GET請求時,如果請求不具有上述這兩個報頭或者根據這兩個報頭攜帶的資訊判斷資源已經發生改變,那麼它返回一個狀態碼為“200 OK”的響應。除了將資源內容作為響應主體,如果能夠獲取到該資源最後一次修改的時間(一般精確到秒),那麼格式化的時間戳還會通過一個名為Last-Modified的響應報頭提供給客戶端。針對資源自身內容生成的標籤,則會以ETag響應報頭的形式提供給客戶端。反之,如果做出相反的判斷,服務端就會返回一個狀態碼為“304 Not Modified”的響應,這個響應不包含主體內容。一般來說,這樣的響應也會攜帶Last-Modified報頭和ETag報頭。
與條件請求相關的請求報頭還有If-Unmodified-Since和If-Match,它們具有與If-Modified-Since和If-None-Match完全相反的語義,分別表示如果目標資源在指定時間之後沒有被修改(If-Unmodified-Since)或者目標資源目前的ETag與提供的ETag匹配的請求才會返回資源的內容荷載。針對這樣的請求,如果根據攜帶的這兩個報頭判斷出目標資源並不曾發生變化,服務端才會返回一個將資源荷載作為主體內容的“200 OK”響應,這樣的響應也會攜帶Last-Modified報頭和ETag報頭。如果做出了相反的判斷,服務端就會返回一個狀態碼為“412 Precondition Failed”的響應,表示資源目前的狀態不滿足請求設定的前置條件。下表列舉了條件請求的響應狀態碼。
請 求 報 頭 | 語 義 | 滿 足 條 件 | 不滿足條件 |
If-Modified-Since | 目標內容在指定時間戳之後是否有更新 | 200 OK | 304 Not Modified |
If-None-Match | 目標內容的標籤是否與指定的不一致 | 200 OK | 304 Not Modified |
If-Unmodified-Since | 目標內容是否在指定時間戳之後沒有更新 | 200 OK | 412 Precondition Failed |
If-Match | 目標內容的標籤是否與指定的一致 | 200 OK | 412 Precondition Failed |
二、預設響應
下面通過例項演示的形式介紹StaticFileMiddleware中介軟體在針對條件請求方面做了什麼。假設我們在ASP.NET Core應用中釋出了一個文字檔案(foobar.txt),內容為“abcdefghijklmnopqrstuvwxyz0123456789”(26個字母+10個數字),目標地址為“http://localhost:5000/foobar.txt”。然後直接針對這個地址傳送一個普通的GET請求會得到什麼樣的響應?
HTTP/1.1 200 OK Date: Wed, 18 Sep 2019 23:20:40 GMT Content-Type: text/plain Server: Kestrel Content-Length: 39 Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT Accept-Ranges: bytes ETag: "1d56e76ed13ed27" abcdefghijklmnopqrstuvwxyz0123456789
從上面給出的請求與響應報文的內容可以看出,對於一個針對物理檔案的GET請求,如果目標檔案存在,伺服器就會返回一個狀態碼為“200 OK”的響應。除了承載檔案內容的主體,響應報文還有兩個額外的報頭,分別是表示目標檔案最後修改時間的Last-Modified報頭和作為檔案內容標籤的ETag報頭。
三、If-Modified-Since & If-None-Match
現在客戶端不但獲得了目標檔案的內容,還得到了該檔案最後被修改的時間戳和標籤,如果它只想確定這個檔案是否被更新,並且在更新之後返回新的內容,那麼它可以針對這個檔案所在的地址再次傳送一個GET請求,並將這個時間戳和標籤通過相應的請求報頭髮送給服務端。我們知道這兩個報頭的名稱分別是If-Modified-Since和If-None-Match。由於我們沒有修改檔案的內容,所以伺服器返回如下一個狀態碼為“304 Not Modified”的響應。這個不包括主體內容的響應報文同樣具有相同的Last-Modified報頭和ETag報頭。
GET http://localhost:50000/foobar.txt HTTP/1.1 Host: localhost:50000 If-Modified-Since: Wed, 18 Sep 2019 23:15:14 GMT If-None-Match: "1d56e76ed13ed27" HTTP/1.1 304 Not Modified Date: Wed, 18 Sep 2019 23:21:54 GMT Content-Type: text/plain Server: Kestrel Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT Accept-Ranges: bytes ETag: "1d56e76ed13ed27"
如果將If-None-Match報頭修改成一個較早的時間戳,或者改變了If-None-Match報頭的標籤,服務端都將做出檔案已經被修改的判斷。在這種情況下,最初狀態碼為“200 OK”的響應會再次被返回,具體的請求和對應的響應體現在如下所示的程式碼片段中。
GET http://localhost:5000/foobar.txt HTTP/1.1 If-Modified-Since: Wed, 18 Sep 2019 01:01:01 GMT Host: localhost:5000 HTTP/1.1 200 OK Date: Wed, 18 Sep 2019 23:24:16 GMT Content-Type: text/plain Server: Kestrel Content-Length: 39 Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT Accept-Ranges: bytes ETag: "1d56e76ed13ed27" abcdefghijklmnopqrstuvwxyz0123456789
GET http://localhost:50000/foobar.txt HTTP/1.1 Host: localhost:50000 If-None-Match: "abc123xyz456" HTTP/1.1 200 OK Date: Wed, 18 Sep 2019 23:26:03 GMT Content-Type: text/plain Server: Kestrel Content-Length: 39 Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT Accept-Ranges: bytes ETag: "1d56e76ed13ed27" abcdefghijklmnopqrstuvwxyz0123456789
四、If-Unmodified-Since & If-Match
如果客戶端想確定目標檔案是否被修改,但是希望在未被修改的情況下才返回目標檔案的內容,這樣的請求就需要使用If-Unmodified-Since報頭和If-Match報頭來承載基準時間戳與標籤。例如,對於如下兩個請求攜帶的If-Unmodified-Since報頭和If-Match報頭,服務端都將做出檔案尚未被修改的判斷,所以檔案的內容通過一個狀態碼為“200 OK”的響應返回。
GET http://localhost:5000/foobar.txt HTTP/1.1 If-Unmodified-Since: Wed, 18 Sep 2019 23:59:59 GMT Host: localhost:5000 HTTP/1.1 200 OK Date: Wed, 18 Sep 2019 23:27:57 GMT Content-Type: text/plain Server: Kestrel Content-Length: 39 Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT Accept-Ranges: bytes ETag: "1d56e76ed13ed27" abcdefghijklmnopqrstuvwxyz0123456789
GET http://localhost:50000/foobar.txt HTTP/1.1 Host: localhost:50000 If-Match: "1d56e76ed13ed27" HTTP/1.1 200 OK Date: Wed, 18 Sep 2019 23:30:35 GMT Content-Type: text/plain Server: Kestrel Content-Length: 39 Last-Modified: Wed, 18 Sep 2019 23:15:14 GMT Accept-Ranges: bytes ETag: "1d56e76ed13ed27" abcdefghijklmnopqrstuvwxyz0123456789
如果目標檔案當前的狀態無法滿足If-Unmodified-Since報頭或者If-Match報頭體現的條件,那麼返回的將是一個狀態碼為“412 Precondition Failed”的響應,如下所示的程式碼片段就是這樣的請求報文和對應的響應報文。
GET http://localhost:5000/foobar.txt HTTP/1.1 If-Unmodified-Since: Wed, 18 Sep 2019 01:01:01 GMT Host: localhost:5000 HTTP/1.1 412 Precondition Failed Date: Wed, 18 Sep 2019 23:31:53 GMT Server: Kestrel Content-Length: 0
GET http://localhost:50000/foobar.txt HTTP/1.1 Host: localhost:50000 If-Match: "abc123xyz456" HTTP/1.1 412 Precondition Failed Date: Wed, 18 Sep 2019 23:33:57 GMT Server: Kestrel Content-Length: 0
靜態檔案中介軟體[1]: 搭建檔案伺服器
靜態檔案中介軟體[2]: 條件請求以提升效能
靜態檔案中介軟體[3]: 區間請求以提供部分內容
靜態檔案中介軟體[4]: StaticFileMiddleware
靜態檔案中介軟體[5]: DirectoryBrowserMiddleware & DefaultFilesMi