说明:由于部分产品没有静态资源的管理,我突然想到能不能用现有的静态文件中间件的功能调整一下实现多组织件上传文件的隔离呢?那第一步先看懂   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. centos 7 设置 本地更新源

    #yum-config-manager --disable \*--屏弊所有更新源#mkdir /r7iso# cd /run/media/{用户名}/CentOS\ 7\ x86_64/ #cp - ...

  2. Nginx 十大优化 与 防盗链

    Nginx是俄罗斯人编写的十分轻量级的HTTP服务器,Nginx,它的发音为“engine X”,是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器.Ngin ...

  3. Java的异常处理机制

    异常 异常指的是,程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止. 由图可知,异常的根类是throwable.其下有两个子类 Error:严重错误Error,无法通过处理的错误,只 ...

  4. Python学习小记(1)---import小记

    在这种目录结构下,import fibo会实际导入fibo文件夹这个module λ tree /F 卷 Programs 的文件夹 PATH 列表 卷序列号为 BC56-3256 D:. │ fib ...

  5. 「Flink」理解流式处理重要概念

    什么是流式处理呢? 这个问题其实我们大部分时候是没有考虑过的,大多数,我们是把流式处理和实时计算放在一起来说的.我们先来了解下,什么是数据流. 数据流(事件流) 数据流是无边界数据集的抽象 我们之前接 ...

  6. Centos7之firewall配置命令

    firewalld的基本使用 查看状态:systemctl status firewalld 启动:systemctl start firewalld 停止:systemctl stop firewa ...

  7. MySQL 8 InnoDB Table 和 Page 压缩

    压缩用一点CPU换取磁盘IO.内存空间.磁盘空间. 在有Secondary Indexes 的表中,使用压缩更加明显,相关索引数据也会压缩. InnoDB 表压缩 对表压缩只需要在Create Tab ...

  8. WebStorm2018破解教程

    话不多说,直接上教程: 1,下载压缩包,并解压缩,下载地址如下: 链接:谁点谁知道提取码:9am8 2,双击压缩包中的WebStorm-2018.2.1.exe文件,进行安装. 3,安装完成之后,将压 ...

  9. 解决Intellij Idea下修改jsp页面不自动更新

    解决Intellij Idea下修改jsp页面不自动更新 On frame deactivation:被设置成了Do nothing 解决办法:改为Update resources(更新资源)或者Up ...

  10. 洛谷P1331-搜索基础-什么是矩形?(我的方案)

    原题链接:https://www.luogu.com.cn/problem/P1331 简单来说就是给出一个由‘#’和‘.‘组成的矩阵.需要识别存在几个矩形(被完全填充的).如果有矩形相互衔接则认为出 ...