ASP.NET那点不为人知的事(一)

 

我们上网时,在浏览器地址输入网址:Http://www.cnblogs.com,按下回车,一张网页就呈现在我们眼前。这究竟发生了什么?对于一名优秀的Programmer来说,我想有必要一下熟悉浏览器--->服务器请求的过程。

ASP.NET

ASP.NET是运行在公共语言运行时刻时(CLR)上的应用程序框架。他用来在服务器端构建功能强大的web应用程序。当浏览器请求 ASP.NET 文件时,IIS 会把该请求传递给服务器上的 ASP.NET 引擎,ASP.NET 引擎会逐行地读取该文件,并执行文件中的脚本,最后,ASP.NET 文件会以纯 HTML 的形式返回浏览器。

客户端浏览器和服务器之间的请求响应是通过Socket进行通信,基于HTTP协议,客户端发送一次HTTP请求,服务器接收到请求,处理之后向浏览器回应响应报文。那么什么是HTTP协议呢?

HTTP协议:

当浏览器寻找到Web服务器地址后,浏览器将帮助我们把对服务器的请求转换为一系列参数(消息)发给Web服务器,浏览器和Web服务器的对话中,需要使用双方都能理解语法规范进行通信,这种程序之间进行通信的语法规定,我们称之为协议。浏览器与服务器之间的协议是应用层协议,当前遵循的协议是HTTP/1.1。HTTP/1.1协议时Web开发的基础,这是一个无状态协议,客户端浏览器和服务器通过Socket通信进行请求和响应完成一次会话。每次会话中,通信双方发送的数据称为消息,分为两种:请求消息和响应消息。

对于消息而言,一般他有三部分组成,并且消息的头和消息体之间用一个空行进行分隔:

我们通过浏览器插件HttpWatch Professional可以清晰看到浏览器和服务器之间的通信内容:

了解了什么是HTTP协议之后,我们在回到先前提出的那个问题,浏览器的请求怎样到达服务器?

HTTP.SYS组件

我们知道要访问一个网站,必须要其部署在相应服务器软件上(如IIS),浏览器向服务器发送请求之后,当请求通过Socket到达服务器时,首先服务器Windows内核中的HTTP.SYS组件捕获请求,根据URL的请求地址将其转发到应用程序池(Application Pool,ASP.NET应用程序必须运行在一个应用程序池中),再由运行在应用程序池里的工作者进程(Worker Process,用于装载专门处理ASP.NET页面的一个ISAPI扩展程序:aspnet_isapi.dll)响应请求,当请求处理完成时,HTTP.SYS又将结果发送出去(HTTP.SYS会在内部建立一个缓存区,用于缓存近期的处理结果)。当HTTP.SYS请求分析这是一个需要交给IIS服务器处理的HTTP请求时,HTTP.SYS组件就会把这次请求交给IISl处理,服务器软件(IIS)会判断用户请求的是静态页面(Html)还是动态页面(Aspx.Ashx),如果请求的是Html静态页面或者js,css,xml以及图片等,IIS直接返回请求的Html静态页面和js等相应文件。那么如果请求的是动态页面呢?还是向处理静态页面一样吗?显然是不可能的。IIS服务器会分析请求的类型,然后从处理程序映射(即下文IIS服务器扩展)表中去匹配,当在处理程序映射表中能够匹配到请求的类型时,那么IIS服务器就将请求交给处理程序映射表中所对应的程序来处理。当IIS发现,在处理程序映射表中没有能匹配的项的时候,就直接返回请求所对应物理路径下的文件,如Html,JS,CSS,JPG,PNG等。

IIS服务器扩展

由于IIS服务器在设计时引入了开放的ISAPI接口标准,具备极高的可扩展性。在核心组件不变的情况下可灵活支持不同类型不同版本的ASP.NET应用程序。

ISAPI(Internet Server Application Programming Interface)

ISAPI(服务器应用编程接口),它为开发人员提供了强大的可编程能力,只要按照标准接口开发不同类型的Web应用程序的ISAPI扩展程序,就能实现对IIS功能上的扩展,从而使IIS可以处理不同类型的客户端请求。IIS管理器提供了应用程序配置功能,可以对不同的客户端请求配置不同的ISAPI扩展程序ISAPI扩展程序通常以DLL形式存在,可以被IIS加载并调用。有了基于ISAPI的扩展扩展程序,IIS服务器就可以根据客户端请求的资源扩展名,来决定应由哪个ISAPI扩展程序来处理客户端请求,然后就可以将请求转发给合适的ISAPI扩展程序。

IIS7处理程序映射

ASP.NET的后台辅助进程aspnet_wp.exe

