- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
.NET本身就是一个基于中间件(middleware)的框架,它通过一系列的中间件组件来处理HTTP请求和响应。在之前的文章 《.NET源码解读kestrel服务器及创建HttpContext对象流程》 中,已经通过源码介绍了如何将HTTP数据包转换为.NET的HttpContext对象。接下来,让我们深入了解一下.NET是如何设计中间件来处理HttpContext对象.
通过本文,您可以了解以下内容:
在介绍中间件之前,让我们先了解一下管道设计模式:
管道设计模式 是一种常见的软件设计模式,用于将一个复杂的任务或操作分解为一系列独立的处理步骤。每个步骤按 特定顺序 处理数据并传递给下一个步骤,形成 线性 的处理流程。每个步骤都是独立且可重用的组件.
在.NET中,针对每个HTTP请求的处理和响应任务被分解为可重用的类或匿名方法,这些组件被称为中间件。 中间件的连接顺序 是特定的,它们在一个管道中按顺序连接起来,形成一个处理流程。这种设计方式可以根据需求自由地添加、删除或重新排序中间件.
中间件的实现非常简单,它基于一个委托,接受一个HttpContext对象和一个回调函数(表示下一个中间件)作为参数。当请求到达时,委托执行自己的逻辑,并将请求传递给下一个中间件组件。这个过程会持续进行,直到最后一个中间件完成响应并将结果返回给客户端.
/*
* 入参1 string:代表HttpContext
* 入参2 Func<Task>:下一个中间件的方法
* 结果返回 Task:避免线程阻塞
* **/
Func<string, Func<Task>, Task> middleware = async (context, next) =>
{
Console.WriteLine($"Before middleware: {context}");
await next(); // 调用下一个中间件
Console.WriteLine($"After middleware: {context}");
};
Func<Task> finalMiddleware = () =>
{
// 最后一个中间件的逻辑
Console.WriteLine("Final middleware");
return Task.CompletedTask;
};
为了给所有的中间件和终端处理器提供统一的委托类型,使得它们在请求处理管道中可以无缝地连接起来。所以引入了RequestDelegate委托。上文中Func方法,最终都会转换成RequestDelegate委托,这一点放在下文源码解析中.
public delegate Task RequestDelegate(HttpContext context);
下面是从源码中提取出的一个简单的中间件管道构建器实现示例。它包含一个 _middlewares 列表,用于存储中间件委托,并提供了 Use 方法用于添加中间件,以及 Build 方法用于构建最终的请求处理委托.
这个实现示例虽然代码不多,但却能充分展示中间件的构建原理。你可以仔细阅读这段代码,深入理解中间件是如何构建和连接的.
public class MiddlewarePipeline
{
private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares =
new List<Func<RequestDelegate, RequestDelegate>>();
public void Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_middlewares.Add(middleware);
}
public RequestDelegate Build()
{
RequestDelegate next = context => Task.CompletedTask;
for (int i = _middlewares.Count - 1; i >= 0; i--)
{
next = _middlewares[i](next);
}
return next;
}
}
如果您想了解中间件中Run、Use、Map、MapWhen等方法,可以直接看 官方文档 。
该中间件通过查询字符串设置当前请求的区域性:
using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpsRedirection();
app.Use(async (context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline.
await next(context);
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});
app.Run();
以下代码将中间件委托移动到类: 该类必须具备:
using System.Globalization;
namespace Middleware.Example;
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline.
await _next(context);
}
}
// 封装扩展方法
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
该方法具体描述请看 官方文档 。
上文描述的自定义类,其实是按照约定来定义实现的。也可以根据IMiddlewareFactory/IMiddleware 中间件的扩展点来使用:
// 自定义中间件类实现 IMiddleware 接口
public class CustomMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
// 中间件逻辑
await next(context);
}
}
// 自定义中间件工厂类实现 IMiddlewareFactory 接口
public class CustomMiddlewareFactory : IMiddlewareFactory
{
public IMiddleware Create(IServiceProvider serviceProvider)
{
// 在这里可以进行一些初始化操作,如依赖注入等
return new CustomMiddleware();
}
}
// 在 Startup.cs 中使用中间件工厂模式添加中间件
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<CustomMiddlewareFactory>();
}
详细具体的自定义中间件方式请参阅 官方文档 。
以下是源代码的部分删减和修改,以便于更好地理解 。
为了更好地理解中间件的创建和执行在整个框架中的位置,我们仍然从 Program 开始。在 Program 中使用 CreateBuilder 方法创建一个默认的主机构建器,配置应用程序的默认设置,并注入基础服务.
// 在Program.cs文件中调用
var builder = WebApplication.CreateBuilder(args);
CreateBuilder方法返回了WebApplicationBuilder实例 。
public static WebApplicationBuilder CreateBuilder(string[] args) =>
new WebApplicationBuilder(new WebApplicationOptions(){ Args = args });
在 WebApplicationBuilder 的构造函数中,将配置并注册中间件 。
internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilder>? configureDefaults = null)
{
// 创建BootstrapHostBuilder实例
var bootstrapHostBuilder = new BootstrapHostBuilder(_hostApplicationBuilder);
// bootstrapHostBuilder 上调用 ConfigureWebHostDefaults 方法,以进行特定于 Web 主机的配置
bootstrapHostBuilder.ConfigureWebHostDefaults(webHostBuilder =>
{
// 配置应用程序包含了中间件的注册过程和一系列的配置
webHostBuilder.Configure(ConfigureApplication);
});
var webHostContext = (WebHostBuilderContext)bootstrapHostBuilder.Properties[typeof(WebHostBuilderContext)];
Environment = webHostContext.HostingEnvironment;
Host = new ConfigureHostBuilder(bootstrapHostBuilder.Context, Configuration, Services);
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
}
ConfigureApplication 方法是用于配置应用程序的核心方法。其中包含了中间件的注册过程。本篇文章只关注中间件,路由相关的内容会在下一篇文章进行详细解释.
private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app)
{
Debug.Assert(_builtApplication is not null);
// 在 WebApplication 之前调用 UseRouting,例如在 StartupFilter 中,
// 我们需要移除该属性并在最后重新设置,以免影响过滤器中的路由
if (app.Properties.TryGetValue(EndpointRouteBuilderKey, out var priorRouteBuilder))
{
app.Properties.Remove(EndpointRouteBuilderKey);
}
// ...
// 将源管道连接到目标管道
var wireSourcePipeline = new WireSourcePipeline(_builtApplication);
app.Use(wireSourcePipeline.CreateMiddleware);
// ..
// 将属性复制到目标应用程序构建器
foreach (var item in _builtApplication.Properties)
{
app.Properties[item.Key] = item.Value;
}
// 移除路由构建器以清理属性,我们已经完成了将路由添加到管道的操作
app.Properties.Remove(WebApplication.GlobalEndpointRouteBuilderKey);
// 如果之前存在路由构建器,则重置它,这对于 StartupFilters 是必要的
if (priorRouteBuilder is not null)
{
app.Properties[EndpointRouteBuilderKey] = priorRouteBuilder;
}
}
通过新构建的RequestDelegate委托处理请求,在目标中间件管道中连接源中间件管道 。
private sealed class WireSourcePipeline(IApplicationBuilder builtApplication)
{
private readonly IApplicationBuilder _builtApplication = builtApplication;
public RequestDelegate CreateMiddleware(RequestDelegate next)
{
_builtApplication.Run(next);
return _builtApplication.Build();
}
}
从Program中app.Run()开始,启动主机,最终会调用IHost的StartAsync方法.
// Program调用Run
app.Run();
// 实现Run();
public void Run([StringSyntax(StringSyntaxAttribute.Uri)] string? url = null)
{
Listen(url);
HostingAbstractionsHostExtensions.Run(this);
}
// 实现HostingAbstractionsHostExtensions.Run(this);
public static async Task RunAsync(this IHost host, CancellationToken token = default)
{
try
{
await host.StartAsync(token).ConfigureAwait(false);
await host.WaitForShutdownAsync(token).ConfigureAwait(false);
}
finally
{
if (host is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
}
else
{
host.Dispose();
}
}
}
将中间件和StartupFilters扩展传入HostingApplication主机,并进行启动 。
public async Task StartAsync(CancellationToken cancellationToken)
{
// ...省略了从配置中获取服务器监听地址和端口...
// 通过配置构建中间件管道
RequestDelegate? application = null;
try
{
IApplicationBuilder builder = ApplicationBuilderFactory.CreateBuilder(Server.Features);
foreach (var filter in StartupFilters.Reverse())
{
configure = filter.Configure(configure);
}
configure(builder);
// Build the request pipeline
application = builder.Build();
}
catch (Exception ex)
{
Logger.ApplicationError(ex);
}
/*
* application:中间件
* DiagnosticListener:事件监听器
* HttpContextFactory:HttpContext对象的工厂
*/
HostingApplication httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, Propagator, HttpContextFactory, HostingEventSource.Log, HostingMetrics);
await Server.StartAsync(httpApplication, cancellationToken);
}
IApplicationBuilder 提供配置应用程序请求管道的机制,Build方法生成此应用程序用于处理HTTP请求的委托.
public RequestDelegate Build()
{
// 构建一个 RequestDelegate 委托,代表请求的处理逻辑
RequestDelegate app = context =>
{
var endpoint = context.GetEndpoint();
var endpointRequestDelegate = endpoint?.RequestDelegate;
if (endpointRequestDelegate != null)
{
throw new InvalidOperationException(message);
}
return Task.CompletedTask;
};
// 逐步构建了包含所有中间件的管道
for (var c = _components.Count - 1; c >= 0; c--)
{
app = _components[c](app);
}
return app;
}
这里对IApplicationBuilder做个整体了解,然后再回归上文流程.
IApplicationBuilder的作用是提供了配置应用程序请求管道的机制。它定义了一组方法和属性,用于构建和配置应用程序的中间件管道,处理传入的 HTTP 请求.
public partial class ApplicationBuilder : IApplicationBuilder
{
private readonly List<Func<RequestDelegate, RequestDelegate>> _components = new();
private readonly List<string>? _descriptions;
/// <summary>
/// Adds the middleware to the application request pipeline.
/// </summary>
/// <param name="middleware">The middleware.</param>
/// <returns>An instance of <see cref="IApplicationBuilder"/> after the operation has completed.</returns>
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
_descriptions?.Add(CreateMiddlewareDescription(middleware));
return this;
}
private static string CreateMiddlewareDescription(Func<RequestDelegate, RequestDelegate> middleware)
{
if (middleware.Target != null)
{
// To IApplicationBuilder, middleware is just a func. Getting a good description is hard.
// Inspect the incoming func and attempt to resolve it back to a middleware type if possible.
// UseMiddlewareExtensions adds middleware via a method with the name CreateMiddleware.
// If this pattern is matched, then ToString on the target returns the middleware type name.
if (middleware.Method.Name == "CreateMiddleware")
{
return middleware.Target.ToString()!;
}
return middleware.Target.GetType().FullName + "." + middleware.Method.Name;
}
return middleware.Method.Name.ToString();
}
/// <summary>
/// Produces a <see cref="RequestDelegate"/> that executes added middlewares.
/// </summary>
/// <returns>The <see cref="RequestDelegate"/>.</returns>
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
// If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
// This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
var endpoint = context.GetEndpoint();
var endpointRequestDelegate = endpoint?.RequestDelegate;
if (endpointRequestDelegate != null)
{
var message =
$"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +
$"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
$"routing.";
throw new InvalidOperationException(message);
}
// Flushing the response and calling through to the next middleware in the pipeline is
// a user error, but don't attempt to set the status code if this happens. It leads to a confusing
// behavior where the client response looks fine, but the server side logic results in an exception.
if (!context.Response.HasStarted)
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
}
// Communicates to higher layers that the request wasn't handled by the app pipeline.
context.Items[RequestUnhandledKey] = true;
return Task.CompletedTask;
};
for (var c = _components.Count - 1; c >= 0; c--)
{
app = _components[c](app);
}
return app;
}
}
回归上文流程,将生成的管道传入HostingApplication中,并在处理Http请求时,进行执行.
// Execute the request
public Task ProcessRequestAsync(Context context)
{
return _application(context.HttpContext!);
}
还是不清楚执行位置的同学,可以翻阅 《.NET源码解读kestrel服务器及创建HttpContext对象流程》 文章中的这块代码来进行了解.
.NET 中间件就是基于管道模式和委托来进行实现。每个中间件都是一个委托方法,接受一个 HttpContext 对象和一个 RequestDelegate 委托作为参数,可以对请求进行修改、添加额外的处理逻辑,然后调用 RequestDelegate 来将请求传递给下一个中间件或终止请求处理.
如果您觉得这篇文章有所收获,还请点个赞并关注。如果您有宝贵建议,欢迎在评论区留言,非常感谢您的支持! 。
(也可以关注我的公众号噢:Broder,万分感谢_) 。
最后此篇关于【.NET源码解读】深入剖析中间件的设计与实现的文章就讲到这里了,如果你想了解更多关于【.NET源码解读】深入剖析中间件的设计与实现的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我的 Django 应用程序在生产过程中变得非常缓慢。可能是由于某些复杂或未索引的查询。 是否有任何类似 django 的方法来分析我的应用程序? 最佳答案 试试 Django Debug Toolb
我正在使用GDownloadUrl将客户端数据发送到服务器。这里是我使用的sode GDownloadUrl( dwnld_url, function(data) {
我一直在尝试开始分析我的 CherryPy 网络服务器,但文档缺乏关于如何设置它的详细信息。我知道我应该能够使用 cherrypy.lib.profiler 作为中间件来安装我的初始服务器。现在,我有
是否有任何 HashMap 实现公开了用于分析 Map 性能的钩子(Hook)方法(平均链长度、最佳/最差/平均访问时间、#rehashes 等)。 在 ~O(1) 访问时间方面使用 HashMap
我想知道是否有可用的技术或工具可以告诉您执行特定方法需要多少时间。 类似于数学/计算机科学中的大 O 符号,可以让您了解算法的复杂性,我想知道代码分析是否有类似的东西。 最佳答案 Profiling是
前面,我们说Ruby没有函数,只有方法.而且实际上有不止一种方法.这一节我们介绍访问控制(accesscontrols). 想想当我们在"最高层"而不是在一个类的定义里定义一个
我有一个用 Visual Basic 编写的大型应用程序6,我需要分析它。有没有类似 ANTS Profiler 的东西但对于 COM应用程序? 最佳答案 我以前用 VBWatch .我对它有很好的体
我做了两个实现来解决 Shikaku 难题。一个使用顶部、左侧、宽度和高度 (TLWH) 作为每个矩形的参数,另一个使用顶部、左侧、底部、右侧 (TLBR)。 出于某种原因,使用 TLBR 的速度要快
如果重要的话,我正在使用 Very Sleepy CS 进行分析。 IPP(Intel's Integrated Performance Primitives)在我的项目中使用,基本上是单次调用: i
我想弄明白这个排列算法是如何工作的: def perm(n, i): if i == len(n) - 1: print n else: for j i
如果 C++ 项目的物理结构有利于编译速度,您将如何获得某种客观的衡量标准?有些人说你应该避免使用模板以获得更好的编译速度,但是如果模板生成大量非冗余目标代码,即使行/秒编译速度不是很好,那也不是很糟
摘自 Robert Sedgewick 和 Kevin Wayne 算法第 4 版 在递归部分基本情况代码是 if(end start) { mid = (start+end
有没有一种简单的方法可以计算一段标准 C 代码实际执行的乘法次数?我想到的代码基本上只是做加法和乘法,主要兴趣是乘法,但也可以计算其他操作的数量。 如果这是一个选项,我想我可以四处用“multiply
我正在编写一个 Netty 应用程序。该应用程序运行在 64 位八核 linux 机器上 Netty 应用程序是一个简单的路由器,它接受请求(传入管道),从请求中读取一些元数据并将数据转发到远程服务(
我希望能得到一些帮助来弄清楚这个异常消息到底对我说了什么。我能够使用调试器将问题缩小到代码中的特定行。但是,我认为更多信息可以更好地找出代码中的实际问题。 public static List
我有一个存储在 USB 拇指驱动器上的 mysql 数据库,该数据库已不可挽回地丢失了其文件分配表。因此,我无法访问整个 ibdata1 文件。不过,我可以找到使用十六进制编辑器使用的记录页面。 所有
我正在使用 jQuery 在单击时显示图像。通过将图像源存储到像这样的变量中,这可以很好地工作.. var theSrc = $(event.target).attr('src'); 然后我可以将这张
我是 R 的新手,但不是 C 的新手。我想看看是否可以为 friend 加速一个包。通常使用 C 我会编译一个设置了 -pg 标志的应用程序,然后将 gmon.out 文件传递给 gprof。 我
在分析我的代码以找出运行缓慢的地方时,我有 3 个功能显然会永远占用,这就是非常困的说法。 这些函数是: ZwDelayExecution 20.460813 20.460813 19.
我正在分析一个应用程序并注意到 52% (195MB) 的内存被 char[] 使用,20% 被 String 使用。这是一个有很多依赖项的大型项目,我刚刚看到它,所以我有几个相关的问题可以帮助我开始
我是一名优秀的程序员,十分优秀!