重新整理 .net core 實踐篇—————中介軟體[十九]

不問前世發表於2021-06-14

前言

簡單介紹一下.net core的中介軟體。

正文

官方文件已經給出了中介軟體的概念圖:

和其密切相關的是下面這兩個東西:

IApplicationBuilder 和 RequestDelegate(HttpContext context)

IApplicationBuilder :

public interface IApplicationBuilder
{
IServiceProvider ApplicationServices { get; set; }

IFeatureCollection ServerFeatures { get; }

IDictionary<string, object> Properties { get; }

IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

IApplicationBuilder New();

RequestDelegate Build();
}

RequestDelegate:

namespace Microsoft.AspNetCore.Http
{
  public delegate Task RequestDelegate(HttpContext context);
}

舉一個 中介軟體的例子:

app.Use(async (context, next) => {
                await context.Response.WriteAsync("hello word");
});

效果:

這裡我沒有執行next,故而在這裡就終止了。

來看下這個Use,幹了什麼:

public static class UseExtensions
{
public static IApplicationBuilder Use(
  this IApplicationBuilder app,
  Func<HttpContext, Func<Task>, Task> middleware)
{
  return app.Use((Func<RequestDelegate, RequestDelegate>) (next => (RequestDelegate) (context =>
  {
	Func<Task> func = (Func<Task>) (() => next(context));
	return middleware(context, func);
  })));
}
}

是的,他是對IApplicationBuilder 的一個擴充套件。

如果不想使用這個擴充套件方法,那麼你要這麼寫:

app.Use((Func<RequestDelegate, RequestDelegate>)  (next=> (RequestDelegate)((context)=>
{
	Func<Task> func = (Func<Task>)(() => next(context));
	Func<HttpContext, Func<Task>, Task> middleware = async (context1, next2) =>
	{
		await context1.Response.WriteAsync("hello word");
	};
	return middleware(context, func);
})));

有些人可能看不慣這樣寫哈,換一種寫法:

app.Use((Func<RequestDelegate, RequestDelegate>)  (next =>
{
	return (RequestDelegate)((context) =>
	{
		Func<Task> func = (Func<Task>)(() => next(context));
		Func<HttpContext, Func<Task>, Task> middleware = async (context1, next2) =>
		{
			await context1.Response.WriteAsync("hello word");
		};
		return middleware(context, func);
	});
}));

又或者,這樣寫:

app.Use((Func<RequestDelegate, RequestDelegate>)  (next =>
{
	return (RequestDelegate)((context) =>
	{
		Func<HttpContext, RequestDelegate, Task> middleware = async (context1, next2) =>
		{
			await context1.Response.WriteAsync("hello word");
		};
		return middleware(context, next);
	});
}));

還可以這樣寫:

public async Task WriteAsync(HttpContext context, RequestDelegate requestDelegate)
{
	await context.Response.WriteAsync("hello word");
}
app.Use((Func<RequestDelegate, RequestDelegate>)  (next =>
            {
                return (RequestDelegate)((context) => WriteAsync(context, next));
            }));

上面沒有用到這個next,那麼這個next是幹什麼的呢?從上面的傳參推斷出,就是我們的下一步。

如果沒有執行下一步,那麼下一步是不會執行的。

來看一下IApplicationBuilder的實現類ApplicationBuilder的use方法:

private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = (IList<Func<RequestDelegate, RequestDelegate>>) new List<Func<RequestDelegate, RequestDelegate>>();
public IApplicationBuilder Use(
  Func<RequestDelegate, RequestDelegate> middleware)
{
  this._components.Add(middleware);
  return (IApplicationBuilder) this;
}

會將我們傳入的middleware,加入到_components 中。

ApplicationBuilder看下build 方法:

public RequestDelegate Build()
{
  RequestDelegate requestDelegate = (RequestDelegate) (context =>
  {
	Endpoint endpoint = context.GetEndpoint();
	if (endpoint?.RequestDelegate != null)
	  throw new InvalidOperationException("The request reached the end of the pipeline without executing the endpoint: '" + endpoint.DisplayName + "'. Please register the EndpointMiddleware using 'IApplicationBuilder.UseEndpoints(...)' if using routing.");
	context.Response.StatusCode = 404;
	return Task.CompletedTask;
  });
  foreach (Func<RequestDelegate, RequestDelegate> func in this._components.Reverse<Func<RequestDelegate, RequestDelegate>>())
	requestDelegate = func(requestDelegate);
  return requestDelegate;
}

