.Net Core 中间件之静态文件(StaticFiles)源码解析
一、介绍
在介绍静态文件中间件之前,先介绍 ContentRoot和WebRoot概念。
ContentRoot:指web的项目的文件夹,包括bin和webroot文件夹。
WebRoot:一般指ContentRoot路径下的wwwroot文件夹。
介绍这个两个概念是因为静态资源文件一般存放在WebRoot路径下,也就是wwwroot。下面为这两个路径的配置,如下所示:
public static void Main(string[] args)
{var host = new WebHostBuilder()
.UseKestrel()
.UseStartup<Startup>()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseWebRoot(Directory.GetCurrentDirectory() + @"\wwwroot\")
.UseEnvironment(EnvironmentName.Development)
.Build();
host.Run();
}
上面的代码将ContentRoot路径和WebRoot路径都配置了,其实只需配置ContentRoot路径,WebRoot默认为ContentRoot路径下的wwwroot文件夹路径。
在了解静态文件中间件前,还需要了解HTTP中关于静态文件缓存的机制。跟静态文件相关的HTTP头部主要有Etag和If-None-Match。
下面为访问静态文件服务器端和客户端的流程:
1、客户端第一次向客户端请求一个静态文件。
2、服务器收到客户端访问静态文件的请求,服务器端会根据静态文件最后的修改时间和文件内容的长度生成一个Hash值,并将这个值放到请求头ETag中。
3、客户端第二次发起同一个请求时,因为之前请求过此文件,所以本地会有缓存。在请求时会在请求头中加上If-Nono-Match,其值为服务器返回的ETag的值。
4、服务器端比对发送的来的If-None-Match的值和本地计算的ETag的值是否相同。如果相同,返回304状态码,客户端继续使用本地缓存。如果不相同,返回200状态码,客户端重新解析服务器返回的数据,不使用本地缓存。
具体看下面例子。
二、简单使用
2.1 最简单的使用
最简单的使用就是在Configure中加入下面一句话,然后将静态文件放到webRoot的路径下,我没有修改webRoot指定的路径,所以就是wwwroot文件夹。
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
app.UseMvc();
}
在wwwroot文件夹下放一个名称为1.txt的测试文本,然后通过地址访问。

这种有一个缺点,暴露这个文件的路径在wwwroot下。
2.2 指定请求地址
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(); app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(@"C:\Users\Administrator\Desktop"),
RequestPath = new PathString("/Static")
}); //app.UseStaticFiles("/Static");
}
这种指定了静态文件存放的路径为:C:\Users\Administrator\Desktop,不是使用默认的wwwroot路径,就隐藏了文件的真实路径,并且需要在地址中加上static才能访问。
当然也可以不指明静态文件的路径,只写请求路径,如上面代码中的注释的例子。这样静态文件就必须存储到WebRoot对应的目录下了。如果WebRoot的目录对应的是wwwroot,静态文件就放到wwwroot文件夹中。

下面通过例子看一下静态文件的缓存,如果你想做这个例子,别忘记先清空缓存。
(第一次请求)

(第二次请求 文件相对第一次请求没有修改的情况)