实际上客户发起的请求最终要由aspnet_isapi.dll(被工作者进程Worker Process装载)传递给aspnet_wp.exe去处理,.NET平台下称其为ASP.NET Process(简称为WP),该文件位于.Net Framework安装目录下,与aspnet_isapi.dll所在位置相同。当aspnet_isapi接收到IIS转发的ASP.NET请求后,会将请求放入队列,并根据实际情况分配请求处理任务给WP进程。一旦请求被转送给WP进程,WP进程便会通知aspnet_isapi请求正在被处理。这个通知的过程是通过同步I/O完成的,这么实现目的是为了保证处理过程的完整性,因为只有当请求在aspnet_isapi内部被标记为"executing"后,WP才会真正开始处理该请求。此后请求便在WP的上下文环境中执行。当执行结束后处理结果会通过一个异步的开放管道回送给aspnet_isapi,这时请求的状态会被更新为“Done”。接着请求就会从队列中清除。如果WP进程崩溃,所有正在处理中的请求都将维持“executing”状态一段时间,等到aspnet_isapi检测到WP进程死掉后,会自动丢弃所有的请求并释放已经分配的资源。

WP会分析每一个请求的信息解析出其中的虚拟目录信息,并检查该虚拟目录对应的AppDomain(应用程序域)是否已经存在,如果不存在,则创建一个新的AppDomain(ApplicationManager创建应用程序域),然后使用它。否则直接重用已经建立的AppDomain对象。这里的AppDomain指的是.NET中引入的应用程序域的概念,程序集管理的最小逻辑单位为应用程序域,包括四个重要的机制,隔离、卸载、安全、配置,它可以理解为一个进程或一个边界或一个容器,它是应用程序的执行环境.NET下所有的应用程序都运行在AppDomain中,每一个ASP.NET应用程序IIS中的站点或者虚拟目录都会有一个AppDomain与之对应,它保存了Applcation对象、Cache等全局变量。

由一张流程图回顾上述浏览器到达服务器的过程

ISAPIRuntme.ProcessRequest方法第一个进入ASP.NET

当aspnet_wp.exe接受到aspnet_isapi.dll的请求后,就将请求转给指定虚拟目录对应的AppDomain中的ISAPIRuntime对象,ISAPIRuntime.ProcessRequest()开始进入ASP.NET,并将浏览器发送请求消息封装成HttpWorkerRequest类(抽象类,开发环境中对应SimpleWorkRequest)。之后再执行HttpRuntime的静态方法:ProcessRequestNoDemand(参数为封装了浏览器请求的信息:HttpWorkerRequest)

补充:默默无闻的工作者对象HttpWorkerRequest

在Asp.Net中,准备用于处理的请求,必须封装为HttpWorkerRequest类型的对象,这是一个抽象类:

[ComVisibleAttribute(false)]
public abstract class HttpWorkerRequest

客户的请求首先会被ISAPIRuntme对象ProcessRequest方法处理

创建了HttpWorkerRequest 类型的wr对象,因为ISAPIWorkerRequest 继承于HttpWorkerRequest

[SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
public int ProcessRequest(IntPtr ecb, int iWRType)
{
IntPtr zero = IntPtr.Zero;
if (iWRType == 2)
{
zero = ecb;
ecb = UnsafeNativeMethods.GetEcb(zero);
}
ISAPIWorkerRequest wr = null;
try
{
bool useOOP = iWRType == 1;
wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
wr.Initialize();
......
if ((appDomainAppPathInternal == null) || StringUtil.EqualsIgnoreCase(appPathTranslated, appDomainAppPathInternal))
{
HttpRuntime.ProcessRequestNoDemand(wr);
return 0;
}
HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString("Hosting_Phys_Path_Changed", new object[] { appDomainAppPathInternal, appPathTranslated }));
return 1;
}
......
}

HttpRuntime调用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);
}
}

该方法先从请求队列中取出一个请求,然后更新请求的引用计数器的信息,然后ProcessRequestNow方法处理请求。

在这儿终于找到了HttpRuntime这个对象了:

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

 _theRuntime就是HttpRuntime类型的对象,他在HttpRuntime的静态构造函数初始化。

static HttpRuntime()
{
......
_theRuntime = new HttpRuntime();
_theRuntime.Init();
AddAppDomainTraceMessage("HttpRuntime::cctor*");
}

点击进入ProcessRequsetNow(Wr)方法,Wr即封装了HTTP Message的HttpWorkRequest对象

在HttpRuntime接受到请求后,立刻通过HttpWorkerRequest传递的参数进行分析和分解,创建方便用户网站应用程序处理用的对象。HttpRequest,HttpResponse

终于发现了HttpContext,根据HttpWorkerRequest初始化HttpContext

private void ProcessRequestInternal(HttpWorkerRequest wr)
{
......
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;
}
}
......
}
}

