说明:由于部分产品没有静态资源的管理,我突然想到能不能用现有的静态文件中间件的功能调整一下实现多组织件上传文件的隔离呢?那第一步先看懂   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、根据请求头和请求资源的数据判断以下几种反馈请求

  1. 请求类型是Head ,返回 200 但是没有请求体
  2. 请求If-Range不匹配,反馈412
  3. 请求的缓存匹配,反馈 304
  4. 请求成功,反馈200及请求体。

StaticFileMiddleware 解析的更多相关文章

  1. ASP.NET Core应用针对静态文件请求的处理[3]: StaticFileMiddleware中间件如何处理针对文件请求

    我们通过<以Web的形式发布静态文件>和<条件请求与区间请求>中的实例演示,以及上面针对条件请求和区间请求的介绍,从提供的功能和特性的角度对这个名为StaticFileMidd ...

  2. StaticFileMiddleware中间件如何处理针对文件请求

    StaticFileMiddleware中间件如何处理针对文件请求 我们通过<以Web的形式发布静态文件>和<条件请求与区间请求>中的实例演示,以及上面针对条件请求和区间请求的 ...

  3. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  4. .NET Core中的认证管理解析

    .NET Core中的认证管理解析 0x00 问题来源 在新建.NET Core的Web项目时选择“使用个人用户账户”就可以创建一个带有用户和权限管理的项目,已经准备好了用户注册.登录等很多页面,也可 ...

  5. Html Agility Pack 解析Html

    Hello 好久不见 哈哈,今天给大家分享一个解析Html的类库 Html Agility Pack.这个适用于想获取某网页里面的部分内容.今天就拿我的Csdn的博客列表来举例. 打开页面  用Fir ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  7. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  8. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  9. Asp.Net WebApi核心对象解析(下篇)

    在接着写Asp.Net WebApi核心对象解析(下篇)之前,还是一如既往的扯扯淡,元旦刚过,整个人还是处于晕的状态,一大早就来处理系统BUG,简直是坑爹(好在没让我元旦赶过来该BUG),队友挖的坑, ...

随机推荐

  1. k8s Pipline CI/CD

    一.Pipeline介绍 pipeline是一套jenkins官方提供的插件,它可以用来在jenkins中实现和集成连续交付 用户可以利用Pipeline的许多功能: 代码:pipeline在代码中实 ...

  2. K8S ConfigMap使用

    k8s系列文章: 什么是K8S configmap是k8s的一个配置管理组件,可以将配置以key-value的形式传递,通常用来保存不需要加密的配置信息,加密信息则需用到Secret,主要用来应对以下 ...

  3. 实验一 GIT 代码版本管理

    实验一  GIT 代码版本管理 实验目的: 1)了解分布式分布式版本控制系统的核心机理: 2)   熟练掌握git的基本指令和分支管理指令: 实验内容: 1)安装git 2)初始配置git ,git ...

  4. 来去学习之---KMP算法--next计算过程

    一.概述 KMP算法是一种字符串匹配算法,比如现有字符串 T:ABCDABCDABCDCABCDABCDE, P:ABCDABCDE P字符串对应的next值:[0,0,0,0,1,2,3,4,0] ...

  5. git的使用方法大全

    换了一个新的工作环境,由于以前都是使用SVN管理代码,现在要换成git,但我对git了解不多,只好下功夫咯!脑子不灵活,命令语句容易忘,所以做个笔记记录下~~~1.安装git到Git官网下载合适自己电 ...

  6. 如何在IDEA的maven项目中连接并使用MySQL8.0

    首先看一下我的基本的开发环境: 操作系统:MacOS 10.13.5 编辑器:IDEA 2018.3 其他:MySQL8.0.15.Maven 3.3.9.JDK 1.8 好,下面就正式开始: 第一步 ...

  7. Tomcat 项目部署、账户配置、状态监测

    tomcat部署项目 方式一.自动部署(最常用) 直接把war包或部署的文件夹放到webapps下. tomcat启动后会自动监听webapps下的文件|目录,放入打包好的项目会自动部署,移除打包好的 ...

  8. Mysql 两种引擎的区别

    MyISAM与InnoDB的区别是什么? 1. 存储结构 MyISAM:每个MyISAM在磁盘上存储成三个文件.第一个文件的名字以表的名字开始,扩展名指出文件类型..frm文件存储表定义.数据文件的扩 ...

  9. NIO学习笔记,从Linux IO演化模型到Netty—— 从BIO到epoll模型

    本文不涉及具体代码,只分析Linux IO演化的心路历程,学习资料来源网络,不保证一定正确,若有错误,欢迎指出. BIO 服务端创建socket(80端口),文件描述符3号. 当线程调用accept时 ...

  10. .net core 3.0+unit of work (一)

    1.先将unit of work源码下载 2.引入自己的项目 3.根据原始项目示意在自己项目的startup里注册仓储 由于我不想对每个实体都注册一遍,我使用了泛型仓储(core 2.0好像不支持) ...