Asp.Net 上传大文件专题(2)--页面生成流程


回顾上一篇,我们可以了解到以下内容:

1.默认情况下,只能上传小于4M的文件,如果我们要上传大文件的话,可以通过更改maxRequestLength来提高限制。
2.Asp.net 1.X 通过改变maxRequestLength可以增大上传的限制,但是由于需要将用户请求的实体内容完全载入内存后再处理,会大大影响服务器性能。
3.Asp.net 2.0 则会在用户请求的实体内容超出一定阈值或称限制值(256K)之后,被透明地缓冲到磁盘,因此在ASP.NET 2.0中服务器的内存不会因为客户端的异常请求而耗尽。

本篇概要:

      在这一篇中主要理清HTTP请求流从到达WEB服务器开始到生成页面所经历的流程。为了能更好的理解本篇内空,建议大家先去看一下"HTTP请求流程"和"ASP.NET 应用程序生命周期";

正文内容:

      有些朋友可能会不耐烦了,“这和上传大文件有什么关系呀?”。那是因为我们无法通过.Net提供给我们的上传控件得到我们想要的效果,如果想实现我们上传大文件并显示进度,那就只有在服务器接受到HTTP请求后,对该请求进行处理。那我们就得对HTTP请求在服务器端的流程有个了解。
      浏览器发送页面请求(包括Get、Post、Put等请求方式)到IIS服务器后,在侦听程序进行接收后,只有少数几种被客户端请求的资源类型由IIS 直接处理。例如,对 HTML 页面、文本文件、JPEG 和 GIF 图像的传入请求由 IIS 处理。对 Active Server Page (*.asp) 文件的请求通过调用名为 asp.dll 的 ASP 专用扩展模块进行解析。同样,对 ASP.NET 资源(例如,*.aspx、*.asmx、*.ashx)的请求将传递到 ASP.NET ISAPI 扩展。因为IIS 6.0在IIS 5.x 上有所变动,所以我们分开来讲。

1.先来看IIS 5.X 的 ASP.net 请求处理过程:
087282158.bmp

      由上图可知,IIS 5.X 中侦听程序由IIS进程(inetinfo.exe)来实现,它除了可以用来接收HTTP消息的功能外,而且直接把aspnet_isapi.dll(asp.net isapi扩展)寄宿在了该进程里。IIS 接收到消息后,检查脚本映射,然后调用 ASP.NET ISAPI 扩展,又由该扩展将请求和控制以及相关的所有信息传送给辅助进程aspnet_wp.exe(该辅助进程也是由asp.net isapi调用,并在该进程初始化时自动加载了.Net 运行时)中的.Net 运行时。因为ASP.NET ISAPI 扩展和.Net运行时不属于一个进程,所有的请求数据都通过命名管道进行发送。

2.接着我们再来看一下IIS 6.0 的 ASP.net 请求处理过程:
087282159.bmp
      由图可知,在IIS 6.0中,inetinfo.exe不再用来传递HTTP请求到ISAPI扩展,但继续为其他协议的请求提供服务,取而代之的是采用名为 HTTP.sys 的Windows内核模式设备驱动程序实现,它并不处理它所接收到的请求而是将侦听到的HTTP 请求传送到正在运行网站的用户模式进程中(这里不再是aspnet_wp.exe,而是名为W3wp.exe 的可执行文件),在该辅助进程中加载了相应的ISAPI和.Net 运行时,这样就便于ISAPI模块与.Net 运行时环境的数据通信,从而避免了因进程间通讯所带来的损耗。

      那么,当HTTP请求由ASP.NET ISAPI 扩展传送到.Net 运行时后,发生了什么事呢?

      首先由ISAPIRuntime实例调用ProcessRequest方法。这个方法接收一个ECB(非托管对象,其中包含着所有底层的请求信息如服务器变量,HTTP输入流,HTTP输出流等)和一个服务器类型参数 iWRType(这个参数用于指定创建何种版本的ISAPIWorkerRequest),然后把它传给了ISAPIWorkerRequest对象,这个对象负责创建一个表示当前请求的HttpWorkerRequest对象。其中ISAPIWorkerRequest是一个继承自HttpWorkerRequest的抽象类,针对不同的IIS版本,具有不同的ISAPIWorkerRequest子类,比如:ISAPIWorkerRequestOutOfProc(IIS 5.x),ISAPIWorkerRequestInProcForIIS6 ,ISAPIWorkerRequestInProcForIIS7。ProcessRequest通过ISAPI传入的 iWRType 来创建不同HttpWorkerRequest,从而屏蔽了不同IIS的差异,这样后续的操作就不需要考虑这种差异了。

public   int  ProcessRequest(IntPtr ecb,  int  iWRType)
{
    
int num;
    
try
    
{
        HttpWorkerRequest wr 
= ISAPIWorkerRequest.CreateWorkerRequest(ecb, iWRType);
        
string appPathTranslated = wr.GetAppPathTranslated();
        
string appDomainAppPathInternal = HttpRuntime.AppDomainAppPathInternal;
        
if ((appDomainAppPathInternal == null|| StringUtil.EqualsIgnoreCase(appPathTranslated, appDomainAppPathInternal))
        
{//这里开始便把请求的处理流程就交给了HttpRuntime
            HttpRuntime.ProcessRequestNoDemand(wr);
        
//在ProcessRequestNoDemand方法中所有的HTTP请求会被排成一个队列,顺次执行,并且最终会引发ProcessRequestInternal(HttpWorkerRequest wr)方法
            return 0;
        }

        HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString(
"Hosting_Phys_Path_Changed"new object[] { appDomainAppPathInternal, appPathTranslated }));
        num 
= 1;
    }

    
catch (Exception exception)
    
{
        Misc.ReportUnhandledException(exception, 
new string[] { SR.GetString("Failed_to_process_request") });
        
throw;
    }

    
return num;
}
 

         由上面的代码中大家可以看出最终是调用了ProcessRequestInternal方法,这个方法很重要,我们来看一下它主要做了些什么。

 

ProcessRequestInternal

 

      1. 首先检查当前HttpRuntime实例是否第一次被调用,如果是第一次调用则通过FirstRequestInit函数创建并初始化核心对象HttpContext。
          HttpContext 类包含特定于当前应用程序请求的对象,如 HttpRequest 和 HttpResponse 对象。
HttpRequest 对象包含有关当前请求的信息,包括 Cookie 和浏览器信息。HttpResponse 对象包含发送到客户端的响应,包括所有呈现的输出和 Cookie。     
      2.调用HttpResponse.InitResponseWriter函数初始化页面请求的返回对象HttpWorkerRequest.Response。

      3.通过调用HttpApplicationFactory.GetApplicationInstance创建HttpApplication 类的实例并最终调用HttpApplication实例的InitInternal方法启动应用程序。
     
      HttpApplication实例在InitInternal调用后,首先初始化Web.Config中注册的所有模块(HttpModule事件处理器,这个大家要尤其记住,因为我们真正开始编写代码的话,主要就是在这个模块中编写),然后调用HttpApplication 类的 Init 方法。接着会依次引发以下事件。

1.引发 BeginRequest 事件,该事件始终是请求处理期间发生的第一个事件。
2.引发 AuthenticateRequest 事件。
3.引发 PostAuthenticateRequest 事件。
4.引发 AuthorizeRequest 事件。
5.引发 PostAuthorizeRequest 事件。
6.引发 ResolveRequestCache 事件。
7.引发 PostResolveRequestCache 事件。
8.根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现 IHttpHandler 的类,对请求进行处理。如果该请求针对从 Page 类派生的对象(页),并且需要对该页进行编译,则 ASP.NET 会在创建该页的实例之前对其进行编译。
9.引发 PostMapRequestHandler 事件。
10.引发 AcquireRequestState 事件。
11.引发 PostAcquireRequestState 事件。
12.引发 PreRequestHandlerExecute 事件。
13.为该请求调用合适的 IHttpHandler 类的 ProcessRequest 方法(或异步版 BeginProcessRequest)。例如,如果该请求针对某页,则当前的页实例将处理该请求。
14.引发 PostRequestHandlerExecute 事件。 15.引发 ReleaseRequestState 事件。
16.引发 PostReleaseRequestState 事件。
17.如果定义了 Filter 属性,则执行响应筛选。
18.引发 UpdateRequestCache 事件。
19.引发 PostUpdateRequestCache 事件。
20.引发 EndRequest 事件。


        由上面第8个事件可以看出,页面在这个步骤被编译并创建了当前所请求的ASP.NET页面的实例(如果已经编译过,直接从缓存中加载)。 

最后再来回顾一下:

 

1.首先由inetinfo.exe接收到HTTP请求

2.检查脚本映射,然后调用 ASP.NET ISAPI 扩展

3.将消息送入aspnet_wp.exe进程,并加载运行时

4.调用ISAPIRuntime.ProcessRequest方法创建HttpWorkerRequest对象

6.创建HttpContext请求上下文

7.创建HttpApplication 类的实例

8.初始化Web.Config中注册的所有模块(HttpModule)

9.调用HttpApplication 类的 Init 方法

10.触发事件,实例化页面


      由于知识点掌握不够,写这篇文章花了很长时间,但也让我深入地学习了下.Net运行时方面的知识。文章可能写得并不完善,因为所查找的资料在有些内容上有些小的出入,所以欢迎朋友们提出意见,

      下一篇

你可能感兴趣的:(asp.net)