.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中间件会 ...
随机推荐
- JAVA 8 主要新特性 ----------------(六)集合Stream API
一.简介Stream Java8中有两大最为重要的改变.第一个是 Lambda 表达式:另外一 个则是 Stream API(java.util.stream.*).Stream 是 Java8 中处 ...
- 简述Oracle IOT(Index Organized Table)
转:http://blog.itpub.net/17203031/viewspace-744477 对关系型数据库产品(RDBMS)而言,一个重要特性就是:数据信息都被组织为二维数据表,信息的表达可以 ...
- Nginx 代理配置
1.反向代理 修改conf\nginx.conf文件, 添加proxy_pass属性 server { listen 7080; #nginx 端口 server_name localhost; #n ...
- [转] KVM VirtIO paravirtualized drivers: why they matter
http://www.ilsistemista.net/index.php/virtualization/42-kvm-virtio-paravirtualized-drivers-why-they- ...
- Photon自定义加载Resource之外的资源
PhotonNetwork.cs 结尾添加如下代码: #region >>> Photon自定义异步加载GameObject public delegate void CustomL ...
- Java中的NIO基础知识
上一篇介绍了五种NIO模型,本篇将介绍Java中的NIO类库,为学习netty做好铺垫 Java NIO 由3个核心组成,分别是Channels,Buffers,Selectors.本文主要介绍着三个 ...
- ISG 2018 Web Writeup
作者:agetflag 原文来自:ISG 2018 Web Writeup ISG 2018 Web Writeup CTF萌新,所以写的比较基础,请大佬们勿喷,比赛本身的Web题也不难 calc 首 ...
- Javascript高级编程学习笔记(40)—— DOM(6)CDATASection、DocumentType
CDATASection类型 CDATASection类型是只针对XML文档的类型 因为浏览器无法解析 在浏览器中创建CDATASection的函数也无法正常使用 该类型有以下属性 nodeType: ...
- 聊一聊Java如何接入招行一网通支付功能
1.前提条件 相比较于支付宝和微信的支付功能接入这一块,银行相对来说更加严格,比如说支付宝,在你签约之前可以进行一些测试.但是银行来说就不是这样了,如果您现在要进行招行的支付功能开发的话,请务必先让相 ...
- 吴恩达机器学习笔记43-SVM大边界分类背后的数学(Mathematics Behind Large Margin Classification of SVM)
假设我有两个向量,