- 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的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
最近做一个项目,由于是在别人框架里开发app,导致了很多限制,其中一个就是不能直接引用webservice 。 我们都知道,调用webserivice 最简单的方法就是在 "引用"
这是SDL2代码的一部分 SDL主函数 int main(int argc,char *argv[]) { ... ... bool quit=false; S
c 中的函数: PHPAPI char *php_pcre_replace(char *regex, int regex_len, ch
我有以下映射: public class SecurityMap : ClassMap { public SecurityMap() {
我在vue-lic3中使用了SCSS,但是有一个奇怪的错误,使用/ deep /会报告错误,我不想看到它。 代码运行环境 vue-cli3 + vant + scss 的CSS /deep/ .van
我在深入阅读 C# 时遇到了这个我能理解的内容: 当它被限制为引用类型时,执行的比较类型完全取决于类型参数被限制为什么。 但是不能理解这个: 如果进一步限制派生自重载 == 和 != 运算符的特定类型
Closed. This question is opinion-based。它当前不接受答案。 想改善这个问题吗?更新问题,以便editing this post用事实和引用来回答。 3年前关闭。
有人可以详细介绍关于自赋值的运算符重载中的 *this 和 const 例如: Class& Class::operator=(const Class& other) { a = other.
在向树中插入新节点时,如何填充闭包表的深度/长度列? ancestor 和 descendant 中的值是来自另一个表的 ID,表示要以树结构排列的页面。 关闭表: ancestor desce
现在我正在阅读“深入了解 C#”。缺少的一件事是完成一章后我可以解决的一系列问题。那会帮助我理解我刚刚学到的概念。 哪里可以找到适合 C#3.0 的问题集? 谢谢 最佳答案 你可以试试LINQ 101
TypeScript 给 JavaScript 扩展了类型的语法,我们可以给变量加上类型,在编译期间会做类型检查,配合编辑器还能做更准确的智能提示。此外,TypeScript 还支持了高级类型用
是否有一个单行代码来获取生成器并生成该生成器中的所有元素?例如: def Yearly(year): yield YEARLY_HEADER for month in range(1, 13)
所以我阅读了一些与“什么是方法组”相关的 StackOverflow 问题以及其他互联网文章,它们在底线都说了同样的话——方法组是“一组重载方法” ". 但是,在阅读 Jon Skeet 的“C# 深
有什么方法可以从子组件中获取子组件吗? 想象一下以下组件树: 应用程序 问题 问题选项(包含复选框) 问题选项(包含复选框) 问题选项(包含复选框) 我想从 App 访问问题选项以选中所有复选框。 参
class_eval 和 instance_eval 在定义方法等情况下是完全可以预测的。我也理解类的实例和类的单例(又名特征类)之间的区别。 但是 我无法弄清楚以下唯一的事情:比方说,出于某些策略目
我想出了如何将符号 rwx 部分读取/转换为 421 个八进制部分,这非常简单。但是当涉及到特殊字符时,我感到非常困惑。我们知道 -r-xr---wx 转换为 0543,但 -r-sr---wt 或
我怀疑我系统的 Java 版本有问题。某些应用程序出现段错误或内存不足或存在链接错误。如果我从源代码安装了 JDK,我会做类似“make test”的事情,看看哪些测试失败了。但是,看起来从源代码构建
如何克隆一个 repo(使用 libgit2 ) 我想做什么git clone确实,但有 libgit2 .我可能要问的是什么 git clone确实很深入。 这是我目前正在做的: 初始化一个repo
00、头痛的JS闭包、词法作用域? 被JavaScript的闭包、上下文、嵌套函数、this搞得很头痛,这语言设计的,感觉比较混乱,先勉强理解总结一下😂😂😂.
我开始玩 lubridate R 中的包。我注意到 now(tzone="EST")计算为: [1] "2015-08-25 13:01:08 EST" 而 now(tzone="PST")导致警告:
我是一名优秀的程序员,十分优秀!