這個就是套娃工程,把後面一個的requestDelegate,作為前面一個requestDelegate的引數。最後返回第一個requestDelegate。

斷點驗證,我打了兩個斷點,下面是斷點的順序。

第一個斷點停留的位置:

第二個斷點停留的位置:

第二個斷點裡面的next就是第一個斷點返回的結果。

因為返回的是第一個中介軟體的返回的RequestDelegate,那麼執行。

那麼執行順序就是第一個返回的RequestDelegate開始執行,且引數是第二個中介軟體返回的RequestDelegate。

返回的RequestDelegate執行順序如下:

這大概就是中介軟體的原理了。

下面看一下動態中介軟體:

app.Map("/abc", builder =>
{
	app.Use((Func<RequestDelegate, RequestDelegate>)(next =>
	{
		
		return (RequestDelegate)((context) => WriteAsync(context, next));
	}));
});

如上面這樣,如果匹配到了/abc,那麼就走裡面的中介軟體。

看下原始碼吧,Map的。

public static class MapExtensions
{
public static IApplicationBuilder Map(
  this IApplicationBuilder app,
  PathString pathMatch,
  Action<IApplicationBuilder> configuration)
{
  if (app == null)
	throw new ArgumentNullException(nameof (app));
  if (configuration == null)
	throw new ArgumentNullException(nameof (configuration));
  if (pathMatch.HasValue && pathMatch.Value.EndsWith("/", StringComparison.Ordinal))
	throw new ArgumentException("The path must not end with a '/'", nameof (pathMatch));
  IApplicationBuilder applicationBuilder = app.New();
  configuration(applicationBuilder);
  RequestDelegate requestDelegate = applicationBuilder.Build();
  MapOptions options = new MapOptions()
  {
	Branch = requestDelegate,
	PathMatch = pathMatch
  };
  return app.Use((Func<RequestDelegate, RequestDelegate>) (next => new RequestDelegate(new MapMiddleware(next, options).Invoke)));
}
}

裡面做的主要是兩件事,一件事是另外 app.New();弄出一條分支出來。然後呼叫Build()獨立走出一條新的中介軟體鏈。

New方法如下:

public IApplicationBuilder New()
{
  return (IApplicationBuilder) new ApplicationBuilder(this);
}

第二件事就是返回了一個新的中介軟體RequestDelegate,傳入了兩個引數一個是next,這個是用來走老的分支,估摸著不匹配的時候走舊的分支。

還有一個引數是options,這個引數有兩個屬性,一個是Branch 就是新的分支。一個是PathMatch 是匹配字元,那麼就是如果是匹配的話,就走新的分支。

事實證明果然如此:

public class MapMiddleware
{
private readonly RequestDelegate _next;
private readonly MapOptions _options;

public MapMiddleware(RequestDelegate next, MapOptions options)
{
  if (next == null)
	throw new ArgumentNullException(nameof (next));
  if (options == null)
	throw new ArgumentNullException(nameof (options));
  this._next = next;
  this._options = options;
}

public async Task Invoke(HttpContext context)
{
  if (context == null)
	throw new ArgumentNullException(nameof (context));
  PathString matched;
  PathString remaining;
  if (context.Request.Path.StartsWithSegments(this._options.PathMatch, out matched, out remaining))
  {
	PathString path = context.Request.Path;
	PathString pathBase = context.Request.PathBase;
	context.Request.PathBase = pathBase.Add(matched);
	context.Request.Path = remaining;
	try
	{
	  await this._options.Branch(context);
	}
	finally
	{
	  context.Request.PathBase = pathBase;
	  context.Request.Path = path;
	}
	path = new PathString();
	pathBase = new PathString();
  }
  else
	await this._next(context);
}
}

還可以這樣自定義:

app.MapWhen(context =>
{
	return context.Request.Query.Keys.Contains("abc");
}, builder =>
{
	app.Use((Func<RequestDelegate, RequestDelegate>)(next =>
	{

		return (RequestDelegate)((context) => WriteAsync(context, next));
	}));
});

有了上面的簡單分析,應該不難理解哈。

我這裡直接貼了:
MapWhen:

public static class MapWhenExtensions
{
public static IApplicationBuilder MapWhen(
  this IApplicationBuilder app,
  Func<HttpContext, bool> predicate,
  Action<IApplicationBuilder> configuration)
{
  if (app == null)
	throw new ArgumentNullException(nameof (app));
  if (predicate == null)
	throw new ArgumentNullException(nameof (predicate));
  if (configuration == null)
	throw new ArgumentNullException(nameof (configuration));
  IApplicationBuilder applicationBuilder = app.New();
  configuration(applicationBuilder);
  RequestDelegate requestDelegate = applicationBuilder.Build();
  MapWhenOptions options = new MapWhenOptions()
  {
	Predicate = predicate,
	Branch = requestDelegate
  };
  return app.Use((Func<RequestDelegate, RequestDelegate>) (next => new RequestDelegate(new MapWhenMiddleware(next, options).Invoke)));
}
}

