StaticFileMiddleware 解析
说明:由于部分产品没有静态资源的管理,我突然想到能不能用现有的静态文件中间件的功能调整一下实现多组织件上传文件的隔离呢?那第一步先看懂 StaticFileMiddleware做了什么吧。
PS:本文不解释中间件原理。
第一步 方法源码
using System;using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.StaticFiles
{
/// <summary>
/// 这个就是静态文件中间件的核心代码
/// </summary>
public class StaticFileMiddleware
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="next">The next middleware in the pipeline.</param>
/// <param name="hostingEnv">The used by this middleware.</param>
/// <param name="options">The configuration options.</param>
/// <param name="loggerFactory">An instance used to create loggers.</param>
public StaticFileMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, IOptions<StaticFileOptions> options, ILoggerFactory loggerFactory)
{
if (next == null)
{
throw new ArgumentNullException("next");
}
if (hostingEnv == null)
{
throw new ArgumentNullException("hostingEnv");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
if (loggerFactory == null)
{
throw new ArgumentNullException("loggerFactory");
}
this._next = next;
this._options = options.Value;
this._contentTypeProvider = (options.Value.ContentTypeProvider ?? new FileExtensionContentTypeProvider());
this._fileProvider = (this._options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv));
this._matchUrl = this._options.RequestPath;
this._logger = LoggerFactoryExtensions.CreateLogger<StaticFileMiddleware>(loggerFactory);
} /// <summary>
/// 这里才是静态文件处理的业务方法
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
StaticFileContext fileContext = new StaticFileContext(context, this._options, this._matchUrl, this._logger, this._fileProvider, this._contentTypeProvider);
//验证请求方法
if (!fileContext.ValidateMethod())
{
this._logger.LogRequestMethodNotSupported(context.Request.Method);
}
//验证路径
else if (!fileContext.ValidatePath())
{
this._logger.LogPathMismatch(fileContext.SubPath);
}
//验证文件类型系统是否识别
else if (!fileContext.LookupContentType())
{
this._logger.LogFileTypeNotSupported(fileContext.SubPath);
}
else
{
//尝试读取文件信息
if (fileContext.LookupFileInfo())
{
//
fileContext.ComprehendRequestHeaders();
switch (fileContext.GetPreconditionState())
{
//访问成功了
case StaticFileContext.PreconditionState.Unspecified:
case StaticFileContext.PreconditionState.ShouldProcess:
//如果是head请求,直接范围200,不返回请求体
if (fileContext.IsHeadMethod)
{
await fileContext.SendStatusAsync();
return;
}
try
{
//请求包含Range
if (fileContext.IsRangeRequest)
{
await fileContext.SendRangeAsync();
return;
}
//这个默认无参的时候是200
await fileContext.SendAsync();
this._logger.LogFileServed(fileContext.SubPath, fileContext.PhysicalPath);
return;
}
catch (FileNotFoundException)
{
ResponseExtensions.Clear(context.Response);
goto IL_3E2;
}
break;
//请求没有变更,这之后跳出switch,反回304
case StaticFileContext.PreconditionState.NotModified:
break;
case StaticFileContext.PreconditionState.PreconditionFailed:
this._logger.LogPreconditionFailed(fileContext.SubPath);
//请求不匹配
await fileContext.SendStatusAsync();
return;
default:
throw new NotImplementedException(fileContext.GetPreconditionState().ToString());
}
this._logger.LogPathNotModified(fileContext.SubPath);
await fileContext.SendStatusAsync();
return;
}
this._logger.LogFileNotFound(fileContext.SubPath);
}
//不符合静态资源的要求,走下一个中间件(通常是MVC中间件)
IL_3E2:
await this._next.Invoke(context);
} // Token: 0x04000048 RID: 72
private readonly StaticFileOptions _options; // Token: 0x04000049 RID: 73
private readonly PathString _matchUrl; // Token: 0x0400004A RID: 74
private readonly RequestDelegate _next; // Token: 0x0400004B RID: 75
private readonly ILogger _logger; // Token: 0x0400004C RID: 76
private readonly IFileProvider _fileProvider; // Token: 0x0400004D RID: 77
private readonly IContentTypeProvider _contentTypeProvider;
}
}
第二步 方法解析
1 ValidateMethod 验证请求方式
public bool ValidateMethod()
{
this._method = this._request.Method;
this._isGet = HttpMethods.IsGet(this._method);
this._isHead = HttpMethods.IsHead(this._method);
return this._isGet || this._isHead;
}
这个函数从字面上看就是判断当前静态资源的请求方式是不是get 和head,只要是其中一种就是可以的。
get请求方式就不说吗了,head请求方式的解释如下:
HEAD方法与GET方法的行为很类似,但服务器在响应中只返回首部。不会反回实体的主体部分。这就允许客户端在未获取实际资源的情况下,对资源的首部进行检查。一般应用于在不获取资源的情况下了解资源情况。
2 ValidatePath 验证路径
public bool ValidatePath()
{
return Helpers.TryMatchPath(this._context, this._matchUrl, false, out this._subPath);
} internal static bool TryMatchPath(HttpContext context, PathString matchUrl, bool forDirectory, out PathString subpath)
{
PathString pathString = context.Request.Path;
if (forDirectory && !Helpers.PathEndsInSlash(pathString))
{
pathString += new PathString("/");
}
return pathString.StartsWithSegments(matchUrl, ref subpath);
} internal static bool PathEndsInSlash(PathString path)
{
return path.Value.EndsWith("/", StringComparison.Ordinal);
}
这个验证其实就是验证请求文件的路径前缀是否和你系统设置的是否一致。matchUrl这个参数可以在Startup.cs中在静态文件中间件中作为参数传递进去。如图所示
如上所述,现在系统只能请求/a开头的文件了(路径开头,不是文件名称开头)。
具体匹配逻辑可以看Path 的StartsWithSegments的实现。
3 LookupContentType 文件类型验证
if (this._contentTypeProvider.TryGetContentType(this._subPath.Value, out this._contentType))
{
return true;
}
if (this._options.ServeUnknownFileTypes)
{
this._contentType = this._options.DefaultContentType;
return true;
}
return false;
这个验证就是检验拓展名的。可以结合一下实例查看
首先判断请求文件的拓展名在FileExtensionContentTypeProvider中是否存在(FileExtensionContentTypeProvider原本就会有很多拓展类型,截图中我们认为的添加上了image和log的拓展格式)
系统先去判断拓展格式是否存在,如果存在则连带的把反馈类型也返回出来。
如果不存在就去判断是否设置了允许未知的拓展出。例如我有一个拓展名叫做 .alex,然后我们希望系统可以将其以txt的格式反馈,那么就需要如下操作:
4 LookupFileInfo 文件信息验证
public bool LookupFileInfo()
{
this._fileInfo = this._fileProvider.GetFileInfo(this._subPath.Value);
if (this._fileInfo.Exists)
{
this._length = this._fileInfo.Length;
DateTimeOffset lastModified = this._fileInfo.LastModified;
this._lastModified = new DateTimeOffset(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second, lastModified.Offset).ToUniversalTime();
long value = this._lastModified.ToFileTime() ^ this._length;
this._etag = new EntityTagHeaderValue("\"" + Convert.ToString(value, ) + "\"");
}
return this._fileInfo.Exists;
}
这个验证首先是验证文件是否存在,之后会做一系列的计算操作。这一步会获取两个值
_lastModified:文件最后操作日期
_etag :等效算法,通过文件大小、最后操作日期来生成的一个数据值。上述两个值可以通过浏览器查看如下:
这两个值其实就是判断文件缓存的,具体分析可以参看以下文章:
https://www.cnblogs.com/softidea/p/5986339.html
5 ComprehendRequestHeaders 请求头验证
public void ComprehendRequestHeaders()
{
this.ComputeIfMatch();
this.ComputeIfModifiedSince();
this.ComputeRange();
this.ComputeIfRange();
}
这一段就用到了上一个验证的时候生成的_lastModified和_etag来计算缓存问题,故不再赘述了。逻辑代码如下:
private void ComputeIfMatch()
{
IList<EntityTagHeaderValue> ifMatch = this._requestHeaders.IfMatch;
if (ifMatch != null && ifMatch.Any<EntityTagHeaderValue>())
{
this._ifMatchState = StaticFileContext.PreconditionState.PreconditionFailed;
foreach (EntityTagHeaderValue entityTagHeaderValue in ifMatch)
{
if (entityTagHeaderValue.Equals(EntityTagHeaderValue.Any) || entityTagHeaderValue.Compare(this._etag, true))
{
this._ifMatchState = StaticFileContext.PreconditionState.ShouldProcess;
break;
}
}
}
IList<EntityTagHeaderValue> ifNoneMatch = this._requestHeaders.IfNoneMatch;
if (ifNoneMatch != null && ifNoneMatch.Any<EntityTagHeaderValue>())
{
this._ifNoneMatchState = StaticFileContext.PreconditionState.ShouldProcess;
foreach (EntityTagHeaderValue entityTagHeaderValue2 in ifNoneMatch)
{
if (entityTagHeaderValue2.Equals(EntityTagHeaderValue.Any) || entityTagHeaderValue2.Compare(this._etag, true))
{
this._ifNoneMatchState = StaticFileContext.PreconditionState.NotModified;
break;
}
}
}
} // Token: 0x0600006C RID: 108 RVA: 0x00004CE8 File Offset: 0x00002EE8
private void ComputeIfModifiedSince()
{
DateTimeOffset utcNow = DateTimeOffset.UtcNow;
DateTimeOffset? ifModifiedSince = this._requestHeaders.IfModifiedSince;
if (ifModifiedSince != null && ifModifiedSince <= utcNow)
{
this._ifModifiedSinceState = ((ifModifiedSince < this._lastModified) ? StaticFileContext.PreconditionState.ShouldProcess : StaticFileContext.PreconditionState.NotModified);
}
DateTimeOffset? ifUnmodifiedSince = this._requestHeaders.IfUnmodifiedSince;
if (ifUnmodifiedSince != null && ifUnmodifiedSince <= utcNow)
{
this._ifUnmodifiedSinceState = ((ifUnmodifiedSince >= this._lastModified) ? StaticFileContext.PreconditionState.ShouldProcess : StaticFileContext.PreconditionState.PreconditionFailed);
}
}
后面几个方法,this.ComputeRange();和this.ComputeIfRange();
可以参考一下文章:
https://blog.csdn.net/shuimuniao/article/details/8086438
总结
静态资源中间件的逻辑可以简单的归纳如下:
1、判断是否是静态资源请求方式(Head或者Get)
2、判断请求连接是否符合设置UrlMatch(RequestPath配置)
3、判断文件拓展名是否存在或者启动未知拓展名服务(DefaultContentType、ServeUnknownFileTypes)
4、判断文件是否存在,计算文件_lastModified和_etag
5、如果上述4点判断都失败了,则认为不是静态资源请求,走下一个中间件
6、根据请求头和请求资源的数据判断以下几种反馈请求
- 请求类型是Head ,返回 200 但是没有请求体
- 请求If-Range不匹配,反馈412
- 请求的缓存匹配,反馈 304
- 请求成功,反馈200及请求体。
StaticFileMiddleware 解析的更多相关文章
- ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求
我们通过<以Web的形式发布静态文件>和<条件请求与区间请求>中的实例演示,以及上面针对条件请求和区间请求的介绍,从提供的功能和特性的角度对这个名为StaticFileMidd ...
- StaticFileMiddleware中间件如何处理针对文件请求
StaticFileMiddleware中间件如何处理针对文件请求 我们通过<以Web的形式发布静态文件>和<条件请求与区间请求>中的实例演示,以及上面针对条件请求和区间请求的 ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- .NET Core中的认证管理解析
.NET Core中的认证管理解析 0x00 问题来源 在新建.NET Core的Web项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...
- Html Agility Pack 解析Html
Hello 好久不见 哈哈,今天给大家分享一个解析Html的类库 Html Agility Pack.这个适用于想获取某网页里面的部分内容.今天就拿我的Csdn的博客列表来举例. 打开页面 用Fir ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例
前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...
- Asp.Net WebApi核心对象解析(下篇)
在接着写Asp.Net WebApi核心对象解析(下篇)之前,还是一如既往的扯扯淡,元旦刚过,整个人还是处于晕的状态,一大早就来处理系统BUG,简直是坑爹(好在没让我元旦赶过来该BUG),队友挖的坑, ...
随机推荐
- yum安装logstash 不生效
问题描述 根据logstash的配置方法写了一个配置文件,并放入/etc/logstash/conf.d/目录下,然后我们运行logstash # service logstash start Log ...
- this关键字和static关键字
this关键字 普通方法中,this总是指向调用该方法的对象. 构造方法中,this总是指向正要初始化的对象. this区分成员变量和全局变量的作用,在当前类中可以省略. this的常用方法: 让类中 ...
- Java日志介绍(3)-Logback
Logback 继承自Log4j,它建立在有十年工业经验的日志系统之上.它比其它所有的日志系统更快并且更小,包含了许多独特并且有用的特性. 1.配置 1.1.加载配置 Logback能够在初始化期间自 ...
- springcloud vue.js 微服务 分布式 activiti工作流 前后分离 shiro权限 集成代码生成器
1.代码生成器: [正反双向](单表.主表.明细表.树形表,快速开发利器)freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本.处理类.service等完整模块2. ...
- 05.JS函数
前言: 学习一门编程语言的基本步骤(01)了解背景知识(02)搭建开发环境(03)语法规范(04)常量和变量(05)数据类型(06)数据类型转换(07)运算符(08)逻辑结构(09)函数9.函数——f ...
- docker jenkins 前端node项目 自动化部署异常 env: ‘node’: No such file or directory
出现问题是docker jenkins 里面没有自动安装node导致找不到这个Node命令 解决方案:手动安装nodejs # 进入jenkins对应容器中 # docker exec -it [对应 ...
- ELK学习004:Elasticsearch常规操作
CRUD 在我们的项目中有日志是一个必不可少的东西,但是日志的检索是一个很麻烦的事情,如每天一个日志,要找到问题就得一个一个找,并不能做到检索功能,这还算好的,如果是分布式的,每个机器都得找一遍,这种 ...
- LAMP环境搭建+配置虚拟域名
Centos下PHP,Apache,Mysql 的安装 安装Apache yum -y install httpd systemctl start httpd 添加防火墙 firewall-cmd - ...
- opencv —— threshold、adaptiveThreshold 固定阈值 & 自适应阈值 进行图像二值化处理
阈值化 在对图像进行处理操作的过程中,我们常常需要对图像中的像素做出取舍与决策,直接剔除一些低于或高于一定值的像素. 阈值分割可以视为最简单的图像分割方法.比如基于图像中物体与背景之间的灰度差异,可以 ...
- POJ 2556 (判断线段相交 + 最短路)
题目: 传送门 题意:在一个左小角坐标为(0, 0),右上角坐标为(10, 10)的房间里,有 n 堵墙,每堵墙都有两个门.每堵墙的输入方式为 x, y1, y2, y3, y4,x 是墙的横坐标,第 ...