Middleware (1) — File Server

Feng Gao
7 min readAug 22, 2021

--

1 Overview

ASP.NET core 中对静态文件的处理的中间件是 Microsoft.AspNetCore.StaticFiles 这个包。其中包含了如下的中间件:

  • DefaultFilesMiddleware
  • DirectoryBrowserMiddleware
  • StaticFileMiddleware

还有一个中间件 FileServerMiddleware 用来依次注入上述三种的中间件。

这些类型中间件的 Options(SharedOptions) 包含以下共有的属性

public PathString RequestPath {get; set;}
public IFileProvider FileProvider {get; set;}
public bool RedictToAppendTrailingSlash {get; set;}

2 DefaultFilesMiddleware

首先这个中间件的作用是什么呢?假设我们的 Asp.Net core 应用程序如下

毫无疑问,将会返回一个 Hello World , 那么假设我们在 wwwroot 目录下面增加了一个 index.html 文件

并且在 Configure 方法中 app.Run 调用之前增加下面两个方法

app.UseDefaultFiles();app.UseStaticFiles();

那么在运行应用程序的时候,得到的结果是

发现前面增加 index.html 页面被返回。这就是 DefaultFiles 的功能,当访问的路径(目录)没有对应的文件的时候,将挑选出默认的页面返回。

接下来我们探索它是怎么实现的。 DefaultFileOptions 继承 SharedOptions 并包含了 DefaultFileNames 属性,该属性指定默认的文件名,初始化有四种:

  • default.htm
  • default.hmtl
  • index.htm
  • index.html

如果我们不指定 FileProvier ,那么将会以 wwwroot 目录,创建一个 PhysicalFileProvider 对象。那么这个中间件是如何工作的呢?

  • 首先三个检查条件: 1)没有命中其他的 endpoint, 2) Get/Head 方法,3)请求的路径是否为 RequestPath 开头
  • 通过 fileProvider 获得所在的目录信息
  • 依次迭代 DefaultFileNames ,如果这个文件在目录下存在,则取回文件信息
  • 将文件名字添加到原本的请求路径中,然后跳出迭代,交给下一个中间件处理。

对于直接使用 UseDefaultFiles 的扩展方法,它是返回的 wwwroot 目录代表的 IFileProivder

RequestPath 则默认是为空字符串,当访问 https://localhost:43323/ 时候,由 DefaultFiles 中间件处理后,访问的请求路径就变成了 https://localhost:43323/index.html ,而这个交给 StaticFiles 处理了。

2 StaticFileMiddleware

我们希望当上面的请求到达的时候,服务器的中间件能够返回相应的 index.html 文件作为请求的响应。

StaticFileMiddleware 的配置条件增加了下面几个内容

  • IContentTypeProvider ContentTypeProvider 可以根据请求的路径确定 MIME类型, 默认是 FileExtensionContentTypeProvider , 它是从 IIS 中获取的
  • UnknownServiceType 如果未知类型,是否还要继续处理,默认是 false
  • DefaultContentType 如果 ContentTypeProvider 没有找到,而且还要继续处理,则返回 DefaultContentType 类型
  • OnPrepareResponse 如果请求完成,是否继续要做一些其他事情的委托,默认不进行任何操作

这个中间件的处理流程是这样的

  1. 如果由 endpoint, 则跳过
  2. 如果请求方法不是 Get or Head, 跳过
  3. 如果请求路径不是 RequestPath 开头的,跳过
  4. 如果无法查询到 contentType, 跳过
  5. 然后处理真正的静态文件请求

请求的过程是交给 StaticFileContext 处理的,分为两步

  • LookupFileInfo
  • ServeStaticFile

LookupFileInfo

这个方法主要目的是根据文件的相对路径,通过 IFileProvider 得到文件基本信息

  • 是否存在 (Exist)
  • 文件大小 (_length)
  • 上一次修改时间 (_lastModified)
  • _etag

ServeStaticFile

在处理静态文件处理之前,需要处理请求 Header 中特殊情况

  • If-Match

如果请求 Header 中包含了 If-Match 字段,表明只有请求的资源的 etagIf-Match 中内容有一项完全相同的时候,才能返回资源, 比如

If-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"

If-Match: "67ab43", "54ed21", "7892dd"

If-Match: *
  • If-None-Match

If-Match 相反的,只有 etag 不匹配的时候,才会返回请求的响应。

  • If-Modified-Since

请求的资源的只有在 If-Modified-Since 指定的时间之后修改,才会返回请求响应

  • If-Unmodified-Since

请求的资源只会在 If-Unmodified-Since 指定的时候之后没有修改,才会返回请求的响应

  • If-Range

HTTP 请求可以请求部分数据,所以 If-Range 用来判断是否符合 Range 请求。这个其中的条件分为两种

  1. 时间: 如果在给定的事件后修改,则不能进行进行 Range 请求
  2. ETag: 如果请求资源的实体 Etag 不匹配,则不能进行 Range 请求
  • Range

如果 If-Range 条件满足,就需要解析 HeadersRange 指定的长度大小,比如说

Range: <unit>=<range-start>-
Range: <unit>=<range-start>-<range-end>
Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>
Range: <unit>=-<suffix-length>

注意 Asp.NET Core 不支持多个区间段的请求。

在获取上述的信息后,就开始处理请求

  1. 只处理需要处理的请求
  2. 如果是 Head 请求,直接返回 200 OK 响应
  3. 如果是 Range 请求,在计算出需要处理的长度之后,返回响应的文件长度
  4. 否则返回文件的全部内容

3. DirectoryBrowserMiddleware

这个中间可以实现在浏览器中浏览文件夹的功能,如下图所示

DirectoryBrowserOptions 增加了一个属性

  • IDirectoryFormatter Formatter 该接口可以将渲染 FileInfo 的集合,默人实现方式是 HtmlDirectoryFormatter

该中间件处理比较简单,对于满足请求,通过 IFileProvder 获取这个目录下所有的 FileInfo 信息,然后交给 IDirectoryFormatter 接口进行渲染。默认的实现方式,就是渲染出一张表。

--

--

Feng Gao
Feng Gao

Written by Feng Gao

A software developer in Microsoft at Suzhou. Most articles spoken language is Chinese. I will try with English when I’m ready

No responses yet