在进入看看:根据WR,初始化了请求参数的类型HttpRequest对象和处理回应类型HttpReponse对象

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);
}

privatevoid ProcessRequestInternal(HttpWorkerRequest wr) ProcessRequestInternal这个方法很重要,前面分析了它创建了上下文对象HttpContext,接下来分析HttpApplication的创建。

{
.....
IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);
......
try
{
this.EnsureFirstRequestInit(context);
}
......
context.Response.InitResponseWriter();
......if (applicationInstance is IHttpAsyncHandler)
{
IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance;
context.AsyncAppHandler = handler2;
handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);
}
......
}
}
}
  • EnsureFirstRequestInit()方法完成第一次请求初始化工作,该方法锁定全局变量_beforeRequestFirst,然后调用FirstRequestInit(context)完成配置文件的加载,初始化请求队列,装载Bin目录下所有程序集工作,然后更新_beforeRequestFirst=false;context.FirstRequest=true;
private void EnsureFirstRequestInit(HttpContext context)
{
if (this._beforeFirstRequest)
{
lock (this)
{
if (this._beforeFirstRequest)
{
this._firstRequestStartTime = DateTime.UtcNow;
this.FirstRequestInit(context);
this._beforeFirstRequest = false;
context.FirstRequest = true;
}
}
}
}
  • 执行InitResponseWrite创建HttpWrite对象,用于写入结果返回信息。
  • 创建HttpApplication实例,HttpApplicationFactory.GetApplicationInstance(注意其实不是这个方法直接创建,而是通过这个方法里面又调用了GetNormalApplicationInstance方法来创建默认的HttpApplication实例
  • 那什么是HttpApplicationFactotry?
  • HttpApplicationFactotry用于负责管理一个HttpApplication的对象池。

看一下HttpApplication这个类的申明:

[ToolboxItem(false)]
public class HttpApplication : IComponent, IDisposable, IHttpAsyncHandler, IHttpHandler, IRequestCompletedNotifier, ISyncContext
{

}


调用HttpApplicationFactory对象的GetNormalApplicationInstance得到一个HttpApplication实例:

internal static IHttpHandler GetApplicationInstance(HttpContext context)
{
......return _theApplicationFactory.GetNormalApplicationInstance(context);
}

GetApplicationInstance方法生成一个默认的HttpApplication对象,HttpApplication实现了IHttpAsyncHandler接口。

调用HttpApplication对象(实现了IHttpAsyncHandler接口)的BeginProcessRequest方法执行客户请求。

if (applicationInstance is IHttpAsyncHandler)
{
IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance;
context.AsyncAppHandler = handler2;
handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);
}

OK,回到前一步,再深入一步,进入GetNormalApplicationInstance方法之后,我们看到了HttpApplication对象是如何被创建和初始化:

private HttpApplication GetNormalApplicationInstance(HttpContext context)
{
HttpApplication state = null;
......
if (state == null)
{
state = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType);
using (new ApplicationImpersonationContext())
{
state.InitInternal(context, this._state, this._eventHandlerMethods);
}
}
......
}

internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers)我们发现HttpApplication类提供了一个名为InitInternal的方法,调用它来完成HttpApplication实例的初始化工作,点击进入InitInternal方法内部:

internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers)
{ this._state = state;
PerfCounters.IncrementCounter(AppPerfCounter.PIPELINES); ......
this.InitModules();
Label_006B:
if (handlers != null)
{
this.HookupEventHandlersForApplicationAndModules(handlers);
}
......
.....
if (HttpRuntime.UseIntegratedPipeline)
{
this._stepManager = new PipelineStepManager(this);
}
else
{
this._stepManager = new ApplicationStepManager(this);
}
this._stepManager.BuildSteps(this._resumeStepsWaitCallback);
}
......
}

首先初始化Modules(InitModules)

private void InitModules()
{
HttpModuleCollection modules = RuntimeConfig.GetAppConfig().HttpModules.CreateModules();
HttpModuleCollection other = this.CreateDynamicModules();
modules.AppendCollection(other);
this._moduleCollection = modules;
this.InitModulesCommon();
}

接下来完成事件的绑定(19个管道事件):BuildSteps: 

          if (HttpRuntime.UseIntegratedPipeline)
{
this._stepManager = new PipelineStepManager(this);
}
else
{
this._stepManager = new ApplicationStepManager(this);
}
this._stepManager.BuildSteps(this._resumeStepsWaitCallback);
}
......

BuildSteps完成HttpApplication19个管道事件的注册:

