asp.net管道模型学习(一):Http请求处理流程

我们平常做asp.net开发时,有没有想过一个问题,客户端的请求是如何到达我们写的代码层面,这之前要经过哪些服务器的处理,这篇文章会解答这个问题。

 

Http处理流程概述

 

请求到服务器端的处理流程:

  1. 用户在浏览器中输入的地址,通过DNS解析成“协议+IP+端口“,类似地址,浏览器就将请求发送到这个地址。

  2. 有IP确定服务器,请求到达这个地址(服务器)后,再由端口确定进程,由服务器对应的进程处理,在windows server服务器是由一个名为HTTP.SYS服务来接受http请求。

  3. HTTP.SYS服务接受到请求后,HTTP.SYS和IIS属于两个进程,属于两个进程之间的通讯,HTTP.SYS通过pipeline(管线)方式转发给IIS。

  4. IIS识别不同类型的请求,然后交给不同的应用程序处理,若找不到此类型的处理程序,且请求的文件没有收到服务器保护,则IIS直接把请求的文件返回给客户端。

 

我们先来了解下第2点中的HTTP.SYS服务到底是个什么东西,它其实是一个服务器内置驱动程序,用来监听来自外部的HTTP请求,在操作系统启动时IIS服务器首先就要在HTTP.SYS中注册自己的虚拟路径,其实是告知HTTP.SYS请求的URL是否可以访问,能访问则交给IIS,不能则返回404错误。

 

接下来解释下第4点中IIS是依据什么来处理请求的,答案就是请求文件的后缀名,能够处理各种后缀名的应用程序被叫做ISAPI(Internet Server Application Programe Interface,互联网服务器应用程序接口)应用程序,它其实是一个接口,扮演一个代理的角色,映射请求的页面的后缀和与之对应的应用程序。

我们在IIS中查看下处理程序映射,随便选中一个站点,在站点主页中选中处理程序映射打开就行:

08b5c52d-b853-4a59-8399-9f6952daefed.png

95799e4a-dadd-4517-bec2-b76c9b199005.png

由上图我们可以清楚地看到所有IIS能处理的请求,不同请求路径都对应着服务器的一个可执行文件,如.aspx文件则交给aspnet_isapi.dll程序处理,IIS不再关心这个请求是如何处理的,就是一个转发的作用,而asp.net则只是IIS的一个组成部分,是一个isapi扩展,IIS可以把请求转发给只要实现了isapi接口的任何程序,可以是非asp.net程序,如类似php_isapi或者java_isapi等。

 

asp.net宿主环境

 

IIS将请求转发给asp.net程序处理后,asp.net内部是如何处理请求的呢?

asp.net中有个叫HttpRuntime的类,这个类就是asp.net程序入口,它包含了Http请求的所有信息,如文件、请求的服务端变量、Http请求头信息等,asp.net则通过这些信息来运行加载对应的文件,最终将请求转换到输出流中,如HTML页面,或者一些静态资源如图片。

ISAPI除了除了映射文件外,还实现了其它功能:

  1. HttpRuntime类有个ProcessRequest方法,该方法以一个HttpWorkerRequest类作为参数,HTTP.SYS获取到Http请求时,其实是将所有请求信息保存到这个参数中。

  2. 在相互隔离的应用程序域(AppDomain)中加载HttpRuntime。

  3. 调用HttpRuntime的ProcessRequest方法。

后面的流程才是我们自己写的代码完成的工作,IIS接受返回的数据流,再重新返回给HTTP.SYS,最后HTTP.SYS再将处理好的数据返回给客户端,就是用户看到的网页内容。下面一张图是asp.net的宿主环境:

 

HttpRuntime源码分析

 

我们通过反编译分析HttpRuntime的内部处理,HttpRuntime在System.Web.dll中,找到这个dll文件使用反编译工具查看,找到HttpRuntime类,并找到ProcessRequest方法:

837ae5ee-8072-4f83-bdc2-a03e77050163.png

[AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Medium)]
public static void ProcessRequest(HttpWorkerRequest wr)
{
    if (wr == null)
    {
        throw new ArgumentNullException("wr");
    }
    if (UseIntegratedPipeline)
    {
        object[] args = new object[] { "HttpRuntime.ProcessRequest" };
        throw new PlatformNotSupportedException(SR.GetString("Method_Not_Supported_By_Iis_Integrated_Mode", args));
    }
    ProcessRequestNoDemand(wr);
}

