大数跨境
0
0

写一个简版 asp.net core

写一个简版 asp.net core amazingdotnet
2020-05-22
1
导读:动动手写一个简版 asp.net core

动手写一个简版 asp.net core

Intro

之前看到过蒋金楠老师的一篇 200 行代码带你了解 asp.net core 框架,最近参考蒋老师和 Edison 的文章和代码,结合自己对 asp.net core 的理解 ,最近自己写了一个 MiniAspNetCore ,写篇文章总结一下。

HttpContext

HttpContext 可能是最为常用的一个类了, HttpContext 是请求上下文,包含了所有的请求信息以及响应信息,以及一些自定义的用于在不同中间件中传输数据的信息

来看一下 HttpContext 的定义:

 
  1. public class HttpContext

  2. {

  3. public IServiceProvider RequestServices { get; set; }


  4. public HttpRequest Request { get; set; }


  5. public HttpResponse Response { get; set; }


  6. public IFeatureCollection Features { get; set; }


  7. public HttpContext(IFeatureCollection featureCollection)

  8. {

  9. Features = featureCollection;

  10. Request = new HttpRequest(featureCollection);

  11. Response = new HttpResponse(featureCollection);

  12. }

  13. }

HttpRequest 即为请求信息对象,包含了所有请求相关的信息,

HttpResponse 为响应信息对象,包含了请求对应的响应信息

RequestServices 为 asp.net core 里的 RequestServices,代表当前请求的服务提供者,可以使用它来获取具体的服务实例

Features 为 asp.net core 里引入的对象,可以用来在不同中间件中传递信息和用来解耦合

,下面我们就来看下 HttpRequest 和 HttpResponse 是怎么实现的

HttpRequest:

 
  1. public class HttpRequest

  2. {

  3. private readonly IRequestFeature _requestFeature;


  4. public HttpRequest(IFeatureCollection featureCollection)

  5. {

  6. _requestFeature = featureCollection.Get<IRequestFeature>();

  7. }


  8. public Uri Url => _requestFeature.Url;


  9. public NameValueCollection Headers => _requestFeature.Headers;


  10. public string Method => _requestFeature.Method;


  11. public string Host => _requestFeature.Url.Host;


  12. public Stream Body => _requestFeature.Body;

  13. }

HttpResponse:

 
  1. public class HttpResponse

  2. {

  3. private readonly IResponseFeature _responseFeature;


  4. public HttpResponse(IFeatureCollection featureCollection)

  5. {

  6. _responseFeature = featureCollection.Get<IResponseFeature>();

  7. }


  8. public bool ResponseStarted => _responseFeature.Body.Length > 0;


  9. public int StatusCode

  10. {

  11. get => _responseFeature.StatusCode;

  12. set => _responseFeature.StatusCode = value;

  13. }


  14. public async Task WriteAsync(byte[] responseBytes)

  15. {

  16. if (_responseFeature.StatusCode <= 0)

  17. {

  18. _responseFeature.StatusCode = 200;

  19. }

  20. if (responseBytes != null && responseBytes.Length > 0)

  21. {

  22. await _responseFeature.Body.WriteAsync(responseBytes);

  23. }

  24. }

  25. }

Features

上面我们提到我们可以使用 Features 在不同中间件中传递信息和解耦合

由上面 HttpRequestHttpResponse 的代码我们可以看出来, HttpRequest 和 HttpResponse 其实就是在 IRequestFeature 和 IResponseFeature 的基础上封装了一层,真正的核心其实是 IRequestFeatureIResponseFeature ,而这里使用接口就很好的实现了解耦,可以根据不同的 WebServer 使用不同的 RequestFeatureResponseFeature,来看下 IRequestFeatureIResponseFeature 的实现

 
  1. public interface IRequestFeature

  2. {

  3. Uri Url { get; }


  4. string Method { get; }


  5. NameValueCollection Headers { get; }


  6. Stream Body { get; }

  7. }


  8. public interface IResponseFeature

  9. {

  10. public int StatusCode { get; set; }


  11. NameValueCollection Headers { get; set; }


  12. public Stream Body { get; }

  13. }