internal override void BuildSteps(WaitCallback stepCallback)
{
ArrayList steps = new ArrayList();
HttpApplication app = base._application;
bool flag = false;
UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings;
flag = urlMappings.IsEnabled && (urlMappings.UrlMappings.Count > 0);
steps.Add(new HttpApplication.ValidateRequestExecutionStep(app));
steps.Add(new HttpApplication.ValidatePathExecutionStep(app));
if (flag)
{
steps.Add(new HttpApplication.UrlMappingsExecutionStep(app));
}
app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps);
app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps);
steps.Add(new HttpApplication.MapHandlerExecutionStep(app));//---------------------->
app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps);
app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps);
app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps);
steps.Add(app.CreateImplicitAsyncPreloadExecutionStep());
steps.Add(newHttpApplication.CallHandlerExecutionStep(app));//---------------------->用于创建处理用户请求的对象(Handler)
private void CreateEventExecutionSteps(object eventIndex, ArrayList steps)
{
AsyncAppEventHandler handler = this.AsyncEvents[eventIndex];
if (handler != null)
{
handler.CreateExecutionSteps(this, steps);
}
EventHandler handler2 = (EventHandler) this.Events[eventIndex];
if (handler2 != null)
{
Delegate[] invocationList = handler2.GetInvocationList();
for (int i = 0; i < invocationList.Length; i++)
{
steps.Add(new SyncEventExecutionStep(this, (EventHandler) invocationList[i]));
}
}
}

在HttpApplication对象初始化时,首先会调用InitModules方法来加载在web.config文件中配置的所有HttpModule模块。

接着HookupEventHandlersForApplicationAndModules方法被调用,这个方法完成global.asax文件中配置的HttpModule或HttpApplication事件的绑定

最后ApplicationStopManager对象的BuildSteps方法被调用,完成HttpApplication19个管道事件的注册。这个方法很重要,它将创建各种HttpApplication.IExecutionStep保存到一个数组列表:

internal override void BuildSteps(WaitCallback stepCallback)
{
.....
this._execSteps = new HttpApplication.IExecutionStep[steps.Count];
steps.CopyTo(this._execSteps);
.....
}

以便在BeginProcessRequest方法内部调用ResumeSteps方法依次执行这些对象的Execute()方法,完成各种处置。

调用BeginProcessRequest方法来实现IHttpAsyncHandler接口中定义的方法处理请求:

IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
this._context = context;
this._context.ApplicationInstance = this;
this._stepManager.InitRequest();
this._context.Root();
HttpAsyncResult result = new HttpAsyncResult(cb, extraData);
this.AsyncResult = result;
if (this._context.TraceIsEnabled)
{
HttpRuntime.Profile.StartRequest(this._context);
}
this.ResumeSteps(null);//---------->依次执行管道事件
return result;
}

BeginProcessRequest执行过程

  • 在取得HttpApplication对象实例之后HttpRuntime对象开始调用其的BeginProcessRequest方法来实现IHttpAsyncHandler接口中定义的方法处理请求:
  • 该方法首先调用ApplicationStepManager对象的InitRequest方法完成一些初始化工作例如将记录当前执行步骤的变量清0、置请求处理完成标志为false等。
  • 然后根据上下文创建HttpAsyncResult对象记录执行结果最后ResumeSteps方法被调用这个方法会依次取出在数组列表中的HttpApplication.IExecutionStep对象传递给HttpApplication的ExecuteStep方法由它调用执行IExecutionStep对象的Execute方法。
  • 当执行到MapHandlerExecutionStep时会执行如下代码获取最终执行请求:context.Handler =this._application.MapHttpHandler()。HttpApplication对象的MapHttpHandler方法将根据配置文件结合请求类型和URL以调用相应的IHttpHandlerFactory来获取HttpHandler对象。例如与.aspx页面对应的Page类就是一种HttpHandler。此后请求处理的执行权被转交至对应的HttpHandler对象上。下面代码演示了过程:
    void HttpApplication.IExecutionStep.Execute()
{
HttpContext context = this._application.Context;
HttpRequest request = context.Request;
if (EtwTrace.IsTraceEnabled(5, 1))
{
EtwTrace.Trace(EtwTraceType.ETW_TYPE_MAPHANDLER_ENTER, context.WorkerRequest);
}
context.Handler = this._application.MapHttpHandler(context, request.RequestType, request.FilePathObject, request.PhysicalPathInternal, false);
if (EtwTrace.IsTraceEnabled(5, 1))
{
EtwTrace.Trace(EtwTraceType.ETW_TYPE_MAPHANDLER_LEAVE, context.WorkerRequest);
}
}

 这儿调用了一个很重要的方法MapHttpHandler:

 context.Handler = this._application.MapHttpHandler(context, request.RequestType, request.FilePathObject, request.PhysicalPathInternal, false);
