asp.net core 核心對象解析


首先聲明這篇文章的所有內容均來自https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html

                                                                                                                                                                 ----感謝大內老A(artech)對於.net core的貢獻,我們都是受益人。

HttpContext

HttpContext是Http請求到達服務器后被服務器根據接口定義(這個接口定義實際上就是Feature層,由各種Feature轉換而來的)轉換而成的一個對象,它代表了當前請求的所有內容,它有兩個核心的對象,一個是HttpRequest,另一個就是HttpResponse,HttpContext在已注冊的各個中間件中傳遞、加工后形成最終的HttpResponse然后反饋給請求者。一個HttpContext類似於下面這樣的結構:

public class HttpContext
{           
    public  HttpRequest Request { get; }
    public  HttpResponse Response { get; }
}
public class HttpRequest
{
    public  Uri Url { get; }
    public  NameValueCollection Headers { get; }
    public  Stream Body { get; }
}
public class HttpResponse
{
    public  NameValueCollection Headers { get; }
    public  Stream Body { get; }
    public int StatusCode { get; set;}
}

RequestDelegate

從命名上面可以看出這個是一個委托對象,這個RequestDelegate是由一連串的Func<RequestDelegate,RequestDelegate>轉換而來的(在ApplicationBuilder中)。RequestDelegate在asp.net core中被用作處理Http請求,它對傳入的Http上下文(httpContext)進行處理,最終返回結果,它是整個asp.net core管道的一個重要組成部分(pipeline=server+requestdelegate)

MiddleWare

中間件在asp.net core中起着舉足輕重的作用,中間件在asp.net core中最終被轉換成一個RequestDelegate(在ApplicationBuilder中)。那如何表示這個中間件呢?第一種方式是由一個Func<RequestDelegate,RequestDelegate>來表示,還有一種方式是一個中間件類:

public class Middleware
    {
        private readonly RequestDelegate _next;

        public Middleware(RequestDelegate next)
        {
            _next = next;
        }

        public Task Invoke(HttpContext httpContext)
        {

            return _next(httpContext);
        }
    }

中間件在asp.net core的作用就是處理每一個傳入的HttpContext,然后決定是否由下一個中間件繼續處理,但是中間件是編譯時的產物,在運行時,他們都被轉換成唯一的一個RequestDelegate了。

ApplicationBuilder

ApplicationBuilder顧名思義是用來構建一個Application的,那么在asp.net core 的語義下面,這個Application實際上就是一個RequestDelegate。所以,ApplicationBuilder實際上就是用來構建一個ReqesutDelegate。構建所需原料便是你添加的各種中間件(Func<RequestDelegate,RequestDelegate>).

下面代表表述一個ApplicationBuilder的最核心的接口信息:

public interface  IApplicationBuilder
{
    IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
    RequestDelegate Build();
}

下面代碼是一個ApplicationBuilder最核心的實現:

public class ApplicationBuilder : IApplicationBuilder
{
    private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
    public RequestDelegate Build()
    {

        _middlewares.Reverse();
        return httpContext =>
        {
            RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; };
            foreach (var middleware in _middlewares)
            {
                next = middleware(next);
            }
            return next(httpContext);
        };
    }

    public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
    {
        _middlewares.Add(middleware);
        return this;
    }
}

Server

服務器在管道中的職責非常明確,當我們自動作應用宿主的WebHost的時候,服務它被自動啟動。啟動后的服務器會綁定到指定的端口進行請求監聽,一旦有請求抵達,服務器會根據該請求創建出代表上下文的HttpContext對象,並將該上下文作為輸入調用由所有注冊中間件構建而成的RequestDelegate對象。

下面代碼表示一個Server的核心接口功能:

public interface IServer
{ 
    Task StartAsync(RequestDelegate handler);
}

HttpContext和Server之間的適配