这个方法的参数是一个HttpWorkerRequest对象,Http请求信息都保存在这个参数中,判断wr为空则抛出异常。再次判断UseIntegratedPipeline为true抛出异常,这个判断暂时在此不解释。最后调用ProcessRequestNoDemand来处理,我们看下这个方法的内部:

internal static void ProcessRequestNoDemand(HttpWorkerRequest wr)
{
    RequestQueue queue = _theRuntime._requestQueue;
    wr.UpdateInitialCounters();
    if (queue != null)
    {
        wr = queue.GetRequestToExecute(wr);
    }
    if (wr != null)
    {
        CalculateWaitTimeAndUpdatePerfCounter(wr);
        wr.ResetStartTime();
        ProcessRequestNow(wr);
    }
}

使用了一个请求队列,请求队列不为空时则通过传过来的HttpWorkerRequest参数获取请求并执行,将执行的结果返回给HttpWorkerRequest,若结果不为空则调用ProcessRequestNow方法立马处理请求,我们继续查看ProcessRequestNow方法的内部:

internal static void ProcessRequestNow(HttpWorkerRequest wr)
{
    _theRuntime.ProcessRequestInternal(wr);
}

直接将请求交给了ProcessRequestInternal方法,继续查看这个方法的内部:

private void ProcessRequestInternal(HttpWorkerRequest wr)
{
    Interlocked.Increment(ref this._activeRequestCount);
    if (this._disposingHttpRuntime)
    {
        try
        {
            wr.SendStatus(0x1f7, "Server Too Busy");
            wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
            byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Server Too Busy</body></html>");
            wr.SendResponseFromMemory(bytes, bytes.Length);
            wr.FlushResponse(true);
            wr.EndOfRequest();
        }
        finally
        {
            Interlocked.Decrement(ref this._activeRequestCount);
        }
    }
    else
    {
        HttpContext context;
        try
        {
            context = new HttpContext(wr, false);
        }
        catch
        {
            try
            {
                wr.SendStatus(400, "Bad Request");
                wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
                byte[] data = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
                wr.SendResponseFromMemory(data, data.Length);
                wr.FlushResponse(true);
                wr.EndOfRequest();
                return;
            }
            finally
            {
                Interlocked.Decrement(ref this._activeRequestCount);
            }
        }
        wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, context);
        HostingEnvironment.IncrementBusyCount();
        try
        {
            try
            {
                this.EnsureFirstRequestInit(context);
            }
            catch
            {
                if (!context.Request.IsDebuggingRequest)
                {
                    throw;
                }
            }
            context.Response.InitResponseWriter();
            IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);
            if (applicationInstance == null)
            {
                throw new HttpException(SR.GetString("Unable_create_app_object"));
            }
            if (EtwTrace.IsTraceEnabled(5, 1))
            {
                EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, applicationInstance.GetType().FullName, "Start");
            }
            if (applicationInstance is IHttpAsyncHandler)
            {
                IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance;
                context.AsyncAppHandler = handler2;
                handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);
            }
            else
            {
                applicationInstance.ProcessRequest(context);
                this.FinishRequest(context.WorkerRequest, context, null);
            }
        }
        catch (Exception exception)
        {
            context.Response.InitResponseWriter();
            this.FinishRequest(wr, context, exception);
        }
    }
}

一开始就更新HttpRuntime实际请求数量,我们经常使用的上下文对象HttpContext就在这里被创建了,在HttpContext构造函数中,HttpRequest和HttpResponse对象也被创建了:

internal HttpContext(HttpWorkerRequest wr, bool initResponseWriter)
{
    this._timeoutStartTimeUtcTicks = -1;
    this._timeoutTicks = -1;
    this._threadAbortOnTimeout = true;
    this.ThreadContextId = new object();
    this._wr = wr;
    this.Init(new HttpRequest(wr, this), new HttpResponse(wr, this));
    if (initResponseWriter)
    {
        this._response.InitResponseWriter();
    }
    PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_EXECUTING);
}

再次回到ProcessRequestInternal方法,后面HttpApplicationFactory类的GetApplicationInstance方法

