ASP.NET Core应用针对静态文件请求的处理[2]: 条件请求与区间请求

通过调用ApplicationBuilder的扩展方法UseStaticFiles注册的StaticFileMiddleware中间件帮助我们处理针对文件的请求。对于StaticFileMiddleware处理请求的逻辑,大部分读者都应该想得到:它根据请求的地址找到目标文件的路径,然后利用注册的ContentTypeProvider根据路径解析出与文件内容相匹配的媒体类型,默认情况下得到的媒体类型是根据目标文件的扩展名解析出来的。解析出来的媒体类型将作为响应报头Content-Type的值。StaticFileMiddleware中间件最终利用FileProvider读取文件的内容作为响应消息的主体。实际上,这个中间件在处理请求时比我们想象的要多得多,针对条件请求(Conditional Request)和区间请求(Range Request)的处理就没有在上面演示的实例中体现出来。 [本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、条件请求
    HTTP条件请求
    针对静态文件的条件请求
二、 区间请求
    HTTP区间请求
    针对静态文件的区间请求

一、条件请求

所谓的条件请求就是客户端在发送GET请求获取某种资源的时候,会利用请求报头携带一些条件。服务端处理器在接受到这样的请求之后,会提取这些条件并验证目标资源的当前的状态是否满足客户端指定的条件。在有在这些条件满足的情况下,目标资源的内容才会真正响应给客户端。

HTTP条件请求

HTTP条件请求作为一项标准记录在HTTP规范中。一般来说,一个GET请求在目标资源存在的情况下总是会返回一个状态为“200

OK”的响应,目标资源的内容将直接存放在响应消息的主体部分。如果资源的内容不会轻易改变,我们希望客户端(比如浏览器)在本地缓存获取的资源。对于由它发送的针对同一资源的后续请求,如果资源内容不曾改变,那么资源的内容则无需再次作为网络负载予以响应。这就是条件请求需要解决的一个典型场景。

确定资源是否发生变化可以采用两种策略。第一种就是让资源的提供者记录下最后一次更新资源的时间,资源的负载和这个时间戳将一并作为响应提供给作为请求发送者的客户端。客户端在缓存资源自身内容的同时也会保存这个时间戳。等到下次针对同一资源发送请求,它会将这个时间戳一并发送出去,那么服务端就可以根据这个时间戳判断目标资源在上次响应之后是否被修改过。除了采用记录资源最后修改时间的方式外,我们还可以针对资源的内容生成一个“签名”,签名的一致性体现了资源内容的一致性,在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匹配(If-Match)的请求下才将资源作为响应负载返回。针对这样的请求,如果根据携带的这两个报头判断出目标资源并不曾发生变化,服务端会返回一个将资源内容作为主体的“200
OK”响应,这样的响应也会携带Last-Modified和If-Match报头。反之,如果做出了相反的判断,服务端会响应一个状态码为“412 Precondition Failed”的响应。

针对静态文件的条件请求

接下来我们通过实例演示的形式来介绍StaticFileMiddleware中间件在针对条件请求方面做了些什么。假设我们在ASP.NET

Core应用中发布一个文本文件(foobar.txt),内容为“abcdefghijklmnopqrstuvwxyz0123456789”(26个字母+10个数字),目标地址为“http://localhost:5000/foobar.txt”。现在我们直接利用Fiddler针对这个地址发送一个普通的GET请求,看看会得到怎样的响应。

   1: GET http://localhost:5000/foobar.txt HTTP/1.1
   2: Host: localhost:5000
   3:  
   4: HTTP/1.1 200 OK
   5: Date: Thu, 10 Nov 2016 13:01:59 GMT
   6: Content-Length: 39
   7: Content-Type: text/plain
   8: Server: Kestrel
   9: 
  10: Accept-Ranges: bytes
  11: 
  12:  
  13: abcdefghijklmnopqrstuvwxyz0123456789

从上面给出的请求与响应报文的内容可以看出,对于一个针对物理文件的GET请求,如果目标文件存在,服务器会返回一个状态码为“200

OK”的响应。除了承载着文件内容的主体外,响应报文还具有两个额外的报头,它们分别是表示目标文件最后一次修改时间的Last-Modified和作为文件签名的ETag。

现在客户端不但获得了目标文件的内容,还得到了该文件最后被修改的时间戳和签名,如果它只想确定这个文件是否被更新,并在在更新之后返回新的内容,那么它可以针对这个文件所在的地址再次发送一个GET请求,并将这个时间戳和签名通过相应的请求报头发送给服务端,我们知道这两个报头的名称分别是If-Modified-Since和If-None-Match。由于我们没有修改文件的内容,所以服务器返回如下一个状态为“304
Not Modified”的响应,这个不包括主体的响应同样具有相同的Last-Modified和ETag报头。

   1: GET http://localhost:5000/foobar.txt HTTP/1.1
   2: Host: localhost:5000
   3: If-Modified-Since: 
   4: If-None-Match: "1d23af3dad4aaa7"
   5:  
   6: HTTP/1.1 304 Not Modified
   7: Date: Thu, 10 Nov 2016 13:23:04 GMT
   8: Content-Type: text/plain
   9: Server: Kestrel
  10: Last-Modified: 
  11: Accept-Ranges: bytes
  12: ETag: "1d23af3dad4aaa7"

如果我们将If-None-Match报头修改成一个较早的时间戳,或者改变了If-None-Match报头的签名,服务端都将做出文件已经被修改的判断。在这种情况下,最初那个状态码为“200
OK”的响应又会再次被返回,具体请求和对应的响应体现在如下所示的代码片段中。

   1: GET http://localhost:5000/foobar.txt HTTP/1.1
   2: Host: localhost:5000
   3: If-Modified-Since: 
   4: If-None-Match: "1d23af3dad4aaa7"
   5:  
   6: HTTP/1.1 
   7: Date: Thu, 10 Nov 2016 13:30:25 GMT
   8: Content-Length: 39
   9: Content-Type: text/plain
  10: Server: Kestrel
  11: Last-Modified: 
  12: Accept-Ranges: bytes
  13: ETag: "1d23af3dad4aaa7"
  14:  
  15: GET http://localhost:5000/foobar.txt HTTP/1.1
  16: Host: localhost:5000
  17: If-Modified-Since: Thu, 10 Nov 2016 01:43:37 GMT
  18: If-None-Match: ""
  19:  
  20: HTTP/1.1 
  21: Date: Thu, 10 Nov 2016 13:31:49 GMT
  22: Content-Length: 39
  23: Content-Type: text/plain
  24: Server: Kestrel
  25: Last-Modified: Thu, 10 Nov 2016 01:43:37 GMT
  26: Accept-Ranges: bytes
  27: ETag: ""
  28:  
  29: abcdefghijklmnopqrstuvwxyz0123456789

如果客户端想确定目标文件是否被修改,但是希望在未被修改的情况下才返回目标文件的内容,这样的请求需要使用If-Unmidified-Since和If-Match报头来承载基准时间戳和签名。比如对于如下两个请求携带的If-Unmidified-Since和If-Match报头,服务段都将作出文件尚未被修改的判断,所以文件的内容通过一个状态为“200
OK”的响应返回。  

   1: GET http://localhost:5000/foobar.txt HTTP/1.1
   2: Host: localhost:5000
   3: If-Unmodified-Since: 
   4:  
   5: HTTP/1.1 
   6: Date: Thu, 10 Nov 2016 13:46:32 GMT
   7: Content-Length: 39
   8: Content-Type: text/plain
   9: Server: Kestrel
  10: Last-Modified: 
  11: Accept-Ranges: bytes
  12: ETag: "1d23af3dad4aaa7"
  13:  
  14: abcdefghijklmnopqrstuvwxyz0123456789
  15:  
  16: GET http://localhost:5000/foobar.txt HTTP/1.1
  17: Host: localhost:5000
  18: If-Match: ""
  19:  
  20: HTTP/1.1 
  21: Date: Thu, 10 Nov 2016 13:47:42 GMT
  22: Content-Length: 39
  23: Content-Type: text/plain
  24: Server: Kestrel
  25: Last-Modified: Thu, 10 Nov 2016 01:43:37 GMT
  26: Accept-Ranges: bytes
  27: ETag: ""
  28:  
  29: abcdefghijklmnopqrstuvwxyz0123456789

如果目标文件当前的状态不能满足If-Unmidified-Since或者If-Match报头体现的条件,那么返回的将是一个状态为“412 Preconception Failed”的响应,如下所示的就是两条这样的请求和对应响应的内容。

   1: GET http://localhost:5000/foobar.txt HTTP/1.1
   2: Host: localhost:5000
   3: If-Unmodified-Since: 
   4:  
   5: HTTP/1.1 
   6: Date: Thu, 10 Nov 2016 13:54:09 GMT
   7: Content-Length: 0
   8: Server: Kestrel
   9:  
  10: GET http://localhost:5000/foobar.txt HTTP/1.1
  11: Host: localhost:5000
  12: If-Match: ""
  13:  
  14: HTTP/1.1 
  15: Date: Thu, 10 Nov 2016 13:55:31 GMT
  16: Content-Length: 0
  17: Server: Kestrel

二、 区间请求

大部分针对物理文件的请求都是希望获取整个文件的内容,区间请求则使我们可以获取某个文件部分区间的内容。区间请求使我们可以通过多次请求来获取某个较大文件的内容,并实现断点续传。如果同一个文件同时存放到多台服务器,我们可以利用区间请求同时下载不同部分的内容。和条件请求一样,区间请求也是作为标准定义在HTTP规范之中。

HTTP区间请求

如果我们下希望通过一个GET请求获取目标资源的某个区间的内容,那么我们会将这个区间存放到一个名为Range的报头中。虽然HTTP规范允许指定多个区间,但是StaticFileMiddleware中间件只支持单一区间。至于分区所采用的的计量单位,HTTP规范并未作强制的规定,但是StaticFileMiddleware支持的代码为“bytes”,也就是说它是以字节为单位对文件内容进行分区的。

Range报头携带的分区信息采用的格式为“bytes={from}-{to}”({from}和{to}分别表示区间开始和结束的位置),比如“bytes=1000-1999”表示获取目标资源从1001到2000共计1000个字节(第1个字节的位置为0)。如果{to}大于整个资源的长度,这样的区间被认为是有效的,意味着区间从{from}到资源的最后一个字节。如果区间被定义成“bytes={from}-”这种形式,同样表示区间从{from}到资源的最后一个字节。采用“bytes=-{n}”这种格式定义的区间则表示资源的最后n个字节。不论采用何种形式,如果{from}大于整个资源的总长度,这样的定义都是不合法的定义了。

如果请求的Range报头携带一个不合法的区间,服务端回返回一个状态码为“416 Range Not Satisfiable”的响应,否则会返回一个状态为“206 Partial Content”的响应,响应的主体将只包含指定区间的内容。返回的内容在整个资源的位置通过响应报头Content-Range表示,采用的格式为“{from}-{to}/{length}”。除此之外,还有一个与区间请求相关的响应报头“Accept-Ranges”,它表示服务端能够接受区间类型。比如前面针对条件请求的响应都具有这样一个报头“Accept-Ranges: bytes”,表示服务支持针对资源的区间划分,该报头的值为“none”,则意味着服务端不支持区间请求。

区间请求在某些时候也会去验证资源内容是否发生改变。在这种情况下,请求会利用一个名为If-Range的报头携带一个基础时间戳或者整个资源(不是当前请求的区间)的签名。服务端在接收到请求之后会根据这个报头判断请求的整个资源是否发生变换,如果判断的结果是已经发生改变,它会返回一个状态码为“200 OK”的响应,响应主体将会包含整个资源的内容。只有在判断资源并未发生变化的前提下,服务端再会返回指定区间的内容。

针对静态文件的区间请求

接下来我们照理从HTTP请求和响应报文的角度来探讨StaticFileMiddleware中间件针对区间请求的支持。我们依然沿用前面演示条件请求的那个例子,这个例子中作为目标文件的foobar.txt包含26个字母和10个数字,加上UTF文本文件初始的三个字符(EF
BB BF),所以总长度为39。我们利用Fiddler发送如下两个请求分别获取前面26个字母(3-28)和后面10个数字(-10)。

   1: GET http://localhost:5000/foobar.txt HTTP/1.1
   2: Host: localhost:5000
   3: 
   4:  
   5: HTTP/1.1 
   6: Date: Thu, 10 Nov 2016 15:15:54 GMT
   7: Content-Length: 26
   8: Content-Type: text/plain
   9: Server: Kestrel
  10: 
  11: Last-Modified: Thu, 10 Nov 2016 01:43:37 GMT
  12: 
  13: ETag: "1d23af3dad4aaa7"
  14:  
  15: Abcdefghijklmnopqrstuvwxyz
  16:  
  17: GET http://localhost:5000/foobar.txt HTTP/1.1
  18: Host: localhost:5000
  19: 
  20:  
  21: HTTP/1.1 
  22: Date: Thu, 10 Nov 2016 15:17:02 GMT
  23: Content-Length: 10
  24: Content-Type: text/plain
  25: Server: Kestrel
  26: 
  27: Last-Modified: Thu, 10 Nov 2016 01:43:37 GMT
  28: Accept-Ranges: bytes
  29: ETag: "1d23af3dad4aaa7"
  30:  
  31: 0123456789

由于请求中指定了正确的区间,所以我们会得到两个状态码为“206 Partial
Content”的响应,响应的主体仅仅包含目标区间的内容。除此之外,响应报头“Content-Range” (“bytes
3-28/39”和“bytes
29-38/39”)指明了返回内容的区间范围和整个文件总长度。目标文件最后修改时间戳和签名同样会存在于响应报头Last-Modified和ETag之中。

接下来我们如下一个区间请求,并刻意指定一个不合法的区间(“50-”)。正如HTTP规范所描述的那样,这种情况下我们得到的是一个状态码为“416 Range Not Satisfiable”的响应。

   1: GET http://localhost:5000/foobar.txt HTTP/1.1
   2: Host: localhost:5000
   3: 
   4:  
   5: HTTP/1.1 
   6: Date: Thu, 10 Nov 2016 15:27:00 GMT
   7: Content-Length: 0
   8: Server: Kestrel
   9: 

为了验证区间请求针对文件更新状态的检验,我们使用了请求报头If-Range。在如下所示的这两个请求中,我们分别将一个基准时间戳和文件签名作为这个报头值,很明显服务端针对这两个报头的值都将做出“文件已经更新”的判断。根据HTTP规范的约定,这种请求将会返回一个状态码“200
OK”的响应,响应的主体将会包含整个文件的内容,如下所示的响应消息证实了这一点。

   1: GET http://localhost:5000/foobar.txt HTTP/1.1
   2: Host: localhost:5000
   3: 
   4: 
   5:  
   6: HTTP/1.1 
   7: Date: Thu, 10 Nov 2016 15:35:59 GMT
   8: Content-Length: 39
   9: Content-Type: text/plain
  10: Server: Kestrel
  11: Last-Modified: 
  12: 
  13: ETag: "1d23af3dad4aaa7"
  14:  
  15: 
  16:  
  17: GET http://localhost:5000/foobar.txt HTTP/1.1
  18: Host: localhost:5000
  19: Range: 
  20: 
  21:  
  22: HTTP/1.1 
  23: Date: Thu, 10 Nov 2016 15:36:54 GMT
  24: Content-Length: 39
  25: Content-Type: text/plain
  26: Server: Kestrel
  27: Last-Modified: Thu, 10 Nov 2016 01:43:37 GMT
  28: Accept-Ranges: bytes
  29: ETag: ""
  30:  
  31: 

 



ASP.NET Core应用针对静态文件请求的处理[1]: 以Web的形式发布静态文件
ASP.NET Core应用针对静态文件请求的处理[2]: 条件请求与区间请求
ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求
ASP.NET Core应用针对静态文件请求的处理[4]: DirectoryBrowserMiddleware中间件如何呈现目录结构
ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-09-19 09:41:55

ASP.NET Core应用针对静态文件请求的处理[2]: 条件请求与区间请求的相关文章

ASP.NET Core应用针对静态文件请求的处理[4]: DirectoryBrowserMiddleware中间件如何呈现目录结构

和StaticFileMiddleware中间件一样,DirectoryBrowserMiddleware中间本质上还是定义了一个请求地址与某个物理目录之间的映射关系,而目标目录体现为一个FileProvider对象.当这个中间件接收到匹配的请求后,会根据请求地址解析出对应目录的相对路径,并利用这个FileProvider获取目录的内容.目录的内容最终会以一个HTML文档的形式被定义,而此HTML最终会被这个中间件作为响应的内容,"目录浏览器"的实现原理就这么简单. [本文已经同步到&

ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面

DefaultFilesMiddleware中间件的目的在于将目标目录下的默认文件作为响应内容.我们知道,如果直接请求的就是这个默认文件,那么前面介绍的StaticFileMiddleware中间件会将这个文件响应给客户端.如果我们能够将针对目录的请求重定向到这个默认文件上,一切就迎刃而解了.实际上DefaultFilesMiddleware中间件的实现逻辑很简单,它采用URL重写的形式修改了当前请求的地址,即将针对目录的URL修改成针对默认文件的URL.[本文已经同步到<ASP.NET Cor

ASP.NET Core应用针对静态文件请求的处理[1]: 以Web的形式发布静态文件

虽然ASP.NET Core是一款"动态"的Web服务端框架,但是在很多情况下都需要处理针对静态文件的请求,最为常见的就是这对JavaScript脚本文件.CSS样式文件和图片文件的请求.针对不同格式的静态文件请求的处理,ASP.NET Core为我们提供了三个中间件,它们将是本系列文章论述的重点.不过在针对对它们展开介绍之前,我们照理通过一些简单的实例来体验一下如何在一个ASP.NET Core应用中发布静态文件.[本文已经同步到<ASP.NET Core框架揭秘>之中]

ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求

我们通过<以Web的形式发布静态文件>和<条件请求与区间请求>中的实例演示,以及上面针对条件请求和区间请求的介绍,从提供的功能和特性的角度对这个名为StaticFileMiddleware的中间进行了全面的介绍,接下来我们将更近一步,将从实现原理的角度来进一步认识这个中间件. [本文已经同步到<ASP.NET Core框架揭秘>之中] 目录 一.StaticFileMiddleware 二.ContentTypeProvider 三.利用配置指定StaticFileOp

asp.net core 之静态文件目录的操作

文章前言 之前写了一篇关于模拟登录的文章,自我感觉内容不太丰富,今天的这篇文章,希望在内容上能丰富些.本人缺少写文章的经验,技术上也是新手,但我会努力的,希望大家多多支持小弟. asp.net core项目静态文件 创建一个asp.net core 项目的静态文件一般是放在项目目录下wwwroot文件夹,文件目录如下. 如何将静态文件注入到项目中 在startup.cs文件的Configure方法中写入: app.UseStaticFiles(); 这方法的默认路径正是上面所说的wwwroot目

解析如何利用一个ASP.NET Core应用来发布静态文件_实用技巧

虽然ASP.NET Core是一款"动态"的Web服务端框架,但是在很多情况下都需要处理针对静态文件的请求,最为常见的就是这对JavaScript脚本文件.CSS样式文件和图片文件的请求.针对不同格式的静态文件请求的处理,ASP.NET Core为我们提供了三个中间件,它们将是本系列文章论述的重点.不过在针对对它们展开介绍之前,我们照理通过一些简单的实例来体验一下如何在一个ASP.NET Core应用中发布静态文件. 目录 一.以Web的形式读取文件 二.浏览目录内容 三.显示默认页面

asp.net core实现文件上传功能_实用技巧

本文实例为大家分享了单文件上传.多文件上传的功能,供大家参考,具体内容如下 单文件上传 上传文件在Web应用程序中是一个常见的功能.在asp.net core中上传文件并保存在服务器上,是很容易的.下面就来演示一下怎么样在 ASP.NET Core项目中进行文件上传.  首先,创建一个 asp.net core 项目,然后在Controller文件件添加一个HomeController,然后在 Views 文件夹的 Home 文件夹里添加一个 New.cshtml 视图文件.如下图:  添加一个

Asp.Net Core 通过中间件防止图片盗链的实例_实用技巧

一.原理 要实现防盗链,我们就必须先理解盗链的实现原理,提到防盗链的实现原理就不得不从HTTP协议说起,在HTTP协议中,有一个表头字段叫referer,采用URL的格式来表示从哪儿链接到当前的网页或文件.换句话说,通过referer,网站可以检测目标网页访问的来源网页,如果是资源文件,则可以跟踪到显示它的网页地址.有了referer跟踪来源就好办了,这时就可以通过技术手段来进行处理,一旦检测到来源不是本站即进行阻止或者返回指定的页面.如果想对自己的网站进行防盗链保护,则需要针对不同的情况进行区

ASP.NET Core MVC/WebAPi 模型绑定探索

前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用到了,你再去看理论性的文章时才会豁然开朗,这是我一直以来学习技术的方法.本文我们来讲解.NET Core中的模型绑定. 话题 在ASP.NET Core之前MVC和Web APi被分开,也就说其请求管道是独立的,而在ASP.NET Core中,WebAPi和MVC的请求管道被合并在一起,当我们建立控