MapWhenMiddleware:

public class MapWhenMiddleware
{
private readonly RequestDelegate _next;
private readonly MapWhenOptions _options;

public MapWhenMiddleware(RequestDelegate next, MapWhenOptions options)
{
  if (next == null)
	throw new ArgumentNullException(nameof (next));
  if (options == null)
	throw new ArgumentNullException(nameof (options));
  this._next = next;
  this._options = options;
}

public async Task Invoke(HttpContext context)
{
  if (context == null)
	throw new ArgumentNullException(nameof (context));
  if (this._options.Predicate(context))
	await this._options.Branch(context);
  else
	await this._next(context);
}
}

上面都是異曲同工,就不做解釋了。

這裡再介紹一個方法,run:

app.Run(async context =>
{
	await context.Response.WriteAsync("hello word");
});

這個這個Run方法,沒有傳入next。

如下:

public static void Run(this IApplicationBuilder app, RequestDelegate handler)
{
  if (app == null)
	throw new ArgumentNullException(nameof (app));
  if (handler == null)
	throw new ArgumentNullException(nameof (handler));
  app.Use((Func<RequestDelegate, RequestDelegate>) (_ => handler));
}

表示這是末端。

那麼下面介紹一下,將我們的中介軟體寫入到一個獨立的類裡面去。

定義一個擴充套件類:

public static class SelfBuilderExtensions
{
	public static IApplicationBuilder UseSelfSelfMiddleware(this IApplicationBuilder app)
	{
		return app.UseMiddleware<SelfMiddleware>();
	}
}

具體的實現:

public class SelfMiddleware
{
	private readonly RequestDelegate _next;

	public SelfMiddleware(RequestDelegate next)
	{
		this._next = next;
	}

	public async Task InvokeAsync(HttpContext context)
	{
		Console.WriteLine("request handle");
		await this._next(context);
		Console.WriteLine("response handle");
	}
}

使用:

app.UseSelfSelfMiddleware();

簡單看一下UseMiddleware這個方法:

public static IApplicationBuilder UseMiddleware<TMiddleware>(
  this IApplicationBuilder app,
  params object[] args)
{
  return app.UseMiddleware(typeof (TMiddleware), args);
}

繼續看app.UseMiddleware:

public static IApplicationBuilder UseMiddleware(
  this IApplicationBuilder app,
  Type middleware,
  params object[] args)
{
  if (typeof (IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
  {
	if (args.Length != 0)
	  throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported((object) typeof (IMiddleware)));
	return UseMiddlewareExtensions.UseMiddlewareInterface(app, middleware);
  }
  IServiceProvider applicationServices = app.ApplicationServices;
  return app.Use((Func<RequestDelegate, RequestDelegate>) (next =>
  {
	MethodInfo[] array = ((IEnumerable<MethodInfo>) middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public)).Where<MethodInfo>((Func<MethodInfo, bool>) (m => string.Equals(m.Name, "Invoke", StringComparison.Ordinal) || string.Equals(m.Name, "InvokeAsync", StringComparison.Ordinal))).ToArray<MethodInfo>();
	if (array.Length > 1)
	  throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes((object) "Invoke", (object) "InvokeAsync"));
	if (array.Length == 0)
	  throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod((object) "Invoke", (object) "InvokeAsync", (object) middleware));
	MethodInfo methodInfo = array[0];
	if (!typeof (Task).IsAssignableFrom(methodInfo.ReturnType))
	  throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType((object) "Invoke", (object) "InvokeAsync", (object) "Task"));
	ParameterInfo[] parameters = methodInfo.GetParameters();
	if (parameters.Length == 0 || parameters[0].ParameterType != typeof (HttpContext))
	  throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters((object) "Invoke", (object) "InvokeAsync", (object) "HttpContext"));
	object[] objArray = new object[args.Length + 1];
	objArray[0] = (object) next;
	Array.Copy((Array) args, 0, (Array) objArray, 1, args.Length);
	object instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, objArray);
	if (parameters.Length == 1)
	  return (RequestDelegate) methodInfo.CreateDelegate(typeof (RequestDelegate), instance);
	Func<object, HttpContext, IServiceProvider, Task> factory = UseMiddlewareExtensions.Compile<object>(methodInfo, parameters);
	return (RequestDelegate) (context =>
	{
	  IServiceProvider serviceProvider = context.RequestServices ?? applicationServices;
	  if (serviceProvider == null)
		throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable((object) "IServiceProvider"));
	  return factory(instance, context, serviceProvider);
	});
  }));
}