这里的实现和 asp.net core 的实际的实现方式应该不同,asp.net core 里 Headers 同一个 Header 允许有多个值,asp.net core 里是 StringValues 来实现的,这里简单处理了,使用了一个 NameValueCollection 对象

上面提到的 Features 是一个 IFeatureCollection 对象,相当于是一系列的 Feature 对象组成的,来看下 FeatureCollection 的定义:

 
  1. public interface IFeatureCollection : IDictionary<Type, object> { }


  2. public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection

  3. {

  4. }

这里 IFeatureCollection 直接实现 IDictionary<Type,object> ,通过一个字典 Feature 类型为 Key,Feature 对象为 Value 的字典来保存

为了方便使用,可以定义两个扩展方法来方便的Get/Set

 
  1. public static class FeatureExtensions

  2. {

  3. public static IFeatureCollection Set<TFeature>(this IFeatureCollection featureCollection, TFeature feature)

  4. {

  5. featureCollection[typeof(TFeature)] = feature;

  6. return featureCollection;

  7. }


  8. public static TFeature Get<TFeature>(this IFeatureCollection featureCollection)

  9. {

  10. var featureType = typeof(TFeature);

  11. return featureCollection.ContainsKey(featureType) ? (TFeature)featureCollection[featureType] : default(TFeature);

  12. }

  13. }

Web服务器

上面我们已经提到了 Web 服务器通过 IRequestFeatureIResponseFeature 来实现不同 web 服务器和应用程序的解耦,web 服务器只需要提供自己的 RequestFeatureResponseFeature 即可

为了抽象不同的 Web 服务器,我们需要定义一个 IServer 的抽象接口,定义如下:

 
  1. public interface IServer

  2. {

  3. Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default);

  4. }

IServer 定义了一个 StartAsync 方法,用来启动 Web服务器,

StartAsync 方法有两个参数,一个是 requestHandler,是一个用来处理请求的委托,另一个是取消令牌用来停止 web 服务器

示例使用了 HttpListener 来实现了一个简单 Web 服务器, HttpListenerServer 定义如下:

 
  1. public class HttpListenerServer : IServer

  2. {

  3. private readonly HttpListener _listener;

  4. private readonly IServiceProvider _serviceProvider;


  5. public HttpListenerServer(IServiceProvider serviceProvider, IConfiguration configuration)

  6. {

  7. _listener = new HttpListener();

  8. var urls = configuration.GetAppSetting("ASPNETCORE_URLS")?.Split(';');

  9. if (urls != null && urls.Length > 0)

  10. {

  11. foreach (var url in urls

  12. .Where(u => u.IsNotNullOrEmpty())

  13. .Select(u => u.Trim())

  14. .Distinct()

  15. )

  16. {

  17. // Prefixes must end in a forward slash ("/")

  18. // https://stackoverflow.com/questions/26157475/use-of-httplistener

  19. _listener.Prefixes.Add(url.EndsWith("/") ? url : $"{url}/");

  20. }

  21. }

  22. else

  23. {

  24. _listener.Prefixes.Add("http://localhost:5100/");

  25. }


  26. _serviceProvider = serviceProvider;

  27. }


  28. public async Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default)

  29. {

  30. _listener.Start();

  31. if (_listener.IsListening)

  32. {

  33. Console.WriteLine("the server is listening on ");

  34. Console.WriteLine(_listener.Prefixes.StringJoin(","));

  35. }

  36. while (!cancellationToken.IsCancellationRequested)

  37. {

  38. var listenerContext = await _listener.GetContextAsync();


  39. var featureCollection = new FeatureCollection();

  40. featureCollection.Set(listenerContext.GetRequestFeature());

  41. featureCollection.Set(listenerContext.GetResponseFeature());


  42. using (var scope = _serviceProvider.CreateScope())

  43. {

  44. var httpContext = new HttpContext(featureCollection)

  45. {

  46. RequestServices = scope.ServiceProvider,

  47. };


  48. await requestHandler(httpContext);

  49. }

  50. listenerContext.Response.Close();

  51. }

  52. _listener.Stop();

  53. }

  54. }