面向應用層的HttpContext對象是對請求和響應的封裝,但是請求最初來源於服務器,針對HttpContext的任何響應操作也必需作用於當前的服務器才能真正起作用。現在問題來了,所有的ASP.NET Core應用使用的都是同一個HttpContext類型,但是卻可以注冊不同類型的服務器,我們必需解決兩者之間的適配問題。

首先把HttpContext的轉換過程總結一遍:HttpContext的轉換過程是由①Server上面監聽請求到達時得到一個ServerContext(具體看是什么server)②這個ServerContext上下文用於創建一個ServerFeature,ServerFeature實現了各種中間Feature,包括IRequestFeature和IResponseFeature等③得到的這個ServerFeature用於創建適配HttpContext的FeatureCollection④用FeatureCollection創建HttpContext。就是這么一個過程。這個過程實在Server中完成的,代碼如下:

//位於Server的StartAsync方法 
public async Task StartAsync(RequestDelegate handler)
    {
        Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));    
        _httpListener.Start();
        Console.WriteLine("Server started and is listening on: {0}", string.Join(';', _urls));
        while (true)
        {
            var listenerContext = await _httpListener.GetContextAsync(); 
            var feature = new HttpListenerFeature(listenerContext);
            var features = new FeatureCollection()
                .Set<IHttpRequestFeature>(feature)
                .Set<IHttpResponseFeature>(feature);
            var httpContext = new HttpContext(features);
            await handler(httpContext);
            listenerContext.Response.Close();
        }
    }

計算機領域有一句非常經典的話:“任何問題都可以通過添加一個抽象層的方式來解決,如果解決不了,那就再加一層”。同一個HttpContext類型與不同服務器類型之間的適配問題也可可以通過添加一個抽象層來解決,我們定義在該層的對象稱為Feature。如上圖所示,我們可以定義一系列的Feature接口來為HttpContext提供上下文信息,其中最重要的就是提供請求的IRequestFeature和完成響應的IResponseFeature接口。那么具體的服務器只需要實現這些Feature接口就可以了。

我們接着從代碼層面來看看具體的實現。如下面的代碼片段所示,我們定義了一個IFeatureCollection接口來表示存放Feature對象的集合。從定義可以看出這是一個以Type和Object作為Key和Value的字典,Key代表注冊Feature所采用的類型,而Value自然就代表Feature對象本身,話句話說我們提供的Feature對象最終是以對應Feature類型(一般為接口類型)進行注冊的。為了編程上便利,我們定義了兩個擴展方法Set<T>和Get<T>來設置和獲取Feature對象。

public interface IFeatureCollection : IDictionary<Type, object> { }
public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection { }   
public static partial class Extensions
{
    public static T Get<T>(this IFeatureCollection features) => features.TryGetValue(typeof(T), out var value) ? (T)value : default(T);
    public static IFeatureCollection Set<T>(this IFeatureCollection features, T feature)
    { 
        features[typeof(T)] = feature;
        return features;
    }
}

如下所示的用來提供請求和響應IHttpRequestFeature和IHttpResponseFeature接口的定義,可以看出它們具有與HttpRequest和HttpResponse完全一致的成員定義。

public interface IHttpRequestFeature
{
    Uri                     Url { get; }
    NameValueCollection     Headers { get; }
    Stream                  Body { get; }
}    
public interface IHttpResponseFeature
{
    int                       StatusCode { get; set; }
    NameValueCollection     Headers { get; }
    Stream                  Body { get; }
}

接下來我們來看看HttpContext的具體實現。簡約起見在這里HttpContext只包含Request和Response兩個屬性成員,對應的類型分別為HttpRequest和HttpResponse,如下所示的就是這兩個類型的具體實現。我們可以看出HttpRequest和HttpResponse都是通過一個IFeatureCollection對象構建而成的,它們對應的屬性成員均有分別由包含在這個Feature集合中的IHttpRequestFeature和IHttpResponseFeature對象來提供的。

public class HttpRequest
{
    private readonly IHttpRequestFeature _feature;    
      
    public  Uri Url => _feature.Url;
    public  NameValueCollection Headers => _feature.Headers;
    public  Stream Body => _feature.Body;