一段一段分析:

if (typeof (IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
{
if (args.Length != 0)
  throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported((object) typeof (IMiddleware)));
return UseMiddlewareExtensions.UseMiddlewareInterface(app, middleware);
}

如果middleware 繼承IMiddleware,那麼將會呼叫UseMiddlewareExtensions.UseMiddlewareInterface.
IMiddleware如下:

public interface IMiddleware
{
Task InvokeAsync(HttpContext context, RequestDelegate next);
}

然後UseMiddlewareExtensions.UseMiddlewareInterface:

private static IApplicationBuilder UseMiddlewareInterface(
  IApplicationBuilder app,
  Type middlewareType)
{
  return app.Use((Func<RequestDelegate, RequestDelegate>) (next => (RequestDelegate) (async context =>
  {
	IMiddlewareFactory middlewareFactory = (IMiddlewareFactory) context.RequestServices.GetService(typeof (IMiddlewareFactory));
	if (middlewareFactory == null)
	  throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory((object) typeof (IMiddlewareFactory)));
	IMiddleware middleware = middlewareFactory.Create(middlewareType);
	if (middleware == null)
	  throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware((object) middlewareFactory.GetType(), (object) middlewareType));
	try
	{
	  await middleware.InvokeAsync(context, next);
	}
	finally
	{
	  middlewareFactory.Release(middleware);
	}
  })));

上面的大意就是封裝一箇中介軟體,裡面呼叫的方法就InvokeAsync。這個很好理解。

MethodInfo[] array = ((IEnumerable<MethodInfo>) middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public)).Where<MethodInfo>((Func<MethodInfo, bool>) (m => string.Equals(m.Name, "Invoke", StringComparison.Ordinal) || string.Equals(m.Name, "InvokeAsync", StringComparison.Ordinal))).ToArray<MethodInfo>();
if (array.Length > 1)
  throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes((object) "Invoke", (object) "InvokeAsync"));
if (array.Length == 0)
  throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod((object) "Invoke", (object) "InvokeAsync", (object) middleware));

獲取Invoke和InvokeAsync方法。

如果這兩個方法同時存在,丟擲異常。

如果一個都沒有丟擲異常。

if (!typeof (Task).IsAssignableFrom(methodInfo.ReturnType))
  throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType((object) "Invoke", (object) "InvokeAsync", (object) "Task"));
ParameterInfo[] parameters = methodInfo.GetParameters();
if (parameters.Length == 0 || parameters[0].ParameterType != typeof (HttpContext))
  throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters((object) "Invoke", (object) "InvokeAsync", (object) "HttpContext"));
object[] objArray = new object[args.Length + 1];
objArray[0] = (object) next;
Array.Copy((Array) args, 0, (Array) objArray, 1, args.Length);

如果返回結果不是一個Task報錯。

如果裡面的第一個引數不是HttpContext 報錯。

object[] objArray = new object[args.Length + 1];
objArray[0] = (object) next;
Array.Copy((Array) args, 0, (Array) objArray, 1, args.Length);
object instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, objArray);
if (parameters.Length == 1)
  return (RequestDelegate) methodInfo.CreateDelegate(typeof (RequestDelegate), instance);
Func<object, HttpContext, IServiceProvider, Task> factory = UseMiddlewareExtensions.Compile<object>(methodInfo, parameters);
return (RequestDelegate) (context =>
{
  IServiceProvider serviceProvider = context.RequestServices ?? applicationServices;
  if (serviceProvider == null)
	throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable((object) "IServiceProvider"));
  return factory(instance, context, serviceProvider);
});

上面表示含義是例項化函式的第一個引數應該是RequestDelegate。

然後通過反射生成具體的物件。

如果Invoke或者InvokeAsync 只有一個引數的話,也就是隻有HttpContext引數,直接通過CreateDelegate,建立委託。

如果不止的話,就通過一系列操作進行轉換,這裡就不介紹了,細節篇介紹了。畢竟是實踐篇。

以上只是個人整理,如果有錯誤,望請指點。

下一節異常處理中介軟體。

相關文章