HttpListenerServer 实现的 RequestFeatureResponseFeatue

 
  1. public class HttpListenerRequestFeature : IRequestFeature

  2. {

  3. private readonly HttpListenerRequest _request;


  4. public HttpListenerRequestFeature(HttpListenerContext listenerContext)

  5. {

  6. _request = listenerContext.Request;

  7. }


  8. public Uri Url => _request.Url;

  9. public string Method => _request.HttpMethod;

  10. public NameValueCollection Headers => _request.Headers;

  11. public Stream Body => _request.InputStream;

  12. }


  13. public class HttpListenerResponseFeature : IResponseFeature

  14. {

  15. private readonly HttpListenerResponse _response;


  16. public HttpListenerResponseFeature(HttpListenerContext httpListenerContext)

  17. {

  18. _response = httpListenerContext.Response;

  19. }


  20. public int StatusCode { get => _response.StatusCode; set => _response.StatusCode = value; }


  21. public NameValueCollection Headers

  22. {

  23. get => _response.Headers;

  24. set

  25. {

  26. _response.Headers = new WebHeaderCollection();

  27. foreach (var key in value.AllKeys)

  28. _response.Headers.Add(key, value[key]);

  29. }

  30. }


  31. public Stream Body => _response.OutputStream;

  32. }

为了方便使用,为 HttpListenerContext 定义了两个扩展方法,就是上面 HttpListenerServer 中的 GetRequestFeatureGetResponseFeature

 
  1. public static class HttpListenerContextExtensions

  2. {

  3. public static IRequestFeature GetRequestFeature(this HttpListenerContext context)

  4. {

  5. return new HttpListenerRequestFeature(context);

  6. }


  7. public static IResponseFeature GetResponseFeature(this HttpListenerContext context)

  8. {

  9. return new HttpListenerResponseFeature(context);

  10. }

  11. }

RequestDelegate

在上面的 IServer 定义里有一个 requestHandler 的 对象,在 asp.net core 里是一个名称为 RequestDelegate 的对象,而用来构建这个委托的在 asp.net core 里是 IApplicationBuilder,这些在蒋老师和 Edison 的文章和代码里都可以看到,这里我们只是简单介绍下,我在 MiniAspNetCore 的示例中没有使用这些对象,而是使用了自己抽象的 PipelineBuilder 和原始委托实现的

asp.net core 里 RequestDelegate 定义:

 
  1. public delegate Task RequestDelegate(HttpContext context);

其实和我们上面定义用的 Func<HttpContext,Task> 是等价的

IApplicationBuilder 定义:

 
  1. /// <summary>

  2. /// Defines a class that provides the mechanisms to configure an application's request pipeline.

  3. /// </summary>

  4. public interface IApplicationBuilder

  5. {

  6. /// <summary>

  7. /// Gets or sets the <see cref="T:System.IServiceProvider" /> that provides access to the application's service container.

  8. /// </summary>

  9. IServiceProvider ApplicationServices { get; set; }


  10. /// <summary>

  11. /// Gets the set of HTTP features the application's server provides.

  12. /// </summary>

  13. IFeatureCollection ServerFeatures { get; }


  14. /// <summary>

  15. /// Gets a key/value collection that can be used to share data between middleware.

  16. /// </summary>

  17. IDictionary<string, object> Properties { get; }


  18. /// <summary>

  19. /// Adds a middleware delegate to the application's request pipeline.

  20. /// </summary>

  21. /// <param name="middleware">The middleware delegate.</param>

  22. /// <returns>The <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns>

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


  24. /// <summary>

  25. /// Creates a new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" /> that shares the <see cref="P:Microsoft.AspNetCore.Builder.IApplicationBuilder.Properties" /> of this

  26. /// <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.

  27. /// </summary>

  28. /// <returns>The new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns>

  29. IApplicationBuilder New();


  30. /// <summary>

  31. /// Builds the delegate used by this application to process HTTP requests.

  32. /// </summary>

  33. /// <returns>The request handling delegate.</returns>

  34. RequestDelegate Build();

  35. }

我们这里没有定义 IApplicationBuilder,使用了简化抽象的 IAsyncPipelineBuilder,定义如下:

 
  1. public interface IAsyncPipelineBuilder<TContext>

  2. {

  3. IAsyncPipelineBuilder<TContext> Use(Func<Func<TContext, Task>, Func<TContext, Task>> middleware);


  4. Func<TContext, Task> Build();


  5. IAsyncPipelineBuilder<TContext> New();

  6. }