    public HttpRequest(IFeatureCollection features) => _feature = features.Get<IHttpRequestFeature>();
}

public class HttpResponse
{
    private readonly IHttpResponseFeature _feature;

    public  NameValueCollection Headers => _feature.Headers;
    public  Stream Body => _feature.Body;
    public int StatusCode { get => _feature.StatusCode; set => _feature.StatusCode = value; }

    public HttpResponse(IFeatureCollection features) => _feature = features.Get<IHttpResponseFeature>();

}

HttpContext的實現就更加簡單了。如下面的代碼片段所示,我們在創建一個HttpContext對象是同樣會提供一個IFeatureCollection對象,我們利用該對象創建對應的HttpRequest和HttpResponse對象,並作為對應的屬性值。

public class HttpContext
{           
    public  HttpRequest Request { get; }
    public  HttpResponse Response { get; }

    public HttpContext(IFeatureCollection features)
    {
        Request = new HttpRequest(features);
        Response = new HttpResponse(features);
    }
}

HttpListenerServer

在對服務器和它與HttpContext的適配原理具有清晰的認識之后,我們來嘗試着自己定義一個服務器。在前面的Hello World實例中,我們利用WebHostBuilder的擴展方法UseHttpListener注冊了一個HttpListenerServer,我們現在就來看看這個采用HttpListener作為監聽器的服務器類型是如何實現的。

由於所有的服務器都需要將自己的Feature實現來為HttpContext提供對應的上下文信息,所以我們得先來為HttpListenerServer定義相應的接口。對HttpListener稍微了解的朋友應該知道它在接收到請求之后會創建一個自己的上下文對象,對應的類型為HttpListenerContext。如果采用HttpListenerServer作為應用的服務器,意味着HttpContext承載的上下文信息最初來源於這個HttpListenerContext,所以Feature的目的旨在解決這兩個上下文之間的適配問題。

如下所示的HttpListenerFeature就是我們為HttpListenerServer定義的Feature。HttpListenerFeature同時實現了IHttpRequestFeature和IHttpResponseFeature,實現的6個屬性成員最初都來源於創建該Feature對象提供的HttpListenerContext對象。

public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature
{
    private readonly HttpListenerContext _context;
    public HttpListenerFeature(HttpListenerContext context) => _context = context;

    Uri IHttpRequestFeature.Url => _context.Request.Url;
    NameValueCollection IHttpRequestFeature.Headers => _context.Request.Headers;
    NameValueCollection IHttpResponseFeature.Headers => _context.Response.Headers;
    Stream IHttpRequestFeature.Body => _context.Request.InputStream;
    Stream IHttpResponseFeature.Body => _context.Response.OutputStream;
    int IHttpResponseFeature.StatusCode { get => _context.Response.StatusCode; set => _context.Response.StatusCode = value; }
}

如下所示的是HttpListenerServer的最終定義。我們在構造一個HttpListenerServer對象的時候可以提供一組監聽地址,如果沒有提供,會采用“localhost:5000”作為默認的監聽地址。在實現的StartAsync方法中,我們啟動了在構造函數中創建的HttpListenerServer對象,並在一個循環中通過調用其GetContextAsync方法實現了針對請求的監聽和接收。

public class HttpListenerServer : IServer
{
    private readonly HttpListener     _httpListener;
    private readonly string[]             _urls;

    public HttpListenerServer(params string[] urls)
    {
        _httpListener = new HttpListener();
        _urls = urls.Any()?urls: new string[] { "http://localhost:5000/"};
    }

    public async Task StartAsync(RequestDelegate handler)
    {
        Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));    
        _httpListener.Start();
        while (true)
        {
            var listenerContext = await _httpListener.GetContextAsync(); 
            var feature = new HttpListenerFeature(listenerContext);
            var features = new FeatureCollection()
                .Set<IHttpRequestFeature>(feature)
                .Set<IHttpResponseFeature>(feature);
            var httpContext = new HttpContext(features);
            await handler(httpContext);
            listenerContext.Response.Close();
        }
    }
}