获取一个HttpApplication实例,将新创建的HttpContext对象作为参数传递,该实例的类实现了IHttpHandler接口,关于IHttpHandler接口会在后面的文章中讲解。

我们继续深入GetApplicationInstance方法来探索HttpApplication实例是如何被创建的,看看该方法的实现:

internal static IHttpHandler GetApplicationInstance(HttpContext context)
{
    if (_customApplication != null)
    {
        return _customApplication;
    }
    if (context.Request.IsDebuggingRequest)
    {
        return new HttpDebugHandler();
    }
    _theApplicationFactory.EnsureInited();
    _theApplicationFactory.EnsureAppStartCalled(context);
    return _theApplicationFactory.GetNormalApplicationInstance(context);
}

方法中前面有两个判断来返回对应的Handler,暂时不管。我们看最后方法通过调用HttpApplicationFactory的静态变量_theApplicationFactory的GetNormalAppliationInstance方法来获取HttpApplication实例,我们再到这个方法继续探索:

private HttpApplication GetNormalApplicationInstance(HttpContext context)
{
    HttpApplication state = null;
    Stack stack = this._freeList;
    lock (stack)
    {
        if (this._numFreeAppInstances > 0)
        {
            state = (HttpApplication) this._freeList.Pop();
            this._numFreeAppInstances--;
            if (this._numFreeAppInstances < this._minFreeAppInstances)
            {
                this._minFreeAppInstances = this._numFreeAppInstances;
            }
        }
    }
    if (state == null)
    {
        state = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType);
        using (new ApplicationImpersonationContext())
        {
            state.InitInternal(context, this._state, this._eventHandlerMethods);
        }
    }
    if (AppSettings.UseTaskFriendlySynchronizationContext)
    {
        state.ApplicationInstanceConsumersCounter = new CountdownTask(1);
        if (<>c.<>9__37_0 == null)
        {
            Action<Task, object> action1 = <>c.<>9__37_0;
        }
        state.ApplicationInstanceConsumersCounter.Task.ContinueWith(<>c.<>9__37_0 = new Action<Task, object>(<>c.<>9.<GetNormalApplicationInstance>b__37_0), state, TaskContinuationOptions.ExecuteSynchronously);
    }
    return state;
}

_freeList是HttpApplication池,它是一个Stack栈,通过_numFreeAppInstances判断是否有可用的HttpApplication实例,如果有,则从stack中出栈,将_numFreeAppInstances减1,即可用的HttpApplication实例数量减1,然后再次判断可用实例数是否小于最小的实例数,若小于浙江最小实例数设置成可用实例数。那若HttpApplication池中可用实例数为0时,通过HttpRuntime的CreateNonPublicInstance方法创建一个HttpApplication实例.

 

asp.net管道

 

上面是在比较低的层次上讲到的IIS和asp.net框架所完成的工作,那我们写的代码(高层次框架,如WebForm、MVC、WebAPI等)是如何和这部分进行衔接的呢?当Http请求到达asp.net runtime时,交由管道来处理,管道由托管模块(各种HttpModule)和处理程序(各种HttpHandler)组成,控制管道工作的是HttpApplication,下面是管道处理的一个图:

管道处理流程如下:

  1. HttpRuntime将请求转交给HttpApplication(表示Web应用程序),HttpApplication创建针对Http请求的HttpContext对象,这些对象包含如HttpRequest、HttpResponse等对象, 这些对象在程序中可以通过上下文类进行访问。

  2. 接下来Http请求会通过一系列的HttpModule,这些Module对Http请求具有完全控制权,可以做一些执行某个实际工作前的事情。

  3. 接来下会被HttpHandler处理,如webForm中的aspx页面继承的page类就是实现了IHttpHandler接口,mvc中则是交给MvcHandler处理。

  4. HttpHandler处理完成后,Http请求会再次回到Module,此时Module则可以做一些某个实际工作完成后的事情。

请求在管道中要经过一系列的事件,这些事件由HttpApplication引发,通常由HttpModule订阅,也可以在全局类Global.asax中订阅,这一系列的事件就是一次请求的生命周期。