对于 asp.net core 的中间件来说 ,上面的 TContext 就是 HttpContext,替换之后也就是下面这样的:

 
  1. public interface IAsyncPipelineBuilder<HttpContext>

  2. {

  3. IAsyncPipelineBuilder<HttpContext> Use(Func<Func<HttpContext, Task>, Func<HttpContext, Task>> middleware);


  4. Func<HttpContext, Task> Build();


  5. IAsyncPipelineBuilder<HttpContext> New();

  6. }

是不是和 IApplicationBuilder 很像,如果不像可以进一步把 Func<HttpContext,Task> 使用 RequestDelegate 替换

 
  1. public interface IAsyncPipelineBuilder<HttpContext>

  2. {

  3. IAsyncPipelineBuilder<HttpContext> Use(Func<RequestDelegate, RequestDelegate> middleware);


  4. RequestDelegate Build();


  5. IAsyncPipelineBuilder<HttpContext> New();

  6. }

最后再将接口名称替换一下:

 
  1. public interface IApplicationBuilder1

  2. {

  3. IApplicationBuilder1 Use(Func<RequestDelegate, RequestDelegate> middleware);


  4. RequestDelegate Build();


  5. IApplicationBuilder1 New();

  6. }

至此,就完全可以看出来了,这 IAsyncPipelineBuilder<HttpContext> 就是一个简版的 IApplicationBuilder

IAsyncPipelineBuilder 和 IApplicationBuilder 的作用是将注册的多个中间件构建成一个请求处理的委托

中间件处理流程:

更多关于 PipelineBuilder 构建中间件的信息可以查看 让 .NET 轻松构建中间件模式代码 了解更多

WebHost

通过除了 Web 服务器之外,还有一个 Web Host 的概念,可以简单的这样理解,一个 Web 服务器上可以有多个 Web Host,就像 IIS/nginx (Web Server) 可以 host 多个站点

可以说 WebHost 离我们的应用更近,所以我们还需要 IHost 来托管应用

 
  1. public interface IHost

  2. {

  3. Task RunAsync(CancellationToken cancellationToken = default);

  4. }

WebHost 定义:

 
  1. public class WebHost : IHost

  2. {

  3. private readonly Func<HttpContext, Task> _requestDelegate;

  4. private readonly IServer _server;


  5. public WebHost(IServiceProvider serviceProvider, Func<HttpContext, Task> requestDelegate)

  6. {

  7. _requestDelegate = requestDelegate;

  8. _server = serviceProvider.GetRequiredService<IServer>();

  9. }


  10. public async Task RunAsync(CancellationToken cancellationToken = default)

  11. {

  12. await _server.StartAsync(_requestDelegate, cancellationToken).ConfigureAwait(false);

  13. }

  14. }

为了方便的构建 Host对象,引入了 HostBuilder 来方便的构建一个 Host,定义如下:

 
  1. public interface IHostBuilder

  2. {

  3. IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction);


  4. IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction);


  5. IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction);


  6. IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction);


  7. IHost Build();

  8. }