internal IHttpHandler MapHttpHandler(HttpContext context, string requestType, VirtualPath path, string pathTranslated, bool useAppConfig)
{
IHttpHandler handler = (context.ServerExecuteDepth == 0) ? context.RemapHandlerInstance : null;
...
IHttpHandlerFactory factory = this.GetFactory(mapping);
try
{
IHttpHandlerFactory2 factory2 = factory as IHttpHandlerFactory2;
if (factory2 != null)
{
handler = factory2.GetHandler(context, requestType, path, pathTranslated);
}
else
{
handler = factory.GetHandler(context, requestType, path.VirtualPathString, pathTranslated);
}
}
...
....
}
return handler;
}

通过实现了IHttpHandlerFactory(PageHandlerFactory 或者 SimpleHandlerFactory等)创建了HttpHandler

 因为steps.Add(new HttpApplication.MapHandlerExecutionStep(app))注册了Handler,所以会在第八个事件里通过反射创建了页面请求的对象(实现了IHttpHandler接口)。
void HttpApplication.IExecutionStep.Execute()
{
HttpContext context = this._application.Context;
IHttpHandler handler = context.Handler;
.....
...
IHttpAsyncHandler handler2 = (IHttpAsyncHandler) handler;
this._sync = false;
this._handler = handler2;
....
}
然后再第11个和12个事件之间,会调用了第八个事件创建的页面对象的ProcessRequest方法,具体内容详看我下一篇文章:《ASP.NET那点不为人知的事(二)》

  
补充:BuildSteps方法里注册的HttpApplication管道的19个事件:

19个事件的处理过程:

  • 在Asp.Net中,Asp.Net服务器对于每一次请求的处理过程是相同的,都要经过HttpApplication处理管道,管道内部的处理过程是固定的,在服务器处理请求的各个阶段,伴随着处理的进行,一次触发对应的事件,以便程序员在处理的各个阶段完成自定义的处理工作。

  • 首先触发的事件是BeginRequest,这个事件标志着ASP.NET服务器处理工作的开始,也是程序员在ASP.NET中针对请求能够处理的第一个事件。

  • 开始处理请求后,第一个重要的工作就是确定请求用户的身份以及实现安全机制。这个工作通过AuthenticateRequest和PostAuthenticateRequest两个事件提供检查当前请求用户身份的机会。PostAuthenticateRequest则表示用户身份已经检查完成,检查后的用户可以通过HttpContext的User属性获取列。

