.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中间件会 ...
随机推荐
- LCD调试
(1) 液晶显示模式 并行:MCU接口.RGB接口.Vysnc接口 串行:SPI接口.MDDI接口 (2) 屏幕颜色 实质上即为色阶的概念.色阶是表示手机液晶显示屏亮度强弱的指数标准,也就是通常所说的 ...
- python csv读写
https://blog.csdn.net/taotiezhengfeng/article/details/75577998
- .NET工行E生活接入AES加密算法的吐槽-2018
工行E生活V2版本AES加密算法 吐槽一下工行的java算法,真的是非标准的,参考了java代码,还参考了php代码终于搞定了. 真的是很坑,很坑.中间还涉及到多重加密之类的,一行行把代码翻译成C#代 ...
- MySql在Mac上的安装配置
一.下载安装 官网下载社区版dmg安装文件: https://dev.mysql.com/downloads/mysql/ 1.执行安装文件,按步骤完成安装. 2.安装完成后终端输入: mysql - ...
- gitlab 之 项目管理
首先git是什么? Git是一款免费.开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目.Git的读音为/gɪt/. Git是一个开源的分布式版本控制系统,可以有效.高速的处理从很小到非常 ...
- day_11函数的形参与实参
昨天讲了函数的定义和简单分类 1:什么是函数:具体特定功能的代码快 --特定功能代码作为一个整体,并给该整体命名,就是函数. 函数的优点: 1:减少代码的冗余 2:结构清晰,可读性强 3:具有复用性, ...
- [转]Kaldi语音识别
转:http://ftli.farbox.com/post/kaldizhong-wen-shi-bie Kaldi语音识别 1.声学建模单元的选择 1.1对声学建模单元加入位置信息 2.输入特征 3 ...
- Codeforces Round #554 (Div. 2) 1152B. Neko Performs Cat Furrier Transform
学了这么久,来打一次CF看看自己学的怎么样吧 too young too simple 1152B. Neko Performs Cat Furrier Transform 题目链接:"ht ...
- Java进程和线程关系及区别
1.定义 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基 ...
- 动态创建数据table
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...