事件模式使用的就是观察者模式,该模式定义了对象之间的一种联系,使得当一个对象改变状态时,所有其它的对象都可以被通知到,asp.net的管道就是采用这种模式,观察者就是一系列的HttpModule,被观察的对象就是每个“请求”,状态由HttpApplication控制,用于描述当前请求的处理阶段,HttpApplication会根据一个特定的顺序修改这个状态,并在每个状态改变后触发响应的事件。asp.net会为每一个请求分配一个HttpApplication,观察者(HttpModule)可以修改请求的数据。asp.net使用的事件模式让自身框架具有了极强的扩展能力。

HttpApplication包含了一些事件,事件成员如下:

ffdeb150-4f74-436e-bcab-549f03225af9.png

由HttpApplication管线处理请求的事件如下(详情移步 https://msdn.microsoft.com/zh-cn/library/bb470252.aspx):

1.对请求进行验证,将检查浏览器发送的信息,并确定其是否包含潜在恶意标记。有关更多信息,请参见 ValidateRequest 和脚本侵入概述。

2.如果已在 Web.config 文件的 UrlMappingsSection 节中配置了任何 URL,则执行 URL 映射。

3.引发 BeginRequest 事件。

4.引发 AuthenticateRequest 事件。

5.引发 PostAuthenticateRequest 事件。

6.引发 AuthorizeRequest 事件。

7.引发 PostAuthorizeRequest 事件。

8.引发 ResolveRequestCache 事件。

9.引发 PostResolveRequestCache 事件。

10.引发 MapRequestHandler 事件。将根据所请求资源的文件扩展名,选择相应的处理程序。处理程序可以是本机代码模块,如 IIS 7.0StaticFileModule,也可以是托管代码模块,如 PageHandlerFactory 类(它处理 .aspx 文件)。

11.引发 PostMapRequestHandler 事件。

12.引发 AcquireRequestState 事件。

13.引发 PostAcquireRequestState 事件。

14.引发 PreRequestHandlerExecute 事件。

15.为该请求调用合适的 IHttpHandler 类的 ProcessRequest 方法(或异步版 IHttpAsyncHandler.BeginProcessRequest)。例如,如果该请求针对某页,则当前的页实例将处理该请求。

16.引发 PostRequestHandlerExecute 事件。

17.引发 ReleaseRequestState 事件。

18.引发 PostReleaseRequestState 事件。

19.如果定义了 Filter 属性,则执行响应筛选。

20.引发 UpdateRequestCache 事件。

21.引发 PostUpdateRequestCache 事件。

22.引发 LogRequest 事件。

23.引发 PostLogRequest 事件。

24.引发 EndRequest 事件。

25.引发 PreSendRequestHeaders 事件。

26.引发 PreSendRequestContent 事件。

从BeginRequest开始,后面并不是每个事件都会被触发,HttpModule可以任意订阅这些事件,并且在事件处理器中可以参与修改请求的操作,但是EndRequest事件肯定会被触发。事件处理过程中,可以随时调用Response.End()来提前结束请求的整个过程,发生异常而没有处理时也会提前结束整个过程。 

下面两张老图片可以很直观地说明管线的处理过程:

  

下面用代码把这些事件给展示出来:

后台定义一个视图方法:

/// <summary>
/// 展示请求事件
/// </summary>
/// <returns></returns>
public ActionResult ShowEvents()
{
    List<SysEventInfo> eventList = new List<SysEventInfo>();
    HttpApplication application = HttpContext.ApplicationInstance;//获取当前上下文的HttpApplication实例
    int i = 1;
    foreach(var e in application.GetType().GetEvents())
    {
        eventList.Add(new SysEventInfo {
            Id=i++,Name=e.Name,TypeName=e.GetType().Name
        });
    }
    return View(eventList);
}

新建一个视图页:

@model IEnumerable<SysEventInfo>
@{
    ViewBag.Title = "ShowEvents";
}

<h2>ShowEvents</h2>

<table class="table">
    <tr>
        <th>序号</th>

        <th>名称</th>

        <th>详细类型名称</th>
    </tr>
    @foreach (SysEventInfo item in Model)
    {
        <tr>
            <td>
                @item.Id
            </td>
            <td>
                @item.Name
            </td>
            <td>
                @item.TypeName
            </td>
        </tr>
    }
</table>

输出结果:

b26dfb7a-82a8-4e9d-968e-d0a534bb052f.png   

 
posted @ 2019-09-18 20:45  James.Yu  阅读(337)  评论(0)    收藏  举报