2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界

ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界

时间:2020-03-10 03:51:37

相关推荐

ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界

本系列文章从源码分析的角度来探索 Core 的运行原理,分为以下几个章节:

Core 运行原理解剖[1]:Hosting

Core 运行原理解剖[2]:Hosting补充之配置介绍

Core 运行原理解剖[3]:Middleware-请求管道的构成

IHttpContextFactory

在第一章中,我们介绍到,WebHost在启动IServer时,会传入一个IHttpApplication<TContext>类型的对象,Server 负责对请求的监听,在接收到请求时,会调用该对象的ProcessRequestAsync方法将请求转交给我们的应用程序。IHttpApplication<TContext>的默认实现为HostingApplication,有如下定义:

public class HostingApplication : IHttpApplication<HostingApplication.Context> {

private readonly RequestDelegate _application;

private readonly IHttpContextFactory _httpContextFactory;

public Context CreateContext(IFeatureCollection contextFeatures) { var context = new Context();

var httpContext = _httpContextFactory.Create(contextFeatures); _diagnostics.BeginRequest(httpContext, ref context); context.HttpContext = httpContext;

return context; }

public Task ProcessRequestAsync(Context context) {

return _application(context.HttpContext); }

public void DisposeContext(Context context, Exception exception) { var httpContext = context.HttpContext; _diagnostics.RequestEnd(httpContext, exception, context); _httpContextFactory.Dispose(httpContext); _diagnostics.ContextDisposed(context); } }

首先使用IHttpContextFactory来创建HttpContext实例,然后在ProcessRequestAsync方法中调用上一章介绍的RequestDelegate,由此进入到我们的应用程序当中。

IHttpContextFactory 负责对HttpContext的创建和释放,分别对应着CreateDispose方法,它的默认实现类为HttpContextFactory,定义如下:

public class HttpContextFactory : IHttpContextFactory{

private readonly IHttpContextAccessor _httpContextAccessor;

private readonly FormOptions _formOptions;

public HttpContext Create(IFeatureCollection featureCollection)

{

var httpContext = new DefaultHttpContext(featureCollection);

if (_httpContextAccessor != null){_httpContextAccessor.HttpContext = httpContext;}

var formFeature = new FormFeature(httpContext.Request, _formOptions);featureCollection.Set<IFormFeature>(formFeature);

return httpContext;}

public void Dispose(HttpContext httpContext) {

if (_httpContextAccessor != null){_httpContextAccessor.HttpContext = null;}}}

如上,HttpContextFactory 只是简单的使用new DefaultHttpContext(featureCollection)来创建 HttpContext 的实例,而这里涉及到一个IFeatureCollection对象,它是由 Server 根据原始请求创建而来的,下面就先介绍一下该对象。

IFeatureCollection

不过,在介绍IFeatureCollection之前,我们先需先回顾一下OWIN:

OWIN是 “Open Web Server Interface for .NET” 的首字母缩写,它定义了一套Web Server和Web Application之间的标准接口,主要用于解除 与 IIS 的紧密耦合。为此,OWIN 定义了四个核心组件:Host,Server,Middleware,Application,并为Server和Middleware的之间的交互提供了一个Func<IDictionary<string,object>,Task>类型的标准接口。

每一个OWIN中间件,都会接收到一个IDictionary<string,object>类型的变量,用来表示当前请求的相关信息,也称为环境字典。每一个支持OWIN标准的 Web Server 都会根据请求的原始上下文信息,封装成这个环境字典,然后在OWIN中间件之间传递,进而完成整个请求的处理。环境字典定义了一系列预先约定好的Key,比如:用 "owin.RequestBody" 来表示请求体,"owin.RequestHeaders" 来表示请求头,"owin.RequestMethod" 来表示请求方法等。

OWIN是随着 MVC5进行到我们的视线中,在当时, WebAPI 2.0 也基于OWIN实现了自寄宿模式。再后来,提出了 5 与 MVC6,完全是基于OWIN的模式来开发的,再到今天的 Core,OWIN的概念已被模糊化了,但是还是随处可以见到OWIN的影子,并且也提供了对 OWIN 的扩展支持。

在 Core 中,提出了IFeatureCollection的概念,它本质上也是一个IDictionary<string,object>键值对,但是它具有面向对象的特点,相对于IDictionary<string,object>更加清晰,容易理解,并且Server构建成这样一个对象也很容易,它有如下定义:

public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>{

bool IsReadOnly { get; }

int Revision { get; }

object this[Type key] { get; set; }TFeature Get<TFeature>();

void Set<TFeature>(TFeature instance);}

它的定义非常简单,由一系列以键值对来表示的标准特性对象(TFeature)组成,可以通过一个索引以及GetSet方法来获取或设置这些特性对象。

下面,我们看一下在 Core 中的对它的一个模拟实现:

public class FeatureCollection : IFeatureCollection{

private IDictionary<Type, object> _features;

private readonly IFeatureCollection _defaults;

private volatile int _containerRevision;

public virtual int Revision{

get { return _containerRevision + (_defaults?.Revision ?? 0); }}

public object this[Type key]{

get{

object result;

return _features != null && _features.TryGetValue(key, out result) ? result : _defaults?[key];}

set{

if (value == null){

if (_features != null && _features.Remove(key)){_containerRevision++;}

return;}

if (_features == null){_features = new Dictionary<Type, object>();}_features[key] = value;_containerRevision++;}}

public TFeature Get<TFeature>(){

return (TFeature)this[typeof(TFeature)];}

public void Set<TFeature>(TFeature instance){ this[typeof(TFeature)] = instance;} }

如上,它的内部属性_features便是OWIN中的标准环境字典,并且提供了更加方便的泛型Get,Set方法,以及一个索引器来访问该环境字典。不过,如果只是这样,那使用起来依然不够方便,更为重要的是 Core 还提供了一系列的特性对象,并以这些特性对象的类型做为环境字典中的Key。

通过上面代码,还可以发现,每次对该环境字典的修改,都会使Revision属性递增1。

这里为什么说FeatureCollection是一个模拟的实现呢?具我观察,FeatureCollection对象只在 Core的测试代码中用到,而每个Server都有它自己的方式来构建IFeatureCollection,并不会使用FeatureCollection,关于Server中是如何创建IFeatureCollection实例的,可以参考KestrelHttpServer中的实现,这里就不再深究。

那特性对象又是什么呢?我们先看一下请求特性的定义:

public interface IHttpRequestFeature{

string Protocol { get; set; }

string Scheme { get; set; }

string Method { get; set; }

string PathBase { get; set; }

string Path { get; set; }

string QueryString { get; set; }

string RawTarget { get; set; }IHeaderDictionary Headers { get; set; }Stream Body { get; set; }}

再看一下表单特性的定义:

public interface IFormFeature{

bool HasFormContentType { get; }IFormCollection Form { get; set; }

IFormCollection ReadForm(); Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken);}

可以看到,这些特性对象与我们熟悉的HttpContext中的属性非常相似,这也就大大简化了在IHttpRequestFeatureHttpContext之间的转换。我们可以通过这些特性接口定义的属性来获取到原始上下文中描述的信息,并通过特性对象提供的方法来操作原始上下文,它就像Web Server与我们的应用程序之间的桥梁,完成抽象和具体之间的转换。

Core 提供了一系列丰富的特性对象,如 Session, Cookies, Query, Form, WebSocket, Request, Response 等等, 更详细的列表可以查看Microsoft.AspNetCore.Http.Features。

HttpContext

HttpContext 对象我们应该都很熟悉了,它用来表示一个抽象的HTTP上下文,而HttpContext对象的核心又体现在用于描述请求的Request和描述响应的Response属性上。除此之外,它还包含一些与当前请求相关的其他上下文信息,如描述当前HTTP连接的ConnectionInfo对象,控制WebSocket的WebSocketManager,代表当前用户的ClaimsPrincipal对象的Session,等等:

public abstract class HttpContext{

public abstract IFeatureCollection Features { get; }

public abstract HttpRequest Request { get; }

public abstract HttpResponse Response { get; }

public abstract ConnectionInfo Connection { get; }

public abstract WebSocketManager WebSockets { get; }

public abstract ClaimsPrincipal User { get; set; }

public abstract IDictionary<object, object> Items { get; set; }

public abstract IServiceProvider RequestServices { get; set; }

public abstract CancellationToken RequestAborted { get; set; }

public abstract string TraceIdentifier { get; set; }

public abstract ISession Session { get; set; }

public abstract void Abort();}