(第三次请求 文件相对第一次请求有修改的情况)
三、源码分析
源码在https://github.com/aspnet/StaticFiles,这个项目还包含有其他中间件。既然是中间件最重要的就是参数为HttpContext的Invoke方法了,因为每一个请求都要经过其处理,然后再交给下一个中间件处理。下面为处理流程。
public async Task Invoke(HttpContext context)
{
var fileContext = new StaticFileContext(context, _options, _matchUrl, _logger, _fileProvider, _contentTypeProvider); if (!fileContext.ValidateMethod())//静态文件的请求方式只能是Get或者Head
{
_logger.LogRequestMethodNotSupported(context.Request.Method);
}
//判断请求的路径和配置的请求路径是否匹配。如请求路径为http://localhost:5000/static/1.txt
//配置为RequestPath = new PathString("/Static")
//则匹配,并将文件路径赋值给StaticFileContext中点的_subPath
else if (!fileContext.ValidatePath())
{
_logger.LogPathMismatch(fileContext.SubPath);
}
//通过获取要访问文件的扩展名,获取此文件对应的MIME类型,
//如果找到文件对应的MIME,返回True,并将MIME类型赋值给StaticFileContext中的_contextType
//没有找到返回False.
else if (!fileContext.LookupContentType())
{
_logger.LogFileTypeNotSupported(fileContext.SubPath);
}
//判断访问的文件是否存在。
//如果存在返回True,并根据文件的最后修改时间和文件的长度,生成Hash值,并将值赋值给_etag,也就是相应头中的Etag。
//如果不存在 返回False,进入下一个中间件中处理
else if (!fileContext.LookupFileInfo())
{
_logger.LogFileNotFound(fileContext.SubPath);
}
else
{
fileContext.ComprehendRequestHeaders();
//根据StaticFileContext中的值,加上对应的相应头,并发送响应。具体调用方法在下面
switch (fileContext.GetPreconditionState())
{
case StaticFileContext.PreconditionState.Unspecified:
case StaticFileContext.PreconditionState.ShouldProcess:
if (fileContext.IsHeadMethod)
{
await fileContext.SendStatusAsync(Constants.Status200Ok);
return;
}
try
{
if (fileContext.IsRangeRequest)
{
await fileContext.SendRangeAsync();
return;
}
await fileContext.SendAsync();
_logger.LogFileServed(fileContext.SubPath, fileContext.PhysicalPath);
return;
}
catch (FileNotFoundException)
{
context.Response.Clear();
}
break;
case StaticFileContext.PreconditionState.NotModified:
_logger.LogPathNotModified(fileContext.SubPath);
await fileContext.SendStatusAsync(Constants.Status304NotModified);
return;
case StaticFileContext.PreconditionState.PreconditionFailed:
_logger.LogPreconditionFailed(fileContext.SubPath);
await fileContext.SendStatusAsync(Constants.Status412PreconditionFailed);
return;
default:
var exception = new NotImplementedException(fileContext.GetPreconditionState().ToString());
Debug.Fail(exception.ToString());
throw exception;
}
}
//进入下一个中间件中处理
await _next(context);
}
添加响应头的方法:
public void ApplyResponseHeaders(int statusCode)
{
_response.StatusCode = statusCode;
if (statusCode < )
{
if (!string.IsNullOrEmpty(_contentType))
{
_response.ContentType = _contentType;
}
//设置响应头中最后修改时间、ETag和accept-ranges
_responseHeaders.LastModified = _lastModified;
_responseHeaders.ETag = _etag;
_responseHeaders.Headers[HeaderNames.AcceptRanges] = "bytes";
}
if (statusCode == Constants.Status200Ok)
{
_response.ContentLength = _length;
}
_options.OnPrepareResponse(new StaticFileResponseContext()
{
Context = _context,
File = _fileInfo,
});
}
校验文件是否修改的方法:
public bool LookupFileInfo()
{
_fileInfo = _fileProvider.GetFileInfo(_subPath.Value);
if (_fileInfo.Exists)
{
_length = _fileInfo.Length;
DateTimeOffset last = _fileInfo.LastModified;
_lastModified = new DateTimeOffset(last.Year, last.Month, last.Day, last.Hour, last.Minute, last.Second, last.Offset).ToUniversalTime();
//通过修改时间和文件长度,得到ETag的值
long etagHash = _lastModified.ToFileTime() ^ _length;
_etag = new EntityTagHeaderValue('\"' + Convert.ToString(etagHash, ) + '\"');
}
return _fileInfo.Exists;
}
.Net Core 中间件之静态文件(StaticFiles)源码解析的更多相关文章
- .Net Core 认证系统之Cookie认证源码解析
接着上文.Net Core 认证系统源码解析,Cookie认证算是常用的认证模式,但是目前主流都是前后端分离,有点鸡肋但是,不考虑移动端的站点或者纯管理后台网站可以使用这种认证方式.注意:基于浏览器且 ...
- [源码解析] TensorFlow 分布式环境(2)---Master 静态逻辑
[源码解析] TensorFlow 分布式环境(2)---Master 静态逻辑 目录 [源码解析] TensorFlow 分布式环境(2)---Master 静态逻辑 1. 总述 2. 接口 2.1 ...
- [源码解析] TensorFlow 分布式环境(3)--- Worker 静态逻辑
[源码解析] TensorFlow 分布式环境(3)--- Worker 静态逻辑 目录 [源码解析] TensorFlow 分布式环境(3)--- Worker 静态逻辑 1. 继承关系 1.1 角 ...
- 【Spring源码分析】.properties文件读取及占位符${...}替换源码解析
前言 我们在开发中常遇到一种场景,Bean里面有一些参数是比较固定的,这种时候通常会采用配置的方式,将这些参数配置在.properties文件中,然后在Bean实例化的时候通过Spring将这些.pr ...
- [源码解析] Pytorch 如何实现后向传播 (2)---- 引擎静态结构
[源码解析] Pytorch 如何实现后向传播 (2)---- 引擎静态结构 目录 [源码解析] Pytorch 如何实现后向传播 (2)---- 引擎静态结构 0x00 摘要 0x01 Engine ...
- [源码解析] PyTorch 分布式(10)------DistributedDataParallel 之 Reducer静态架构
[源码解析] PyTorch 分布式(10)------DistributedDataParallel之Reducer静态架构 目录 [源码解析] PyTorch 分布式(10)------Distr ...
- ASP.NET Core 1.1 静态文件、路由、自定义中间件、身份验证简介
概述 之前写过一篇关于<ASP.NET Core 1.0 静态文件.路由.自定义中间件.身份验证简介>的文章,主要介绍了ASP.NET Core中StaticFile.Middleware ...
- NET Core 1.1 静态文件、路由、自定义中间件、身份验证简介
NET Core 1.1 静态文件.路由.自定义中间件.身份验证简介 概述 之前写过一篇关于<ASP.NET Core 1.0 静态文件.路由.自定义中间件.身份验证简介>的文章,主要 ...
- ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面
DefaultFilesMiddleware中间件的目的在于将目标目录下的默认文件作为响应内容.我们知道,如果直接请求的就是这个默认文件,那么前面介绍的StaticFileMiddleware中间件会 ...
随机推荐
- 利用AnyProxy代理监控APP流量
1.介绍 AnyProxy 是阿里巴巴基于 Node.js 开发的一款开源代理服务器. 代理服务器站在客户端和服务端的中间,它可以收集双方通信的每个比特.一个完整的代理请求过程为:客户端首先与代理服务 ...
- 可遇不可求的Question之Mysql在不重启服务的情况下修改运行时变量篇
比方说在一些实际生产环境中,想改个MYSQL的配置,但是又不想停止服务重起MYSQL,有什么办法呢?使用SET命令可以做到,请看下面几个例子: 1.设置key_buffer_size的大小为10M. ...
- 基于fpga的vga学习(3)
本次学习如何通过vga发送数字.文字.字母, 首先利用建模软件,将想要发送的数据通过数学建模转换,这里我用的软件是PCtoLCD,具体效果如下 这里可以看出,建模将数据装换成0和1,一个字母用16x8 ...
- ubuntu下file_get_contents返回空字符串
ubuntu下file_get_contents返回空字符串 | 浏览:302 | 更新:2014-03-30 10:11 本文起初面临的问题是PHP中SoapClient不好使,最后file_get ...
- 编码符_new88
begin#239B38F58D59E401465E1FEE0AFA7AE2DD920EB6645F4A2075C7ABBBE2141B925668C9D635D90DE884907F4E52F921 ...
- Unity一键设置导入图片格式
前几天由于项目原因,做了一个自动根据模型自动创建动画状态机,然后紧接着做了根据动画状态机和模型一键制作Prefab. 现在因为图片数量或者其它原因需要写一个一键设置图片格式的插件. 至于制作动画状态机 ...
- struts2 简单注解配置代替xml配置文件
1. 主要文件 LoginAction.javapackage com.edu.struts2.action;import org.apache.struts2.convention.annotati ...
- Docker应用:Docker-compose(容器编排)
阅读目录: Docker应用:Hello World Docker应用:Docker-compose(容器编排) 前言: 昨天完成了Docker入门示例(Docker应用:Hello World),示 ...
- 【设计经验】3、ISE中烧录QSPI Flash以及配置mcs文件的加载速度与传输位宽
一.软件与硬件平台 软件平台: 操作系统:Windows 7 64-bit 开发套件:ISE14.7 硬件平台: FPGA型号:XC6SLX45-CSG324 QSPI Flash型号:W25Q128 ...
- Git使用详细教程(4):git rm使用详解
我们使用git rm 文件名来进行删除文件的操作. git rm index.php这个命令把工作区的index.php删除并暂存了. 如何撤回已暂存的删除命令? 上图中已经给出了提示,使用git r ...