Web APi之Web Host消息處理管道(六)


前言

我們知道Web API本身是無法提供請求-響應的機制,它是通過Web Host以及Self Host的寄宿的宿主方式來提供一個請求-響應的運行環境。二者都是將請求和響應抽象成HttpResponseMessage和HttpRequesMessage對象,並將請求HttpRequestMessage傳入到HttpMessageHandler進行處理最終將響應通過HttpResponseMessage逆向通過HttpMessageHandler返回到客戶端,但是在其過程中,此二者在管道中的機制是不一樣的,由於在最新Web API中是以Web Host寄宿實現,所以僅本節僅討論Web Host寄宿的實現。

Web Host 

Web API采用Web Host寄宿模式,其路由系統最終還是通過ASP.NET的路由系統來實現,也就是說其本質是將ASP.NET應用程序作為Web API的宿主,利用ASP.NET自身的路由系統並結合IIS來實現去持續監聽以及請求和響應的問題。

既然Web Host寄宿是利用ASP.NET的路由系統實現,那么我們首先就得了解下ASP.NET的路由系統,ASP.NET的路由是利用UrlRoutingModel中的HttpModel來完成,並通過HttpApplication中的PostResloveRequesCache事件對其請求進行攔截,並借助注冊的路由將請求的URL進行解析獲得路由數據對象RouteData,UrlRoutingModel最終從匹配對象Route對象中獲取對應的HttpHandler並映射給當前上下文HttpContext,緊接着上下文HttpContext獲取請求並進行響應。

 

上述聽起來似乎有點拗口並且不太能讓人理解,我們首先來了解下ASP.NET的請求管道,當一個請求過來時首先會經過擴展名來進行相應的處理,如果是靜態文件則直接返回到客戶端,若是動態文件如擴展名為.aspx,則將交給aspnet.isapi.dll來進行處理,然后就是創建一個ASP.NET運行環境HttpRuntime,在創建運行環境的同時首先創建一個HttpWorkerRquest對象,這個對象則保存着請求的報文信息,接着借助HttpWorkerRequest對象創建上下文 HttpContext (包含着HttpResponse和HttpRequest以及HttpSessionState等)接下來就是通過ApplicationFactory工廠創建一個上述所說的 HttpApplication 對象,此對象為管道事件的核心對象,然后調用ProcessRequest方法將上下文HttpContext傳入其中,最終通過HttpApplication來執行19個管道事件,在前八個事件利用 HttpModel 進行相關的驗證、授權等,在第八個事件則創建頁面類對象同時實現IHttpHandler接口創建一個我們上述所說的 HttpHandler ,后面大概就是創建頁面控件樹以及執行頁面對象的頁面生命周期等。

通過上述敘述知,Web API利用Web Host作為寄宿則利用UrlRoutingModel來動態映射給HttpContext中的HttpHandler是實現管道集成的核心,它會將ASP.NET中的HttpRequest對象表示的請求轉換成HttpRequestMessage對象並傳入到消息管道中,並將輸出的HttpResponseMessage寫入到HttpResponse中並最終進行響應輸出。現在最主要的問題是這個HttpHandler在Web Host模式下是怎樣實現的?請繼續往下看。

HttpControllerRouteHandler

當通過Web API的配置文件通過模板以及約束等注冊一個HttpRoute路由對象到路由集合中時也就是將其注冊到路由表中(因為路由表中的屬性Routes存着注冊的路由的路由集合),首先會創建一個HostedHttpRoute對象,我們看看該對象的定義:

其中最重要的是OriginalRoute屬性,照定義來看就僅僅是創建了這個對象而已,確確實實是這樣,因為該對象僅僅是起一個過渡作用,因為真正創建的對象是HttpWebRoute的Route對象,通過查看其構造函數可知,如下:

public HostedHttpRoute(string uriTemplate, IDictionary<string, object> defaults, IDictionary<string, object> constraints, IDictionary<string, object> dataTokens, HttpMessageHandler handler)
{
    RouteValueDictionary dictionary;
    RouteValueDictionary dictionary2;
    RouteValueDictionary dictionary3;
    base..ctor();
    dictionary = (defaults != null) ? new RouteValueDictionary(defaults) : null;
    dictionary2 = (constraints != null) ? new RouteValueDictionary(constraints) : null;
    dictionary3 = (dataTokens != null) ? new RouteValueDictionary(dataTokens) : null;
    this.OriginalRoute = new HttpWebRoute(uriTemplate, dictionary, dictionary2, dictionary3, HttpControllerRouteHandler.Instance, this);
    this.Handler = handler;
    return;
}

如上紅色標記,此時將屬性OriginalRoute作為HttpWebRoute對象的引用存在HostedHttpRoute中並返回一個Route對象。

那么上述所說的RouteData是什么時候生成呢?

我們再來看看這個Route對象的定義:

當請求過來時請求URL會與注冊的路由(Route)進行匹配,若匹配成功,由上知,此時同時會通過GetRouteData方法生成一個 RouteData 對象,我們同時看看該RouteData的定義:

public class RouteData
{
    // Fields
    private RouteValueDictionary _dataTokens;
    private IRouteHandler _routeHandler;
    private RouteValueDictionary _values;

    // Methods
    public RouteData();
    public RouteData(RouteBase route, IRouteHandler routeHandler);
    public string GetRequiredString(string valueName);

    // Properties
    public RouteValueDictionary DataTokens { get; }
    public RouteBase Route { get; set; }
    public IRouteHandler RouteHandler { get; set; }
    public RouteValueDictionary Values { get; }
}

