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 & DefaultFilesMiddleware

posted @ 2020-12-16 10:57  Artech  阅读(1436)  评论(2编辑  收藏  举报