public IPrincipal User
{
get
{
return this._principalContainer.Principal;
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries"), SecurityPermission(SecurityAction.Demand, ControlPrincipal=true)]
set
{
this.SetPrincipalNoDemand(value);
}
}

Iprincipal又有一个名为Identity,类型了System.Security.Principal.IIdentity属性

[ComVisible(true), __DynamicallyInvokable]
public interface IPrincipal
{
// Methods
[__DynamicallyInvokable]
bool IsInRole(string role); // Properties
[__DynamicallyInvokable]
IIdentity Identity { [__DynamicallyInvokable] get; }
}
[ComVisible(true), __DynamicallyInvokable]
public interface IIdentity
{
// Properties
[__DynamicallyInvokable]
string AuthenticationType { [__DynamicallyInvokable] get; }
[__DynamicallyInvokable]
bool IsAuthenticated { [__DynamicallyInvokable] get; }
[__DynamicallyInvokable]
string Name { [__DynamicallyInvokable] get; }
}
IsAuthenticated表示当前请求用户是否已经被验证,IsAuthenticated =false,那么表示这是一个匿名用户,如果为True,那么通过IIdentity类型为string的Name属性,
这就表示当前请求的用户名。
  • 当ASP.NET获取用户身份后,根据当前请求的用户身份,开始请求权限的检查工作。当第四个事件AuthorizeRequest触发的时候开始进行用户的权限检查,而第五个事件PostAuthorizeRequest则标志已经完成用户权限检查工作。如果用户没有通过安检,一般情况下将跳过剩余事件,直接触发EndRequest事件结束处理请求过程。

 

  • 当用户获取了请求权限,那么服务器开始准备用最快的方式来使用户得到回应结果。ResolveRequestCache事件标志着到从前缓存的结果进行检查,看看是否可以直接从以前的缓存结果中直接获取处理结果,PostResolveRequestCache表示缓存检查结束。

 

  • 当不能从缓存中获取结果时,必须通过一次处理来计算出当前请求的结果。在ASP.NET中,用户处理请求以得到结果的对象称为处理程序Handler。为了处理这个这个请求,ASP.NET必须按照匹配规则找到一个处理当前请求的处理程序,PostMapRequestHandler事件表示当前ASP.NET已经获取了这个处理程序,HttpContext的Handler属性就表示这个处理程序对象。

 

  • 得到了处理程序之后,还不能马上开始进行处理,这是由于处理请求还需要与这个请求有关的数据,比如说这个用户上一次向服务器发送请求的时候,在服务器上报错了一些这个用户特有的数据。由于HTTP协议的无状态性,状态管理问题是个核心问题,所以ASP时代就引入Session,提供基于会话状态的管理。为了获取这个用户在以前保存的数据,通过AcquireRequestState事件取得请求状态,PostAcquireRequest事件则表示已经完成了用户数据的获取工作,可以再处理中使用了。

  • PreRequestHandlerExcute事件用来通知程序员,处理程序就要开始进行处理工作了,如果用户的状态已经获取之后,还有需要的处理程序之进行的工作,那么就在这个事件中处理吧。在PreRequestHandlerExcute事件之后,ASP.NET服务器将通过执行处理程序完成请求处理工作。这个处理程序有可能是一个WebForm,也可能是Web服务。这个工作是在第11个事件和第12个事件之间完成的。

 

  • 处理程序之后,服务器开始进行扫尾工作,PostRequestHandlerExcute事件通知程序员,ASP.NET服务器处理程序已经完成。

  • 在处理完成之后,由于处理程中,用户可能修改了用于特定的专属数据,那么修改之后的用户状态数据需要进行序列化或者进行保存处理。ReleaseRequestState事件通知程序员需要释放这些状态数据,PostReleaseRequestState则表示已经释放完成。

 

  • 在处理完成之后,如果需要将这次处理结果缓存起来,以便于后继的请求可以直接使用这个结果,UpdateRequestCache事件提供了处理的机会,PostUpdateRequestCache则表示缓存已经更新完毕。

  • 在ASP.NET4.0中,新增加了两个事件完成处理的日志工作:LogRequest表示将这次请求加入日志,PostLogRequest表示完成了日志工作。

  • 在前面的事件中,请求并不一定要经过所有的事件,比如说,用户没用经过授权的检查,那么将跳过后面的事件,但是,EndRequest事件是所有请求都要经过的最后一个HttpApplication处理管道的事件,也是程序员处理的ASP.NET处理请求中的最后一个机会。这个事件之后,处理的结果将被回应到浏览器,完成ASP.NET服务器的处理工作。

小结

未完,待续。

Response.Redirect引起的“无法在发送HTTP标头之后进行重定向”

 

博客后台切换至i.cnblogs.com之后,在日志中发现大量的“无法在发送HTTP标头之后进行重定向”(Cannot redirect after HTTP headers have been sent)的错误信息。

检查代码发现问题是由下面的代码触发的:

IHttpHandler IHttpHandlerFactory.GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
context.Response.Redirect("http://i.cnblogs.com/" +
context.Request.RawUrl.Substring(context.Request.RawUrl.LastIndexOf("/") + 1)); //后续也有context.Response.Redirect代码
//...
return PageParser.GetCompiledPageInstance(newurl, path, context);
}

“无法在发送HTTP标头之后进行重定向”问题来源于Response.Redirect之后,又进行了Response.Redirect。

解决方法很简单:在Response.Redirect之后立即返回。

IHttpHandler IHttpHandlerFactory.GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
context.Response.Redirect("http://i.cnblogs.com/" +
context.Request.RawUrl.Substring(context.Request.RawUrl.LastIndexOf("/") + 1));
return null;
//...
}

为什么之前没有加return null呢?因为以前一直以为Response.Redirect会结束当前请求,不会执行Response.Redirect之后的代码。

现在残酷的现实说明了不完全是这样的,那问题背后的真相是什么?让我们来一探究竟。

由于微软公开了.NET Framework的源代码,现在无需再看Reflactor出来的代码,可以直接下载源代码用Visual Studio进行查看。

.NET Framework源代码下载链接:http://referencesource.microsoft.com/download.html (相关新闻:微软开放了.NET 4.5.1的源代码

用Visual Studio打开DotNetReferenceSource\Source\ndp.sln,搜索HttpResponse.cs,找到Response.Redirect的实现代码:

public void Redirect(String url)
{
Redirect(url, true, false);
}

实际调用的是internal void Redirect(String url, bool endResponse, bool permanent) ,传给endResponse的值的确是true啊,为什么后面的代码还会执行?

进一步查看internal void Redirect()的实现代码(省略了无关代码):

internal void Redirect(String url, bool endResponse, bool permanent)
{
//... Page page = _context.Handler as Page;
if ((page != null) && page.IsCallback) {
//抛异常
} // ... url处理 Clear(); //Clears all headers and content output from the buffer stream. //...
this.StatusCode = permanent ? 301 : 302; //进行重定向操作
//...
_isRequestBeingRedirected = true; var redirectingHandler = Redirecting;
if (redirectingHandler != null) {
redirectingHandler(this, EventArgs.Empty);
} if (endResponse)
End(); //结束当前请求
}

从上面的代码可以看出,我们要找的真相在End()方法中,继续看HttpResponse.End()的实现代码:

public void End() {
if (_context.IsInCancellablePeriod) {
AbortCurrentThread();
}
else {
// when cannot abort execution, flush and supress further output
_endRequiresObservation = true; if (!_flushing) { // ignore Reponse.End while flushing (in OnPreSendHeaders)
Flush();
_ended = true; if (_context.ApplicationInstance != null) {
_context.ApplicationInstance.CompleteRequest();
}
}
}
}

注意啦!真相浮现了!

以前一直以为的Response.Redirect会结束当前请求,就是上面的AbortCurrentThread()情况,如果将Response.Redirect放在try...catch中就会捕捉到ThreadAbortException异常。

通常情况下,我们在WebForms的Page或MVC的Controller中进行Redirect,_context.IsInCancellablePeriod的值为true,执行的是AbortCurrentThread(),所以不会遇到这个问题。

而我们现在的场景恰恰是因为_context.IsInCancellablePeriod的值为false,为什么会是false呢?

进一步看一下_context.IsInCancellablePeriod的实现:

private int _timeoutState; // 0=non-cancelable, 1=cancelable, -1=canceled

internal bool IsInCancellablePeriod {
get { return (Volatile.Read(ref _timeoutState) == 1); }
}

根据上面的代码,触发这个问题的条件是_timeoutState的值要么是0,要么是-1,根据我们的实际情况,应该是0=non-cancelable。

再来看看我们的实际应用场景,我们是在实现IHttpHandlerFactory接口的GetHandler方法中进行Response.Redirect操作的,也就是说在这个阶段_timeoutState的值还没被设置(默认值就是0)。为了验证这个想法,继续看一下_timeoutState在哪个阶段设值的。

Shift+F12找到所有引用_timeoutState的地方,在HttpConext中发现了设置_timeoutState的方法BeginCancellablePeriod,实现代码如下:

internal void BeginCancellablePeriod() {
// It could be caused by an exception in OnThreadStart
if (Volatile.Read(ref _timeoutStartTimeUtcTicks) == -1) {
SetStartTime();
} Volatile.Write(ref _timeoutState, 1);
}

然后再Shift+F12找到了在HttpApplication.ExecuteStep()中调用了BeginCancellablePeriod():

internal Exception ExecuteStep(IExecutionStep step, ref bool completedSynchronously)
{
//..
if (step.IsCancellable) {
_context.BeginCancellablePeriod(); // request can be cancelled from this point
}
//..
}

从上面的代码可以看出,当step.IsCancellable为true时,会调用BeginCancellablePeriod(),就不会出现这个问题。

而我们用到的IHttpHandlerFactory.GetHandler()所在的IExecutionStep的实现可能将IsCancellable设置为了false。

那IHttpHandlerFactory.GetHandler()是在哪个IExecutionStep的实现中调用的呢?

在园子里木宛城主的一篇写得非常棒的博文(ASP.NET那点不为人知的事)中找到了答案——MapHandlerExecutionStep:

当执行到MapHandlerExecutionStep时会执行如下代码获取最终执行请求:context.Handler = this._application.MapHttpHandler()。HttpApplication对象的MapHttpHandler方法将根据配置文件结合请求类型和URL以调用相应的IHttpHandlerFactory来获取HttpHandler对象。

我们再回到.NET Framework的源代码中看一看MapHandlerExecutionStep的实现:

// execution step -- map HTTP handler (used to be a separate module)
internal class MapHandlerExecutionStep : IExecutionStep {
private HttpApplication _application; internal MapHandlerExecutionStep(HttpApplication app) {
_application = app;
} void IExecutionStep.Execute() {
//...
} bool IExecutionStep.CompletedSynchronously {
get { return true;}
} bool IExecutionStep.IsCancellable {
get { return false
; }
}

}

看到有没有?IExecutionStep.IsCancellable返回的值是false。

到此,水落石出,真相大白!

请看大屏幕——

由于MapHandlerExecutionStep(调用IHttpHandlerFactory.GetHandler()的地方)返回的IsCancellable的值是false,于是在HttpApplication.ExecuteStep()执行时没有调用_context.BeginCancellablePeriod()——也就是没有把_timeoutState设置为1,_context.IsInCancellablePeriod的值就是false。从而造成在Response.Redirect中进行Response.End()时没有执行AbortCurrentThread()(通常情况下都会执行这个)。于是代码继续执行,后面又来一次Response.Redirect,最终引发了——“无法在发送HTTP标头之后进行重定向”(Cannot redirect after HTTP headers have been sent)。

 
 
 
标签: ASP.NET