此對象中有一個匹配路由Route的引用屬性,也就是當前匹配成功的Route對象並且還有返回值為IRouteHandler的屬性RouteHandler。

所以在注冊路由的過程是:

HttpRoute-------------->HostedHttpRoute-------------->HttpWebRoute------------->Route

在HttpWebRoute繼承自Route類中有一個屬性RouteHandler如下:

public IRouteHandler RouteHandler { [CompilerGenerated] get; [CompilerGenerated] set; }

我們通過如下HttpControllerRouteHandler的定義知,上述RouteHandler就是一個HttpControllerRouteHandler對象,因為HttpControllerRouteHandler實現了接口IRouteHandler,如下:

public class HttpControllerRouteHandler : IRouteHandler
{
    // Fields
    private static readonly Lazy<HttpControllerRouteHandler> _instance;
    [CompilerGenerated]
    private static Func<HttpControllerRouteHandler> CS$<>9__CachedAnonymousMethodDelegate1;

    // Methods
    static HttpControllerRouteHandler();
    protected HttpControllerRouteHandler();
    [CompilerGenerated]
    private static HttpControllerRouteHandler <.cctor>b__0();
    protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext);
    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext);

    // Properties
    public static HttpControllerRouteHandler Instance { get; }
}

注意上述標記,通過GetHttpHandler方法知,在ASP.NET路由中的HttpHandler就是由HttpControllerRouteHandler對象提供的,同時我們看下該方法的定義:

protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
    return new HttpControllerHandler(requestContext.RouteData);
}

看見這個方法的返回值沒,又涉及到一個對象,其實通過HttpControllerRouteHandler中的方法獲得的HttpHandler其實是一個HttpControllerHandler對象,而整個消息處理管道則是有它來創建的。請繼續往下看!

HttpControllerHandler

上面說過此對象是Web Host寄宿模式下的整個管道的執行者,下面我們就來看看這個類的定義(操蛋,太長了還是就看下這個類的繼承,再敘述其他的再給代碼):

public class HttpControllerHandler : HttpTaskAsyncHandler
{
}

再看下其父類的定義:

public abstract class HttpTaskAsyncHandler : IHttpAsyncHandler, IHttpHandler
{
    // Methods
    protected HttpTaskAsyncHandler();
    [EditorBrowsable(EditorBrowsableState.Never)]
    public virtual void ProcessRequest(HttpContext context);
    public abstract Task ProcessRequestAsync(HttpContext context);
    IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
    void IHttpAsyncHandler.EndProcessRequest(IAsyncResult result);

    // Properties
    public virtual bool IsReusable { get; }
}

簡單敘述下,HttpRouteHandler是HttpTaskAsyncHadler的子類,並且其父類為實現了IHttpAsync和IHttpHandler接口的抽象類。最主要的是HttpTaskAsyncHandler調用了實現IHttpAsyncHandler接口的BeginProcessRequest方法,並返回一個IAsyncResult,而EndProcessRequest方法則用來執行了返回值為IAsyncResult的BeginProcessRequest方法。

我們再來看看HttpRouteHandler的一個構造函數:

 public HttpControllerHandler(RouteData routeData, HttpMessageHandler handler);

該構造函數的第一個參數就是根據路由解析得到的路由數據,第二個參數就是管道中的處理程序,實質上就是第一個HttpMessageHandler即管道頭(HttpServer)。

下面我們總結下在ASP.NET路由系統中HttpControllerHandler被創建的整個過程。請看!

 總結

當進入ASP.NET請求管道中時,在HttpModel中通過事件對其請求進行攔截后,然后利用UrlRoutingModel中注冊的路由對象對當前請求的URL進行匹配,若匹配通過由對應匹配的路由解析並生成一個RouteData對象,當然這個Route對象就是HttpWebRoute對象,接着利用RouteData對象對應的Route來獲得RouteHandler,這個RouteHandler就是HttpControllerRouteHandler,接着利用UrlRoutingModel中得到的RouteData和當前上下文HttpContext生成一個請求上下文對象,再以該請求上下文對象為對象調用HttpControllerRouteHandler上的GetHttpHandler方法獲得HttpHandler(返回的是HttpControllerHandler),並將HttpHandler映射到當前上下文HttpContext中,然后調用HttpControllerHandler上繼承自IHttpAsyncHandler上的BeginProcessRequest方法開始進入Web API管道。

 

下面我們實現IHttpModel接口並對照上述敘述進行編碼,如下:

    public class WebHost : IHttpModule
    {

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public void Init(HttpApplication context)
        {
            context.PostResolveRequestCache += context_PostResolveRequestCache;
        }

        void context_PostResolveRequestCache(object sender, EventArgs e)
        {
            var app = sender as HttpApplication;
            var contextWrapper = new HttpContextWrapper(app.Context);
            var routeData = RouteTable.Routes.GetRouteData(contextWrapper);
            var requestContext = new RequestContext(contextWrapper, routeData);
            var httpHandler = routeData.RouteHandler.GetHttpHandler(requestContext);
            var httpAsyncHandler = httpHandler as IHttpAsyncHandler;
            httpAsyncHandler.BeginProcessRequest(app.Context, null, null);
        }
    }

總結

下面就Web API中Web Host寄宿模式下的消息處理管道給出整體示意圖:來自【Web Host管道

 

溫馨提示 :有關上述圖片HttpControllerDispatcher在后續原理中進行講解。。。。。。


免責聲明!

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



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