WebHostBuilder

 
  1. public class WebHostBuilder : IHostBuilder

  2. {

  3. private readonly IConfigurationBuilder _configurationBuilder = new ConfigurationBuilder();

  4. private readonly IServiceCollection _serviceCollection = new ServiceCollection();


  5. private Action<IConfiguration, IServiceProvider> _initAction = null;


  6. private readonly IAsyncPipelineBuilder<HttpContext> _requestPipeline = PipelineBuilder.CreateAsync<HttpContext>(context =>

  7. {

  8. context.Response.StatusCode = 404;

  9. return Task.CompletedTask;

  10. });


  11. public IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction)

  12. {

  13. configAction?.Invoke(_configurationBuilder);

  14. return this;

  15. }


  16. public IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction)

  17. {

  18. if (null != configureAction)

  19. {

  20. var configuration = _configurationBuilder.Build();

  21. configureAction.Invoke(configuration, _serviceCollection);

  22. }


  23. return this;

  24. }


  25. public IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction)

  26. {

  27. if (null != configureAction)

  28. {

  29. var configuration = _configurationBuilder.Build();

  30. configureAction.Invoke(configuration, _requestPipeline);

  31. }

  32. return this;

  33. }


  34. public IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction)

  35. {

  36. if (null != initAction)

  37. {

  38. _initAction = initAction;

  39. }


  40. return this;

  41. }


  42. public IHost Build()

  43. {

  44. var configuration = _configurationBuilder.Build();

  45. _serviceCollection.AddSingleton<IConfiguration>(configuration);

  46. var serviceProvider = _serviceCollection.BuildServiceProvider();


  47. _initAction?.Invoke(configuration, serviceProvider);


  48. return new WebHost(serviceProvider, _requestPipeline.Build());

  49. }


  50. public static WebHostBuilder CreateDefault(string[] args)

  51. {

  52. var webHostBuilder = new WebHostBuilder();

  53. webHostBuilder

  54. .ConfigureConfiguration(builder => builder.AddJsonFile("appsettings.json", true, true))

  55. .UseHttpListenerServer()

  56. ;


  57. return webHostBuilder;

  58. }

  59. }

这里的示例我在 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

运行示例代码:

 
  1. public class Program

  2. {

  3. private static readonly CancellationTokenSource Cts = new CancellationTokenSource();


  4. public static async Task Main(string[] args)

  5. {

  6. Console.CancelKeyPress += OnExit;


  7. var host = WebHostBuilder.CreateDefault(args)

  8. .ConfigureServices((configuration, services) =>

  9. {

  10. })

  11. .ConfigureApplication((configuration, app) =>

  12. {

  13. app.When(context => context.Request.Url.PathAndQuery.StartsWith("/favicon.ico"), pipeline => { });


  14. app.When(context => context.Request.Url.PathAndQuery.Contains("test"),

  15. p => { p.Run(context => context.Response.WriteAsync("test")); });

  16. app

  17. .Use(async (context, next) =>

  18. {

  19. await context.Response.WriteLineAsync($"middleware1, requestPath:{context.Request.Url.AbsolutePath}");

  20. await next();

  21. })

  22. .Use(async (context, next) =>

  23. {

  24. await context.Response.WriteLineAsync($"middleware2, requestPath:{context.Request.Url.AbsolutePath}");

  25. await next();

  26. })

  27. .Use(async (context, next) =>

  28. {

  29. await context.Response.WriteLineAsync($"middleware3, requestPath:{context.Request.Url.AbsolutePath}");

  30. await next();

  31. })

  32. ;

  33. app.Run(context => context.Response.WriteAsync("Hello Mini Asp.Net Core"));

  34. })

  35. .Initialize((configuration, services) =>

  36. {

  37. })

  38. .Build();

  39. await host.RunAsync(Cts.Token);

  40. }


  41. private static void OnExit(object sender, EventArgs e)

  42. {

  43. Console.WriteLine("exiting ...");

  44. Cts.Cancel();

  45. }

  46. }

在示例项目目录下执行 dotnet run,并访问 http://localhost:5100/:

仔细观察浏览器 console 或 network 的话,会发现还有一个请求,浏览器会默认请求 /favicon.ico 获取网站的图标

因为我们针对这个请求没有任何中间件的处理,所以直接返回了 404

在访问 /test,可以看到和刚才的输出完全不同,因为这个请求走了另外一个分支,相当于 asp.net core 里 MapMapWhen 的效果,另外 Run 代表里中间件的中断,不会执行后续的中间件

More

上面的实现只是我在尝试写一个简版的 asp.net core 框架时的实现,和 asp.net core 的实现并不完全一样,如果需要请参考源码,上面的实现仅供参考,上面实现的源码可以在 Github 上获取 https://github.com/WeihanLi/SamplesInPractice/tree/master/MiniAspNetCore

asp.net core 源码:https://github.com/dotnet/aspnetcore

Reference


【声明】内容源于网络
0
0
amazingdotnet
dotnet 开发知识库,了不起的 dotnet,dotnet 奇淫怪巧
内容 539
粉丝 0
amazingdotnet dotnet 开发知识库,了不起的 dotnet,dotnet 奇淫怪巧
总阅读212
粉丝0
内容539