前言:
此时姐妹们对“net core 请求管道”可能比较关注,同学们都需要剖析一些“net core 请求管道”的相关文章。那么小编同时在网上收集了一些对于“net core 请求管道””的相关文章,希望我们能喜欢,各位老铁们快快来了解一下吧!Intro#
之前看到过蒋金楠老师的一篇 200 行代码带你了解 asp.net core 框架,最近参考蒋老师和 Edison 的文章和代码,结合自己对 asp.net core 的理解 ,最近自己写了一个 MiniAspNetCore ,写篇文章总结一下。
HttpContext#
HttpContext 可能是最为常用的一个类了,HttpContext 是请求上下文,包含了所有的请求信息以及响应信息,以及一些自定义的用于在不同中间件中传输数据的信息
来看一下 HttpContext 的定义:
Copypublic class HttpContext{ public IServiceProvider RequestServices { get; set; } public HttpRequest Request { get; set; } public HttpResponse Response { get; set; } public IFeatureCollection Features { get; set; } public HttpContext(IFeatureCollection featureCollection) { Features = featureCollection; Request = new HttpRequest(featureCollection); Response = new HttpResponse(featureCollection); }}
HttpRequest 即为请求信息对象,包含了所有请求相关的信息,
HttpResponse 为响应信息对象,包含了请求对应的响应信息
RequestServices 为 asp.net core 里的RequestServices,代表当前请求的服务提供者,可以使用它来获取具体的服务实例
Features 为 asp.net core 里引入的对象,可以用来在不同中间件中传递信息和用来解耦合
,下面我们就来看下 HttpRequest 和 HttpResponse 是怎么实现的
HttpRequest:
Copypublic class HttpRequest{ private readonly IRequestFeature _requestFeature; public HttpRequest(IFeatureCollection featureCollection) { _requestFeature = featureCollection.Get<IRequestFeature>(); } public Uri Url => _requestFeature.Url; public NameValueCollection Headers => _requestFeature.Headers; public string Method => _requestFeature.Method; public string Host => _requestFeature.Url.Host; public Stream Body => _requestFeature.Body;}
HttpResponse:
Copypublic class HttpResponse{ private readonly IResponseFeature _responseFeature; public HttpResponse(IFeatureCollection featureCollection) { _responseFeature = featureCollection.Get<IResponseFeature>(); } public bool ResponseStarted => _responseFeature.Body.Length > 0; public int StatusCode { get => _responseFeature.StatusCode; set => _responseFeature.StatusCode = value; } public async Task WriteAsync(byte[] responseBytes) { if (_responseFeature.StatusCode <= 0) { _responseFeature.StatusCode = 200; } if (responseBytes != null && responseBytes.Length > 0) { await _responseFeature.Body.WriteAsync(responseBytes); } }}Features#
上面我们提到我们可以使用 Features 在不同中间件中传递信息和解耦合
由上面 HttpRequest/HttpResponse 的代码我们可以看出来,HttpRequest 和 HttpResponse 其实就是在 IRequestFeature 和 IResponseFeature 的基础上封装了一层,真正的核心其实是 IRequestFeature/IResponseFeature ,而这里使用接口就很好的实现了解耦,可以根据不同的 WebServer 使用不同的 RequestFeature/ResponseFeature,来看下 IRequestFeature/IResponseFeature 的实现
Copypublic interface IRequestFeature{ Uri Url { get; } string Method { get; } NameValueCollection Headers { get; } Stream Body { get; }}public interface IResponseFeature{ public int StatusCode { get; set; } NameValueCollection Headers { get; set; } public Stream Body { get; }}
这里的实现和 asp.net core 的实际的实现方式应该不同,asp.net core 里 Headers 同一个 Header 允许有多个值,asp.net core 里是 StringValues 来实现的,这里简单处理了,使用了一个 NameValueCollection 对象
上面提到的 Features 是一个 IFeatureCollection 对象,相当于是一系列的 Feature 对象组成的,来看下 FeatureCollection 的定义:
Copypublic interface IFeatureCollection : IDictionary<Type, object> { }public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection{}
这里 IFeatureCollection 直接实现 IDictionary<Type, object> ,通过一个字典 Feature 类型为 Key,Feature 对象为 Value 的字典来保存
为了方便使用,可以定义两个扩展方法来方便的Get/Set
Copypublic static class FeatureExtensions{ public static IFeatureCollection Set<TFeature>(this IFeatureCollection featureCollection, TFeature feature) { featureCollection[typeof(TFeature)] = feature; return featureCollection; } public static TFeature Get<TFeature>(this IFeatureCollection featureCollection) { var featureType = typeof(TFeature); return featureCollection.ContainsKey(featureType) ? (TFeature)featureCollection[featureType] : default(TFeature); }}Web服务器#
上面我们已经提到了 Web 服务器通过 IRequestFeature/IResponseFeature 来实现不同 web 服务器和应用程序的解耦,web 服务器只需要提供自己的 RequestFeature/ResponseFeature 即可
为了抽象不同的 Web 服务器,我们需要定义一个 IServer 的抽象接口,定义如下:
Copypublic interface IServer{ Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default);}
IServer 定义了一个 StartAsync 方法,用来启动 Web服务器,
StartAsync 方法有两个参数,一个是 requestHandler,是一个用来处理请求的委托,另一个是取消令牌用来停止 web 服务器
示例使用了 HttpListener 来实现了一个简单 Web 服务器,HttpListenerServer 定义如下:
Copypublic class HttpListenerServer : IServer{ private readonly HttpListener _listener; private readonly IServiceProvider _serviceProvider; public HttpListenerServer(IServiceProvider serviceProvider, IConfiguration configuration) { _listener = new HttpListener(); var urls = configuration.GetAppSetting("ASPNETCORE_URLS")?.Split(';'); if (urls != null && urls.Length > 0) { foreach (var url in urls .Where(u => u.IsNotNullOrEmpty()) .Select(u => u.Trim()) .Distinct() ) { // Prefixes must end in a forward slash ("/") // _listener.Prefixes.Add(url.EndsWith("/") ? url : $"{url}/"); } } else { _listener.Prefixes.Add(";); } _serviceProvider = serviceProvider; } public async Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default) { _listener.Start(); if (_listener.IsListening) { Console.WriteLine("the server is listening on "); Console.WriteLine(_listener.Prefixes.StringJoin(",")); } while (!cancellationToken.IsCancellationRequested) { var listenerContext = await _listener.GetContextAsync(); var featureCollection = new FeatureCollection(); featureCollection.Set(listenerContext.GetRequestFeature()); featureCollection.Set(listenerContext.GetResponseFeature()); using (var scope = _serviceProvider.CreateScope()) { var httpContext = new HttpContext(featureCollection) { RequestServices = scope.ServiceProvider, }; await requestHandler(httpContext); } listenerContext.Response.Close(); } _listener.Stop(); }}
HttpListenerServer 实现的 RequestFeature/ResponseFeatue
Copypublic class HttpListenerRequestFeature : IRequestFeature{ private readonly HttpListenerRequest _request; public HttpListenerRequestFeature(HttpListenerContext listenerContext) { _request = listenerContext.Request; } public Uri Url => _request.Url; public string Method => _request.HttpMethod; public NameValueCollection Headers => _request.Headers; public Stream Body => _request.InputStream;}public class HttpListenerResponseFeature : IResponseFeature{ private readonly HttpListenerResponse _response; public HttpListenerResponseFeature(HttpListenerContext httpListenerContext) { _response = httpListenerContext.Response; } public int StatusCode { get => _response.StatusCode; set => _response.StatusCode = value; } public NameValueCollection Headers { get => _response.Headers; set { _response.Headers = new WebHeaderCollection(); foreach (var key in value.AllKeys) _response.Headers.Add(key, value[key]); } } public Stream Body => _response.OutputStream;}
为了方便使用,为 HttpListenerContext 定义了两个扩展方法,就是上面 HttpListenerServer 中的 GetRequestFeature/GetResponseFeature:
Copypublic static class HttpListenerContextExtensions{ public static IRequestFeature GetRequestFeature(this HttpListenerContext context) { return new HttpListenerRequestFeature(context); } public static IResponseFeature GetResponseFeature(this HttpListenerContext context) { return new HttpListenerResponseFeature(context); }}RequestDelegate#
在上面的 IServer 定义里有一个 requestHandler 的 对象,在 asp.net core 里是一个名称为 RequestDelegate 的对象,而用来构建这个委托的在 asp.net core 里是 IApplicationBuilder,这些在蒋老师和 Edison 的文章和代码里都可以看到,这里我们只是简单介绍下,我在 MiniAspNetCore 的示例中没有使用这些对象,而是使用了自己抽象的 PipelineBuilder 和原始委托实现的
asp.net core 里 RequestDelegate 定义:
Copypublic delegate Task RequestDelegate(HttpContext context);
其实和我们上面定义用的 Func<HttpContext, Task> 是等价的
IApplicationBuilder 定义:
Copy/// <summary>/// Defines a class that provides the mechanisms to configure an application's request pipeline./// </summary>public interface IApplicationBuilder{ /// <summary> /// Gets or sets the <see cref="T:System.IServiceProvider" /> that provides access to the application's service container. /// </summary> IServiceProvider ApplicationServices { get; set; } /// <summary> /// Gets the set of HTTP features the application's server provides. /// </summary> IFeatureCollection ServerFeatures { get; } /// <summary> /// Gets a key/value collection that can be used to share data between middleware. /// </summary> IDictionary<string, object> Properties { get; } /// <summary> /// Adds a middleware delegate to the application's request pipeline. /// </summary> /// <param name="middleware">The middleware delegate.</param> /// <returns>The <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns> IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); /// <summary> /// Creates a new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" /> that shares the <see cref="P:Microsoft.AspNetCore.Builder.IApplicationBuilder.Properties" /> of this /// <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />. /// </summary> /// <returns>The new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns> IApplicationBuilder New(); /// <summary> /// Builds the delegate used by this application to process HTTP requests. /// </summary> /// <returns>The request handling delegate.</returns> RequestDelegate Build();}
我们这里没有定义 IApplicationBuilder,使用了简化抽象的 IAsyncPipelineBuilder,定义如下:
Copypublic interface IAsyncPipelineBuilder<TContext>{ IAsyncPipelineBuilder<TContext> Use(Func<Func<TContext, Task>, Func<TContext, Task>> middleware); Func<TContext, Task> Build(); IAsyncPipelineBuilder<TContext> New();}
对于 asp.net core 的中间件来说 ,上面的 TContext 就是 HttpContext,替换之后也就是下面这样的:
Copypublic interface IAsyncPipelineBuilder<HttpContext>{ IAsyncPipelineBuilder<HttpContext> Use(Func<Func<HttpContext, Task>, Func<HttpContext, Task>> middleware); Func<HttpContext, Task> Build(); IAsyncPipelineBuilder<HttpContext> New();}
是不是和 IApplicationBuilder 很像,如果不像可以进一步把 Func<HttpContext, Task> 使用 RequestDelegate 替换
Copypublic interface IAsyncPipelineBuilder<HttpContext>{ IAsyncPipelineBuilder<HttpContext> Use(Func<RequestDelegate, RequestDelegate> middleware); RequestDelegate Build(); IAsyncPipelineBuilder<HttpContext> New();}
最后再将接口名称替换一下:
Copypublic interface IApplicationBuilder1{ IApplicationBuilder1 Use(Func<RequestDelegate, RequestDelegate> middleware); RequestDelegate Build(); IApplicationBuilder1 New();}
至此,就完全可以看出来了,这 IAsyncPipelineBuilder<HttpContext> 就是一个简版的 IApplicationBuilder
IAsyncPipelineBuilder 和 IApplicationBuilder 的作用是将注册的多个中间件构建成一个请求处理的委托
中间件处理流程:
更多关于 PipelineBuilder 构建中间件的信息可以查看 让 .NET 轻松构建中间件模式代码 了解更多
WebHost#
通过除了 Web 服务器之外,还有一个 Web Host 的概念,可以简单的这样理解,一个 Web 服务器上可以有多个 Web Host,就像 IIS/nginx (Web Server) 可以 host 多个站点
可以说 WebHost 离我们的应用更近,所以我们还需要 IHost 来托管应用
Copypublic interface IHost{ Task RunAsync(CancellationToken cancellationToken = default);}
WebHost 定义:
Copypublic class WebHost : IHost{ private readonly Func<HttpContext, Task> _requestDelegate; private readonly IServer _server; public WebHost(IServiceProvider serviceProvider, Func<HttpContext, Task> requestDelegate) { _requestDelegate = requestDelegate; _server = serviceProvider.GetRequiredService<IServer>(); } public async Task RunAsync(CancellationToken cancellationToken = default) { await _server.StartAsync(_requestDelegate, cancellationToken).ConfigureAwait(false); }}
为了方便的构建 Host对象,引入了 HostBuilder 来方便的构建一个 Host,定义如下:
Copypublic interface IHostBuilder{ IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction); IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction); IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction); IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction); IHost Build();}
WebHostBuilder:
Copypublic class WebHostBuilder : IHostBuilder{ private readonly IConfigurationBuilder _configurationBuilder = new ConfigurationBuilder(); private readonly IServiceCollection _serviceCollection = new ServiceCollection(); private Action<IConfiguration, IServiceProvider> _initAction = null; private readonly IAsyncPipelineBuilder<HttpContext> _requestPipeline = PipelineBuilder.CreateAsync<HttpContext>(context => { context.Response.StatusCode = 404; return Task.CompletedTask; }); public IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction) { configAction?.Invoke(_configurationBuilder); return this; } public IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction) { if (null != configureAction) { var configuration = _configurationBuilder.Build(); configureAction.Invoke(configuration, _serviceCollection); } return this; } public IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction) { if (null != configureAction) { var configuration = _configurationBuilder.Build(); configureAction.Invoke(configuration, _requestPipeline); } return this; } public IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction) { if (null != initAction) { _initAction = initAction; } return this; } public IHost Build() { var configuration = _configurationBuilder.Build(); _serviceCollection.AddSingleton<IConfiguration>(configuration); var serviceProvider = _serviceCollection.BuildServiceProvider(); _initAction?.Invoke(configuration, serviceProvider); return new WebHost(serviceProvider, _requestPipeline.Build()); } public static WebHostBuilder CreateDefault(string[] args) { var webHostBuilder = new WebHostBuilder(); webHostBuilder .ConfigureConfiguration(builder => builder.AddJsonFile("appsettings.json", true, true)) .UseHttpListenerServer() ; return webHostBuilder; }}
这里的示例我在 IHostBuilder 里增加了一个 Initialize 的方法来做一些初始化的操作,我觉得有些数据初始化配置初始化等操作应该在这里操作,而不应该在 Startup 的 Configure 方法里处理,这样 Configure 方法可以更纯粹一些,只配置 asp.net core 的请求管道,这纯属个人意见,没有对错之分
这里 Host 的实现和 asp.net core 的实现不同,有需要的可以深究源码,在 asp.net core 2.x 的版本里是有一个 IWebHost 的,在 asp.net core 3.x 以及 .net 5 里是没有 IWebHost 的取而代之的是通用主机 IHost, 通过实现了一个 IHostedService 来实现 WebHost 的
Run#
运行示例代码:
Copypublic class Program{ private static readonly CancellationTokenSource Cts = new CancellationTokenSource(); public static async Task Main(string[] args) { Console.CancelKeyPress += OnExit; var host = WebHostBuilder.CreateDefault(args) .ConfigureServices((configuration, services) => { }) .ConfigureApplication((configuration, app) => { app.When(context => context.Request.Url.PathAndQuery.StartsWith("/favicon.ico"), pipeline => { }); app.When(context => context.Request.Url.PathAndQuery.Contains("test"), p => { p.Run(context => context.Response.WriteAsync("test")); }); app .Use(async (context, next) => { await context.Response.WriteLineAsync($"middleware1, requestPath:{context.Request.Url.AbsolutePath}"); await next(); }) .Use(async (context, next) => { await context.Response.WriteLineAsync($"middleware2, requestPath:{context.Request.Url.AbsolutePath}"); await next(); }) .Use(async (context, next) => { await context.Response.WriteLineAsync($"middleware3, requestPath:{context.Request.Url.AbsolutePath}"); await next(); }) ; app.Run(context => context.Response.WriteAsync("Hello Mini Asp.Net Core")); }) .Initialize((configuration, services) => { }) .Build(); await host.RunAsync(Cts.Token); } private static void OnExit(object sender, EventArgs e) { Console.WriteLine("exiting ..."); Cts.Cancel(); }}
在示例项目目录下执行 dotnet run,并访问 :
仔细观察浏览器 console 或 network 的话,会发现还有一个请求,浏览器会默认请求 /favicon.ico 获取网站的图标
因为我们针对这个请求没有任何中间件的处理,所以直接返回了 404
在访问 /test,可以看到和刚才的输出完全不同,因为这个请求走了另外一个分支,相当于 asp.net core 里 Map/MapWhen 的效果,另外 Run 代表里中间件的中断,不会执行后续的中间件
标签: #net core 请求管道