在我们处理请求时,如果希望终止该请求,可以通过RequestAborted属性给请求管道发送一个终止信息。当需要对整个管道共享一些与当前上下文相关的数据,可以将它保存在Items字典中。而在 Coer 1.x 中还包含一个管理认证的AuthenticationManager对象,但是在 2.0 中,将它移到了AuthenticationHttpContextExtensions中,因为用户认证本来就一个相对复杂且独立的模块,把它独立出去会更加符合 Core 的简洁模块化特性。

在上文中,我们了解到 HttpContext 的默认实现使用的是DefaultHttpContext类型 ,而 DefaultHttpContext 便是对上面介绍的IFeatureCollection对象的封装:

public class DefaultHttpContext : HttpContext{ private FeatureReferences<FeatureInterfaces> _features; private HttpRequest _request; private HttpResponse _response; public DefaultHttpContext(IFeatureCollection features) {Initialize(features);} public virtual void Initialize(IFeatureCollection features) {_features = new FeatureReferences<FeatureInterfaces>(features);_request = InitializeHttpRequest();_response = InitializeHttpResponse();} protected virtual HttpRequest InitializeHttpRequest() => new DefaultHttpRequest(this);}

如上,DefaultHttpContext通过Initialize来完成从 IFeatureCollection 到 HttpContext 的转换,而各个属性的转换又交给了它们自己。

HttpRequest

HttpRequest 可以用来获取到描述当前请求的各种相关信息,比如请求的协议(HTTP或者HTTPS)、HTTP方法、地址,以及该请求的请求头,请求体等:

public abstract class HttpRequest{

public abstract HttpContext HttpContext { get; }

public abstract string Method { get; set; }

public abstract string Scheme { get; set; }

public abstract bool IsHttps { get; set; }

public abstract HostString Host { get; set; }

public abstract PathString PathBase { get; set; }

public abstract PathString Path { get; set; }

public abstract QueryString QueryString { get; set; }

public abstract IQueryCollection Query { get; set; }

public abstract string Protocol { get; set; }

public abstract IHeaderDictionary Headers { get; }

public abstract IRequestCookieCollection Cookies { get; set; }

public abstract long? ContentLength { get; set; }

public abstract string ContentType { get; set; }

public abstract Stream Body { get; set; }

public abstract bool HasFormContentType { get; }

public abstract IFormCollection Form { get; set; }

public abstract Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = new CancellationToken());}

HttpRequest是一个抽象类,它的默认实现是DefaultHttpRequest:

public class DefaultHttpRequest : HttpRequest{

private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;

private FeatureReferences<FeatureInterfaces> _features;

public DefaultHttpRequest(HttpContext context) {Initialize(context);}

public virtual void Initialize(HttpContext context) {_context = context;_features = new FeatureReferences<FeatureInterfaces>(context.Features);}

private IHttpRequestFeature HttpRequestFeature => _features.Fetch(ref _features.Cache.Request, _nullRequestFeature);

public override string Method{

get { return HttpRequestFeature.Method; }

set { HttpRequestFeature.Method = value; }}

public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken) {

return FormFeature.ReadFormAsync(cancellationToken);}}

在 DefaultHttpRequest 中,并没有额外的功能,它只是简单的与IHttpRequestFeature中的同名属性和方法做了一个映射,而 IHttpRequestFeature 对象的获取又涉及到一个FeatureReferences<FeatureInterfaces>类型, 从字面意思来说,就是对Feature对象的一个引用,用来保存对应的Feature实例,并在上文介绍的Revision属性发生变化时,清空Feature实例的缓存:

public struct FeatureReferences<TCache>{

public IFeatureCollection Collection { get; private set; }

public int Revision { get; private set; }

public TCache Cache;[MethodImpl(MethodImplOptions.AggressiveInlining)]

public TFeature Fetch<TFeature, TState>(ref TFeature cached, TState state, Func<TState, TFeature> factory) where TFeature : class{

var flush = false;

var revision = Collection.Revision;

if (Revision != revision){cached = null;flush = true;}

return cached ?? UpdateCached(ref cached, state, factory, revision, flush);}

private TFeature UpdateCached<TFeature, TState>(ref TFeature cached, TState state, Func<TState, TFeature> factory, int revision, bool flush) where TFeature : class{ if (flush){Cache = default(TCache);}cached = Collection.Get<TFeature>();

if (cached == null){cached = factory(state);Collection.Set(cached);Revision = Collection.Revision;}

else if (flush)

{Revision = revision;}

return cached;}

public TFeature Fetch<TFeature>(ref TFeature cached, Func<IFeatureCollection, TFeature> factory)

where TFeature : class => Fetch(ref cached, Collection, factory);}