當HttpListener監聽到抵達的請求后,我們會得到一個HttpListenerContext對象,此時我們只需要據此創建一個HttpListenerFeature對象並它分別以IHttpRequestFeature和IHttpResponseFeature接口類型注冊到創建FeatureCollection集合上。我們最終利用這個FeatureCollection對象創建出代表上下文的HttpContext,然后將它作為參數調用由所有中間件共同構建的RequestDelegate對象即可。

WebHost

WebHost可以理解為寄宿或者承載Web應用的宿主,應用的啟動可以通過啟動作為宿主的WebHost來實現。

一個WebHost由一個服務器和一個RequestDelegate構成的,這個RequestDelegate本身是一個委托類型,它由Func<RequestDelegate,RequestDelegate>表示的中間件(多個)轉換而來。

到目前為止我們已經知道了由一個服務器和多個中間件構成的管道是如何完整針對請求的監聽、接收、處理和最終響應的,接下來來討論這樣的管道是如何被構建出來的。管道是在作為應用宿主的WebHost對象啟動的時候被構建出來的,在ASP.NET Core Mini中,我們將表示應用宿主的IWebHost接口簡寫成如下的形式:只包含一個StartAsync方法用來啟動應用程序。

public interface IWebHost
{
    Task StartAsync();
}

由於由WebHost構建的管道由Server和HttpHandler構成,我們在默認實現的WebHost類型中,我們直接提供者兩個對象。在實現的StartAsync方法中,我么只需要將后者作為參數調用前者的StartAsync方法將服務器啟動就可以了。

public class WebHost : IWebHost
{
    private readonly IServer _server;
    private readonly RequestDelegate _handler; 
    public WebHost(IServer server, RequestDelegate handler)
    {
        _server = server;
        _handler = handler;
    } 
    public Task StartAsync() => _server.StartAsync(_handler);
}

WebHostBuilder

作為最后一個着重介紹的核心對象,WebHostBuilder的使命非常明確:就是創建作為應用宿主的WebHost。由於在創建WebHost的時候需要提供注冊的服務器和由所有注冊中間件構建而成的RequestDelegate,所以在對應接口IWebHostBuilder中,我們為它定義了三個核心方法。

 

public interface IWebHostBuilder
{
    IWebHostBuilder UseServer(IServer server);
    IWebHostBuilder Configure(Action<IApplicationBuilder> configure);
    IWebHost Build();
}

 

除了用來創建WebHost的Build方法之外,我們提供了用來注冊服務器的UseServer方法和用來注冊中間件的Configure方法。Configure方法提供了一個類型為Action<IApplicationBuilder>的參數,意味着我們針對中間件的注冊是利用上面介紹的IApplicationBuilder對象來完成的。

如下所示的WebHostBuilder是針對IWebHostBuilder接口的默認實現,它具有兩個字段分別用來保存注冊的中間件和調用Configure方法提供的Action<IApplicationBuilder>對象。當Build方法被調用之后,我們創建一個ApplicationBuilder對象,並將它作為參數調用這些Action<IApplicationBuilder>委托,進而將所有中間件全部注冊到這個ApplicationBuilder對象上。我們最終調用它的Build方法得到由所有中間件共同構建的RequestDelegate對象,並利用它和注冊的服務器構建作為應用宿主的WebHost對象。

public class WebHostBuilder : IWebHostBuilder
{
    private IServer _server;
    private readonly List<Action<IApplicationBuilder>> _configures = new List<Action<IApplicationBuilder>>();   

    public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
    {
        _configures.Add(configure);
        return this;
    }
    public IWebHostBuilder UseServer(IServer server)
    {
        _server = server;
        return this;
    }   

    public IWebHost Build()
    {
        var builder = new ApplicationBuilder();
        foreach (var configure in _configures)
        {
            configure(builder);
        }
        return new WebHost(_server, builder.Build());
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM