MVC之前的那点事儿系列(3):HttpRuntime详解分析(下)
文章内容
话说,经过各种各样复杂的我们不知道的内部处理,非托管代码正式开始调用ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime继承了IISPAIRuntime接口,该接口可以和COM进行交互,并且暴露了ProcessRequest接口方法)。至于为什么要调用这个方法,大叔也不太清楚,找不到微软相关的资料哦。但大叔确定该方法就是我们进入HttpRuntime的正式大门,接着看吧。
public int ProcessRequest(IntPtr ecb, int iWRType) { IntPtr pHttpCompletion = IntPtr.Zero; if (iWRType == WORKER_REQUEST_TYPE_IN_PROC_VERSION_2) { pHttpCompletion = ecb; ecb = UnsafeNativeMethods.GetEcb(pHttpCompletion); } ISAPIWorkerRequest wr = null; try { bool useOOP = (iWRType == WORKER_REQUEST_TYPE_OOP); wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP); wr.Initialize(); // check if app path matches (need to restart app domain?) String wrPath = wr.GetAppPathTranslated(); String adPath = HttpRuntime.AppDomainAppPathInternal; if (adPath == null || StringUtil.EqualsIgnoreCase(wrPath, adPath)) { HttpRuntime.ProcessRequestNoDemand(wr); return ; } else { // need to restart app domain HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString(SR.Hosting_Phys_Path_Changed, adPath, wrPath)); return ; } } catch(Exception e) { try { WebBaseEvent.RaiseRuntimeError(e, this); } catch {} // Have we called HSE_REQ_DONE_WITH_SESSION? If so, don't re-throw. if (wr != null && wr.Ecb == IntPtr.Zero) { if (pHttpCompletion != IntPtr.Zero) { UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion); } // if this is a thread abort exception, cancel the abort if (e is ThreadAbortException) { Thread.ResetAbort(); } // IMPORTANT: if this thread is being aborted because of an AppDomain.Unload, // the CLR will still throw an AppDomainUnloadedException. The native caller // must special case COR_E_APPDOMAINUNLOADED(0x80131014) and not // call HSE_REQ_DONE_WITH_SESSION more than once. return ; } // re-throw if we have not called HSE_REQ_DONE_WITH_SESSION throw;
}
}
第一个注意到的就是该方法的IntPtr类型的参数ecb,ecb是啥?ecb是一个非托管的指针,全称是Execution Control Block,在整个Http Request Processing过程中起着非常重要的作用,我们现在来简单介绍一个ECB。
非托管环境ISAPI对ISAPIRuntime的调用,需要传递一些必须的数据,比如ISAPIRuntime要获取Server Variable的数据,获取通过Post Mehod传回Server的数据;以及最终将Response的内容返回给非托管环境ISAPI,然后呈现给Client用户。一般地ISAPIRuntime不能直接调用ISAPI,所以这里就通过一个对象指针实现对其的调用,这个对象就是ECB,ECB实现了对非托管环境ISAPI的访问。
还有一点特别需要强调的是,ISAPI对ISAPIRutime的调用是异步的,也就是说ISAPI调用ISAPIRutime之后立即返回。这主要是出于Performance和Responsibility考虑的,因为ASP.NET Application天生就是一个多线程的应用,为了具有更好的响应能力,异步操作是最有效的解决方式。但是这里就会有一个问题,我们知道我们对ASP.NET 资源的调用本质上是一个Request/Response的Message Exchange Pattern,异步调用往往意味着ISAPI将Request传递给ISAPIRuntime,将不能得到ISAPIRuntime最终生成的Response,这显然是不能接受的。而ECB解决了这个问题,ISAPI在调用ISAPIRutime的ProcessRequest方法时会将自己对应的ECB的指针传给它,ISAPIRutime不但可以将最终生成的Response返回给ISAPI,还能通过ECB调用ISAPI获得一些所需的数据。
上述代码里第2个加粗的代码是执行ISAPIWorkerRequest的静态方法CreateWorkerRequest从而创建ISAPIWorkerRequest对象实例,参数分别为ecb和代表WorkerRequest类型的int参数iWRType,让我们来看看这个方法的代码:
internal static ISAPIWorkerRequest CreateWorkerRequest(IntPtr ecb, bool useOOP) { ISAPIWorkerRequest wr = null; if (useOOP) { EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero); if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, false); wr = new ISAPIWorkerRequestOutOfProc(ecb); } else { int version = UnsafeNativeMethods.EcbGetVersion(ecb) >> ; if (version >= ) { EtwTrace.TraceEnableCheck(EtwTraceConfigType.IIS7_ISAPI, ecb); } else { EtwTrace.TraceEnableCheck(EtwTraceConfigType.DOWNLEVEL, IntPtr.Zero); } if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_APPDOMAIN_ENTER, ecb, Thread.GetDomain().FriendlyName, null, true); if (version >= ) { wr = new ISAPIWorkerRequestInProcForIIS7(ecb); } else if (version == ) { wr = new ISAPIWorkerRequestInProcForIIS6(ecb); } else { wr = new ISAPIWorkerRequestInProc(ecb); }
}
return wr;
}
通过判断ecb和type类型的具体内容,来决定创建什么类型的WorkerRequest(上述类型的ISPAIWorkerRequest都继承于HttpWorkerRequest),上面的代码可以看出对不同版本的IIS进行了不同的包装,通过其Initialize方法来初始化一些基本的信息(比如:contentType, querystring的长度,filepath等相关信息)。
OK,继续看ProcessRequest方法的加粗代码,激动人心的时刻来了,看到HttpRuntime.ProcessRequestNoDemand(wr)这行代码了么?这就是真正进入了ASP.NET Runtime Pipeline的唯一入口,传递的参数是上面屏蔽了差异化以后的WorkerRequest对象实例。HttpRuntime.ProcessRequestNoDemand最终体现在调用ProcessRequestInternal方法上,让我们来看看该方法都是做了什么事情。
private void ProcessRequestInternal(HttpWorkerRequest wr) { // Construct the Context on HttpWorkerRequest, hook everything together HttpContext context; try { context = new HttpContext(wr, false /* initResponseWriter */);
}
catch { // If we fail to create the context for any reason, send back a 400 to make sure // the request is correctly closed (relates to VSUQFE3962) wr.SendStatus(, "Bad Request"); wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType, "text/html; charset=utf-8"); byte[] body = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>"); wr.SendResponseFromMemory(body, body.Length); wr.FlushResponse(true); wr.EndOfRequest(); return; } wr.SetEndOfSendNotification(_asyncEndOfSendCallback, context); // Count active requests Interlocked.Increment(ref _activeRequestCount); HostingEnvironment.IncrementBusyCount(); try {
// First request initialization
try {
EnsureFirstRequestInit(context);
}
catch {
// If we are handling a DEBUG request, ignore the FirstRequestInit exception.
// This allows the HttpDebugHandler to execute, and lets the debugger attach to
// the process (VSWhidbey 358135)
if (!context.Request.IsDebuggingRequest) {
throw;
}
} // Init response writer (after we have config in first request init)
// no need for impersonation as it is handled in config system
context.Response.InitResponseWriter(); // Get application instance
IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context); if (app == null)
throw new HttpException(SR.GetString(SR.Unable_create_app_object)); if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, app.GetType().FullName, "Start"); if (app is IHttpAsyncHandler) {
// asynchronous handler IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler)app;
context.AsyncAppHandler = asyncHandler;
asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context);
}
else {
// synchronous handler
app.ProcessRequest(context);
FinishRequest(context.WorkerRequest, context, null);
}
}
catch (Exception e) {
context.Response.InitResponseWriter();
FinishRequest(wr, context, e);
}
}
首先映入眼帘的是try/catch里的HttpContext对象的实例化代码,这就是我们期待已久的全局HttpContext对象产生的地方,参数依然是WorkerRequest的实例,HttpContext构造函数代码如下:
// ctor used in HttpRuntime internal HttpContext(HttpWorkerRequest wr, bool initResponseWriter) { _wr = wr; Init(new HttpRequest(wr, this), new HttpResponse(wr, this)); if (initResponseWriter)
_response.InitResponseWriter(); PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_EXECUTING);
}
我们又看到了2个惊喜的代码,HttpRequest和HttpResponse的实例化,通过对WorkerRequest和对HttpContext对象this参数的传递,将获取各自需要的信息,具体内部是怎么判断操作赋值的,我们就不仔细看了,另外再花2秒钟看一下,catch里面的代码,有我们经常看到的Bad Request页面显示的HTML代码组装逻辑,也就是说如果HttpContext对象创建失败的话,就会给我们显示Bad Request页面。
我们继续更重要的代码,这又是另外一个入口,让我们进入我们熟悉的HttpApplication,代码如下:
// Get application instance IHttpHandler app = HttpApplicationFactory.GetApplicationInstance(context);
通过HttpApplicationFactory的GetApplicationInstance静态方法,获取我们熟悉的HttpApplication对象实例,由于HttpApplication对象是继承IHttpAsyncHandler,而IHttpAsyncHandler又继承于IHttpHandler,所以上面app的类型是IHttpHandler是没有错的。继续看后面的if (app is IHttpAsyncHandler)代码,就知道了app肯定走这里的分支,然后执行调用asyncHandler.BeginProcessRequest方法了。
至此,HttpRuntime已经正式发挥其无可替代的作用了,也正式通过此对象正式进入了HttpApplication对象的创建以及大家熟知的HttpApplication以后的生命周期了。
参考资料:
http://dotnetslackers.com/articles/iis/ASPNETInternalsIISAndTheProcessModel2.aspx
http://msdn.microsoft.com/en-us/library/bb470252.aspx
http://msdn.microsoft.com/en-us/library/Aa479328.aspx
http://learn.iis.net/page.aspx/101/introduction-to-iis-architecture/
http://www.cnblogs.com/zhaoyang/archive/2011/11/16/2251200.html
http://www.dotnetfunda.com/articles/article821-beginners-guide-how-iis-process-aspnet-request.aspx
同步与推荐
本文已同步至目录索引:MVC之前的那点事儿系列
MVC之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。
MVC之前的那点事儿系列(3):HttpRuntime详解分析(下)的更多相关文章
- MVC之前的那点事儿系列(2):HttpRuntime详解分析(上)
文章内容 从上章文章都知道,asp.net是运行在HttpRuntime里的,但是从CLR如何进入HttpRuntime的,可能大家都不太清晰.本章节就是通过深入分析.Net4的源码来展示其中的重要步 ...
- MVC之前的那点事儿系列
MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园 ...
- MVC之前的那点事儿 ---- 系列文章
MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园 ...
- MVC之前的那点事儿系列(10):MVC为什么不再需要注册通配符(*.*)了?
文章内容 很多教程里都提到了,在部署MVC程序的时候要配置通配符映射(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前确实应该这么多,但是.NET4.0之后已经不要再费事了 ...
- MVC之前的那点事儿系列(8):UrlRouting的理解
文章内容 根据对Http Runtime和Http Pipeline的分析,我们知道一个ASP.NET应用程序可以有多个HttpModuel,但是只能有一个HttpHandler,并且通过这个Http ...
- MVC之前的那点事儿系列(6):动态注册HttpModule
文章内容 通过前面的章节,我们知道HttpApplication在初始化的时候会初始化所有配置文件里注册的HttpModules,那么有一个疑问,能否初始化之前动态加载HttpModule,而不是只从 ...
- MVC之前的那点事儿系列(9):MVC如何在Pipeline中接管请求的?
文章内容 上个章节我们讲到了,可以在HttpModules初始化之前动态添加Route的方式来自定义自己的HttpHandler,最终接管请求的,那MVC是这么实现的么?本章节我们就来分析一下相关的M ...
- MVC之前的那点事儿系列(7):WebActivator的实现原理详解
文章内容 上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivator提供了3种功能,允许我们分别在Http ...
- MVC之前的那点事儿系列(5):Http Pipeline详细分析(下)
文章内容 接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码: public class ...
随机推荐
- Lucene系列-搜索
Lucene搜索的时候就要构造查询语句,本篇就介绍下各种Query.IndexSearcher是搜索主类,提供的常用查询接口有: TopDocs search(Query query, int n); ...
- java 堆栈分析3
很多方式,比如jconsole.jvisualvm,或者jstack -as 这样的形式, 都可以看到实时的java堆栈的变化: eden suvirried0 suvirried1 old perg ...
- C-Lodop 非典型应用
Lodop是什么? 有人说她是报表打印工具,因为那个add_print_table语句把报表统计的那点事弄了个明明白白: 有人说她是条码打印工具,因为用了她再也不用后台生成条码图片了,前端一行指令就动 ...
- Js~(function(){})匿名自执行方法的作用
匿名自执行方法体(function(){})经常用在设计JS插件上面,它定义相关组件的行为,自动初始化相关属性,而且在页面中可以直接执行,你不需要手动执行它,它被自动被执行! 在设计你的匿名自执行方法 ...
- 锋利的JQuery —— JQuery性能优化
大图猛戳
- Oracle使用小记
windows下Oracle必须要启动的服务 Oracle ORCL VSS Writer Service:Oracle卷映射拷贝写入服务,VSS(Volume Shadow Copy Service ...
- ServletConfig对象详解
在Servlet 的配置文件中,可以用一个或多个<init-param>标签为servlet配置一些初始化参数. 当servlet配置了初始化参数之后,web容器在创建servlet实例对 ...
- pycharm运行脚本为何不生成测试报告?
今日使用python+selenium编写自动化测试脚本并执行过程中,使用pycharm运行结果后发现脚本运行无报错,脚本中的操作也正常被执行,但就是没有生成测试报告. 为什么呢,为什么呢,生成测试报 ...
- MIME参考列表
定义 MIME(multipurpose internet mail extensions)多用途互联网邮件扩展类型是描述消息内容类型的因特网标准.MIME消息能包含文本.图像.音频.视频以及其他应用 ...
- nyoj 925 国王的烦恼(最小生成树)
/* 题意:N个城市中每两个城市有多条路径连接,可是因为路径存在的天数是有限的!以为某条路经不存在了 导致N个城市不能连通了,那么村名们就会抗议!问一共会有多少次抗议! 思路:最小生成树....我们用 ...