如上,当Revision生成变化时,会将Cache设置为 null , 然后重新从IFeatureCollection中获取,最后更新Revision为最新版本,相当于一个缓存工厂。

Fetch方法使用了[MethodImpl(MethodImplOptions.AggressiveInlining)]特性,表示该方法会尽可能的使用内联方式来执行。而内联是一种很重要的优化方式, 它允许编译器在方法调用开销比方法本身更大的情况下消除对方法调用的开销,即直接将该方法体嵌入到调用者中。

HttpResponse

在了解了表示请求的抽象类HttpRequest之后,我们再来认识一下与它对应的,用来描述响应的HttpResponse类型:

public abstract class HttpResponse{

private static readonly Func<object, Task> _callbackDelegate = callback => ((Func<Task>)callback)();

private static readonly Func<object, Task> _disposeDelegate = disposable =>{((IDisposable)disposable).Dispose();

return pletedTask;};

public abstract HttpContext HttpContext { get; }

public abstract int StatusCode { get; set; }

public abstract IHeaderDictionary Headers { get; }

public abstract Stream Body { get; set; }

public abstract long? ContentLength { get; set; }

public abstract string ContentType { get; set; }

public abstract IResponseCookies Cookies { get; }

public abstract bool HasStarted { get; }

public abstract void OnStarting(Func<object, Task> callback, object state);

public virtual void OnStarting(Func<Task> callback) => OnStarting(_callbackDelegate, callback);

public abstract void OnCompleted(Func<object, Task> callback, object state);

public virtual void RegisterForDispose(IDisposable disposable) => OnCompleted(_disposeDelegate, disposable);

public virtual void OnCompleted(Func<Task> callback) => OnCompleted(_callbackDelegate, callback);

public virtual void Redirect(string location) => Redirect(location, permanent: false);

public abstract void Redirect(string location, bool permanent);}

HttpResponse也是一个抽象类,我们使用它来输出对请求的响应,如设置HTTP状态码,Cookies,HTTP响应报文头,响应主体等,以及提供了一些将响应发送到客户端时的相关事件。

HasStarted属性用来表示响应是否已开始发往客户端,在我们第一次调用response.Body.WriteAsync方法时,该属性便会被设置为True。需要注意的是,一旦HasStarted设置为true后,便不能再修改响应头,否则将会抛出InvalidOperationException异常,也建议我们在HasStarted设置为true后,不要再对 Response 进行写入,因为此时content-length的值已经确定,继续写入可能会造成协议冲突。

HttpResponse 的默认实现为 DefaultHttpResponse ,它与 DefaultHttpRequest 类似,只是对IHttpResponseFeature的封装,不过 Core 也为我们提供了一些扩展方法,如:我们在写入响应时,通常使用的是 Response 的扩展方法WriteAsync

public static class HttpResponseWritingExtensions{

public static Task WriteAsync(this HttpResponse response, string text, CancellationToken cancellationToken = default(CancellationToken)) { return response.WriteAsync(text, Encoding.UTF8, cancellationToken);}

public static Task WriteAsync(this HttpResponse response, string text, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken)) {

byte[] data = encoding.GetBytes(text);

return response.Body.WriteAsync(data, 0, data.Length, cancellationToken);}}

Core 还为 Response 提供了用来一个清空响应头和响应体的扩展方法:

public static class ResponseExtensions{

public static void Clear(this HttpResponse response) {

if (response.HasStarted){

throw new InvalidOperationException("The response cannot be cleared, it has already started sending.");}response.StatusCode = 200;response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = null;response.Headers.Clear();

if (response.Body.CanSeek){response.Body.SetLength(0);}}}

还有比较常用的发送文件的扩展方法:SendFileAsync,获取响应头的扩展方法:GetTypedHeaders等等,就不再细说。