NET那点不为人知的事的更多相关文章

  1. ASP.NET那点不为人知的事(一)

    http://www.cnblogs.com/OceanEyes/archive/2012/08/13/aspnetEssential-1.html#_label0 我们上网时,在浏览器地址输入网址: ...

  2. EntityFramework Core 1.1是如何创建DbContext实例的呢?

    前言 上一篇我们简单讲述了在EF Core1.1中如何进行迁移,本文我们来讲讲EF Core1.1中那些不为人知的事,细抠细节,从我做起. 显式创建DbContext实例 通过带OnConfiguri ...

  3. Response.Redirect引起的“无法在发送HTTP标头之后进行重定向”

    博客后台切换至i.cnblogs.com之后,在日志中发现大量的“无法在发送HTTP标头之后进行重定向”(Cannot redirect after HTTP headers have been se ...

  4. asp.net MVC 网站图片防盗链的几种方法

    目录 1. 通过 URL Rewrite Module 组件 2. 通过 nginx 图片防盗链 3.自定义 HttpHandler 处理 4. 通过 MVC 自定义路由规则防盗链 5. 通过 MVC ...

  5. Java加密算法

    密码的常用术语: 1.密码体制:由明文空间.密文空间.密钥空间.加密算法和解密算法5部分组成. 2.密码协议:也称为安全协议,是指以密码学为基础的消息交换的通信协议,目的是在网络环境中提供安全的服务. ...

  6. ActiveMQ消息的消费原理

    消费端消费消息: 在 初识ActiveMQ 中我提到过,两种方法可以接收消息,一种是使用同步阻塞的ActiveMQMessageConsumer#receive方法.另一种是使用消息监听器Messag ...

  7. Linux内核编程规范与代码风格

    source: https://www.kernel.org/doc/html/latest/process/coding-style.html translated by trav, travmym ...

  8. 前端Leader你应该知道的NPM包管理机制

    npm install 命令 首先总结下npm 安装一个模块包的常用命令. /* 模块依赖会写入 dependencies 节点 */ npm install moduleName npm insta ...

  9. iPhone不为人知的功能常用技巧,看完后才发现很多用iPhone的人实在是愧对乔布斯! - imsoft.cnblogs

    很多人花了四五千买部苹果,结果只用到四五百块钱的普通手机功能. iPhone不为人知的功能,常用技巧: 网上搜集整理的iPhone快捷键操作,虽然表面上iPhone按键只有一个HOME键,大部分操作都 ...

随机推荐

  1. ElasticSearch实战

    ElasticSearch实战-入门 1.概述 今天接着<ElasticSearch实战-日志监控平台>一文来给大家分享后续的学习,在<ElasticSearch实战-日志监控平台& ...

  2. Nyoj 吝啬的国度(图论&&双DFS)

    描述在一个吝啬的国度里有N个城市,这N个城市间只有N-1条路把这个N个城市连接起来.现在,Tom在第S号城市,他有张该国地图,他想知道如果自己要去参观第T号城市,必须经过的前一个城市是几号城市(假设你 ...

  3. flume日志采集

    1.  Log4j Appender 1.1.  使用说明 1.1.2.  Client端Log4j配置文件 (黄色文字为需要配置的内容) log4j.rootLogger=INFO,A1,R # C ...

  4. CSharp设计模式读书笔记(22):策略模式(学习难度:★☆☆☆☆,使用频率:★★★★☆)

    策略模式(Strategy Pattern):定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy). 模式角色与结构: ...

  5. jQuery插件编写及链式编程模型

    jQuery插件编写及链式编程模型小结 JQuery极大的提高了我们编写JavaScript的效率,让我们可以愉快的编写代码,做出各种特效.大多数情况下,我们都是使用别人开发的JQuery插件,今天我 ...

  6. WebBrowser!

    原文:WebBrowser! 我现在先放一些基础的文章在这里,以后再放别的上来官方范例连接http://www.microsoft.com/china/msdn/library/langtool/vc ...

  7. Yii Framework2.0开发教程(10)配合mysql数据库实现用户登录

    1.首先在mysql创建一个存用户的表格 create table test_user ( user_id bigint(20) unsigned not null auto_increment co ...

  8. 我的MYSQL学习心得(八)

    原文:我的MYSQL学习心得(八) 我的MYSQL学习心得(八) 我的MYSQL学习心得(一) 我的MYSQL学习心得(二) 我的MYSQL学习心得(三) 我的MYSQL学习心得(四) 我的MYSQL ...

  9. C编程的指针涛 ---第九笔记

    //这里说的是一个指针,指向算法的应用 //直接排序 //每个排序算法是指针指向的每个元件的特性的方便的交流 //这里的基本思想是,处理的记录的排序n - 1第二选择. //第i次操作选择i大(小)的 ...

  10. c# 播放器 支持所有格式

    原文:c# 播放器 支持所有格式 ---恢复内容开始--- 直接上代码 internal static class LibVlcAPI { internal struct PointerToArray ...