浅谈ASP.NET Core静态文件处理源码探究(2)

var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".myapp"] = "application/x-msdownload"; provider.Mappings[".htm3"] = "text/html"; app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider, //可以在输出之前设置输出相关 OnPrepareResponse = ctx => { ctx.Context.Response.Headers.Append("Cache-Control", $"public, max-age=3600"); } });

接下来我们步入正题直接查看StaticFileMiddleware中间件的代码[点击查看StaticFileMiddleware源码]

public class StaticFileMiddleware { private readonly StaticFileOptions _options; private readonly PathString _matchUrl; private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly IFileProvider _fileProvider; private readonly IContentTypeProvider _contentTypeProvider; public StaticFileMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, IOptions<StaticFileOptions> options, ILoggerFactory loggerFactory) { _next = next; _options = options.Value; //设置文件类型提供程序 _contentTypeProvider = options.Value.ContentTypeProvider ?? new FileExtensionContentTypeProvider(); //文件提供程序 _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv); //匹配路径 _matchUrl = _options.RequestPath; _logger = loggerFactory.CreateLogger<StaticFileMiddleware>(); } public Task Invoke(HttpContext context) { //判断是够获取到终结点信息,这也就是为什么我们使用UseStaticFiles要在UseRouting之前 if (!ValidateNoEndpoint(context)) { } //判断HttpMethod,只能是Get和Head操作 else if (!ValidateMethod(context)) { } //判断请求路径是否存在 else if (!ValidatePath(context, _matchUrl, out var subPath)) { } //根据请求文件名称判断是否可以匹配到对应的MimeType,如果匹配到则返回contentType else if (!LookupContentType(_contentTypeProvider, _options, subPath, out var contentType)) { } else { //执行静态文件操作 return TryServeStaticFile(context, contentType, subPath); } return _next(context); } private Task TryServeStaticFile(HttpContext context, string contentType, PathString subPath) { var fileContext = new StaticFileContext(context, _options, _logger, _fileProvider, contentType, subPath); //判断文件是否存在 if (!fileContext.LookupFileInfo()) { _logger.FileNotFound(fileContext.SubPath); } else { //静态文件处理 return fileContext.ServeStaticFile(context, _next); } return _next(context); } }

关于FileExtensionContentTypeProvider这里就不作讲解了,主要是承载文件扩展名和MimeType的映射关系代码不复杂,但是映射关系比较多,有兴趣的可以自行查看FileExtensionContentTypeProvider源码,通过上面我们可以看到,最终执行文件相关操作的是StaticFileContext类[点击查看StaticFileContext源码]

internal struct StaticFileContext { private const int StreamCopyBufferSize = 64 * 1024; private readonly HttpContext _context; private readonly StaticFileOptions _options; private readonly HttpRequest _request; private readonly HttpResponse _response; private readonly ILogger _logger; private readonly IFileProvider _fileProvider; private readonly string _method; private readonly string _contentType; private IFileInfo _fileInfo; private EntityTagHeaderValue _etag; private RequestHeaders _requestHeaders; private ResponseHeaders _responseHeaders; private RangeItemHeaderValue _range; private long _length; private readonly PathString _subPath; private DateTimeOffset _lastModified; private PreconditionState _ifMatchState; private PreconditionState _ifNoneMatchState; private PreconditionState _ifModifiedSinceState; private PreconditionState _ifUnmodifiedSinceState; private RequestType _requestType; public StaticFileContext(HttpContext context, StaticFileOptions options, ILogger logger, IFileProvider fileProvider, string contentType, PathString subPath) { _context = context; _options = options; _request = context.Request; _response = context.Response; _logger = logger; _fileProvider = fileProvider; _method = _request.Method; _contentType = contentType; _fileInfo = null; _etag = null; _requestHeaders = null; _responseHeaders = null; _range = null; _length = 0; _subPath = subPath; _lastModified = new DateTimeOffset(); _ifMatchState = PreconditionState.Unspecified; _ifNoneMatchState = PreconditionState.Unspecified; _ifModifiedSinceState = PreconditionState.Unspecified; _ifUnmodifiedSinceState = PreconditionState.Unspecified; //再次判断请求HttpMethod if (HttpMethods.IsGet(_method)) { _requestType = RequestType.IsGet; } else if (HttpMethods.IsHead(_method)) { _requestType = RequestType.IsHead; } else { _requestType = RequestType.Unspecified; } } /// <summary> /// 判断文件是否存在 /// </summary> 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, 16) + '\"'); } return _fileInfo.Exists; } /// <summary> /// 处理文件输出 /// </summary> public async Task ServeStaticFile(HttpContext context, RequestDelegate next) { //1.准备输出相关Header,主要是获取和输出静态文件输出缓存相关的内容 //2.我们之前提到的OnPrepareResponse也是在这里执行的 ComprehendRequestHeaders(); //根据ComprehendRequestHeaders方法获取到的文件状态进行判断 switch (GetPreconditionState()) { case PreconditionState.Unspecified: //处理文件输出 case PreconditionState.ShouldProcess: //判断是否是Head请求 if (IsHeadMethod) { await SendStatusAsync(Constants.Status200Ok); return; } try { //判断是否包含range请求,即文件分段下载的情况 if (IsRangeRequest) { await SendRangeAsync(); return; } //正常文件输出处理 await SendAsync(); _logger.FileServed(SubPath, PhysicalPath); return; } catch (FileNotFoundException) { context.Response.Clear(); } await next(context); return; case PreconditionState.NotModified: await SendStatusAsync(Constants.Status304NotModified); return; case PreconditionState.PreconditionFailed: await SendStatusAsync(Constants.Status412PreconditionFailed); return; default: var exception = new NotImplementedException(GetPreconditionState().ToString()); throw exception; } } /// <summary> /// 通用文件文件返回处理 /// </summary> public async Task SendAsync() { SetCompressionMode(); ApplyResponseHeaders(Constants.Status200Ok); string physicalPath = _fileInfo.PhysicalPath; var sendFile = _context.Features.Get<IHttpResponseBodyFeature>(); //判断是否设置过输出特征操作相关,比如是否启动输出压缩,或者自定义的输出处理比如输出加密等等 if (sendFile != null && !string.IsNullOrEmpty(physicalPath)) { await sendFile.SendFileAsync(physicalPath, 0, _length, CancellationToken.None); return; } try { //不存在任何特殊处理的操作作,直接读取文件返回 using (var readStream = _fileInfo.CreateReadStream()) { await StreamCopyOperation.CopyToAsync(readStream, _response.Body, _length, StreamCopyBufferSize, _context.RequestAborted); } } catch (OperationCanceledException ex) { _context.Abort(); } } /// <summary> /// 分段请求下载操作处理 /// </summary> internal async Task SendRangeAsync() { if (_range == null) { ResponseHeaders.ContentRange = new ContentRangeHeaderValue(_length); ApplyResponseHeaders(Constants.Status416RangeNotSatisfiable); _logger.RangeNotSatisfiable(SubPath); return; } //计算range相关header数据 ResponseHeaders.ContentRange = ComputeContentRange(_range, out var start, out var length); _response.ContentLength = length; //设置输出压缩相关header SetCompressionMode(); ApplyResponseHeaders(Constants.Status206PartialContent); string physicalPath = _fileInfo.PhysicalPath; var sendFile = _context.Features.Get<IHttpResponseBodyFeature>(); //判断是否设置过输出特征操作相关,比如是否启动输出压缩,或者自定义的输出处理比如输出加密等等 if (sendFile != null && !string.IsNullOrEmpty(physicalPath)) { _logger.SendingFileRange(_response.Headers[HeaderNames.ContentRange], physicalPath); await sendFile.SendFileAsync(physicalPath, start, length, CancellationToken.None); return; } try { using (var readStream = _fileInfo.CreateReadStream()) { readStream.Seek(start, SeekOrigin.Begin); _logger.CopyingFileRange(_response.Headers[HeaderNames.ContentRange], SubPath); //设置文件输出起始位置和读取长度 await StreamCopyOperation.CopyToAsync(readStream, _response.Body, length, _context.RequestAborted); } } catch (OperationCanceledException ex) { _context.Abort(); } } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/02e386c850b617c8edb815e6797b27eb.html