IHttpContextAccessor

在 4.x 我们经常会通过HttpContext.Current来获取当前请求的HttpContext对象,而在 Core 中,HttpContext 不再有Current属性,并且在 Core 中一切皆注入,更加推荐使用注入的方式来获取实例,而非使用静态变量。因此, Core 提供了一个IHttpContextAccessor接口,用来统一获取当前请求的 HttpContext 实例的方式:

public interface IHttpContextAccessor{HttpContext HttpContext { get; set; }}

它的定义非常简单,就只有一个 HttpContext 属性,它在 Core 中还有一个内置的实现类:HttpContextAccessor

public class HttpContextAccessor : IHttpContextAccessor{

private static AsyncLocal<HttpContext> _httpContextCurrent = new AsyncLocal<HttpContext>();

public HttpContext HttpContext{

get{

return _httpContextCurrent.Value;}

set{_httpContextCurrent.Value = value;}}}

这里使用了一个AsyncLocal<T>类型来保存 HttpContext 对象,可能很多人对AsyncLocal不太了解,这里就来介绍一下:

在.NET 4.5 中引用了asyncawait等关键字,使我们可以像编写同步方法一样方便的来执行异步操作,因此我们的大部分代码都会使用异步。以往我们所使用的ThreadLocal在同步方法中没有问题,但是在await后有可能会创建新实的例(await 之后可能还交给之前的线程执行,也有可能是一个新的线程来执行),而不再适合用来保存线程内的唯一实例,因此在 .NET 4.6 中引用了AsyncLocal<T>类型,它类似于 ThreadLocal,但是在await之后就算切换线程也仍然可以保持同一实例。我们知道在 4.x 中,HttpContext的Current实例是通过CallContext对象来保存的,但是 Core 中不再支持CallContext,故使用AsyncLocal<T>来保证线程内的唯一实例。

不过, Core 默认并没有注入IHttpContextAccessor对象,如果我们想在应用程序中使用它,则需要手动来注册:

public void ConfigureServices(IServiceCollection services){ervices.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();}

在上面介绍的HttpContextFactory类的构造函数中会注入IHttpContextAccessor实例,并为其HttpContext属性赋值,并在Dispose方法中将其设置为null。

总结

在 4.x 中,我们就对 HttpContext 非常熟悉了,而在 Core 中,它的变化并不大,只是做了一些简化,因此本文较为简单,主要描述了一下 HttpContext 是如何创建的,以及它的构成,最后则介绍了一下在每个请求中获取 HttpContext 唯一实例的方式,而在 Core 2.0 中 HttpContext 的AuthenticationManager对象已标记为过时,添加了一些扩展方法来实现AuthenticationManager中的功能,下一章就来介绍一下 Core 中的认证系统。

相关文章:

.NET Core 2.0 正式发布信息汇总

.NET Standard 2.0 特性介绍和使用指南

.NET Core 2.0 的dll实时更新、https、依赖包变更问题及解决

.NET Core 2.0 特性介绍和使用指南

Entity Framework Core 2.0 新特性

体验 PHP under .NET Core

.NET Core 2.0使用NLog

升级项目到.NET Core 2.0,在Linux上安装Docker,并成功部署

解决Visual Studio For Mac Restore失败的问题

Core 2.0 特性介绍和使用指南

.Net Core下通过Proxy 模式 使用 WCF

.NET Core 2.0 开源Office组件 NPOI

Core Razor页面 vs MVC

Razor Page– Core 2.0新功能 Razor Page介绍

MySql 使用 EF Core 2.0 CodeFirst、DbFirst、数据库迁移(Migration)介绍及示例

.NET Core 2.0迁移技巧之web.config配置文件

core MVC 过滤器之ExceptionFilter过滤器(一)

Core 使用Cookie验证身份

Core MVC – Tag Helpers 介绍

Core MVC – Caching Tag Helpers

Core MVC – Form Tag Helpers

Core MVC – 自定义 Tag Helpers

Core MVC – Tag Helper 组件

Core 运行原理解剖[1]:Hosting

Core 运行原理解剖[2]:Hosting补充之配置介绍

Core 运行原理解剖[3]:Middleware-请求管道的构成

原文地址:/RainingNight/p/httpcontext-in-asp-net-core.html

.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。