跟着url走一圈(ASP.NET请求底层流转个人总结 二)
前面一篇文章主要说明了请求在流转到进入管道之前的一些大概情况,同时也介绍了我们可以在管道的各个程序周期事件注册自己的处理方法(可以通过自定义httpModules或者直接在Global.asax文件内书写方法)通过学习我们发现自定义httpModules其实还是蛮简单的。
事实上出了自定义的httpModules 系统也内置了好多个httpModules实现,他们就是我们通常说的管道内对象,当请求到达后他们会按序出来完成自己份内的工作,于是当请求到达我们我们的页面代码时,我们才有很多对象可用,而且很多繁琐的事情不需要我们写代码一一实现,以为内这些家伙已经帮我们搞定了。现在我们急迫的想知道系统内置了那些牛逼的对象呢,请看下表(在Machine.config 内的默认 <HttpModules> 元素内可以找到这些配置)
<add name="OutputCache"
type="System.Web.Caching.OutputCacheModule"/>
<add name="Session"
type="System.Web.SessionState.SessionStateModule"/>
<add name="WindowsAuthentication"
type="System.Web.Security.WindowsAuthenticationModule"/>
<add name="FormsAuthentication"
type="System.Web.Security.FormsAuthenticationModule"/>
<add name="PassportAuthentication"
type="System.Web.Security.PassportAuthenticationModule"/>
<add name="UrlAuthorization"
type="System.Web.Security.UrlAuthorizationModule"/>
<add name="FileAuthorization"
type="System.Web.Security.FileAuthorizationModule"/>
</httpModules>
这些类帮助我们做各种事情,那么我们如何用他们做一些我们自定义的事情呢,答案是在Global.asax注册这些类的事件处理方法。已我们常见的 void Session_Start(object sender, EventArgs e) 为例,他是系统默认注册的方法,当SessionStateModule对象开始为请求创建新会话时会触发Start事件并执行方法。需要做一点说明,请求新会话的标志是分配SessionId。而我们在代码中增加Session变量是在已经分配了新会话的基础上增加新项。只有当前会话过期失效而下次请求开始后才会再次触发事件。
除此之外其他对象也有自己的事件,比如身份验证模块挂钩 AuthenticateRequest 事件,而授权模块挂钩 AuthorizeRequest 事件。总之每个兑现都有各自的事件,你可以为这些事件注册处理方法。
关于httpModules 就说这么多,总而言之它是一堆预处理器帮我们先行处理一些琐碎的事情,系统也提供了一个入口供开发人员将自己的代码注册到适当的地方执行。 下面我们开始说 HttpHandler 类了,这个东西实际在管道中处于核心位置,从某种意义上说我们的代码是由它来分析处理的。还记得之前讨论的ISAPI吗,HttpHandler概念上有点类似。
HttpHandler是HTTP请求的处理中心,真正地对客户端请求的服务器页面做出编译和执行,并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中。HttpHandler与HttpModule不同,一旦定义了自己的HttpHandler类,那么它对系统的HttpHandler的关系将是“覆盖”关系。
IHttpHandler接口声明:
{
bool IsReusable { get; }
public void ProcessRequest(HttpContext context); //请求处理函数
}
我们可以定义自己的类并通过配置来“覆盖”系统默认的 HttpHandler 首先定义自己的类:
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
context.Response.WriteFile(@"C:\123.jpg");
}
}
然后在webconfig中增加以下配置
<httpHandlers>
<add verb="*" path="*.p" type="testHttpHandler"/>
</httpHandlers>
...
配置完毕后所以对网站内扩展名字为*.p的访问都会被testHttpHandler处理并且会输出一张图片。
以上我们配置了一个自定义的扩展文件请求的处理类。如果我们把配置改成 <add verb="*" path="*.aspx" type="testHttpHandler"/>
那么所有aspx文件请求处理就会被我们定义的类接管,所有请求都不会产生我们预想的结果一律输出一张图片因为这个设置覆盖了系统配置。知道了它的运行机制和原理我们就能灵活应用了,网上常见的例子是防止图片盗链就是为了说明自定义HttpHandler应用,在这里就不再详述了。
除了给某类型请求设定一个自定义httpHandler类,还可以给设定一个IHttpHandlerFactory 工厂类。在内置配置中就是用工厂类配置类型请求处理。来看一下在Machine.config文件中的配置区<httphandlers>,在配置文件注册了应用程序的具体处理类。例如在Machine.config中对*.aspx的处理将映射到System.Web.UI.PageHandlerFactory 类,而对*.ashx的处理将映射到System.Web.UI.SimpleHandlerFactory 类,这两个类都是继承于IhttpHandlerFactory接口的具体类:
<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory" />
<add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory" />
......
</httpHandlers>
这个配置区建立了资源请求的类型和处理请求的类之间的一个映射集。如果一个.aspx页面发出了请求,将会调用System.Web.UI.PageHandlerFactory类,HttpApplication调用接口IHttpHandlerFactory中的工厂方法GetHandler来创建一个Handler对象。当一个*.aspx的页面发出请求时,通过PageHandlerFactory将返回一个Page对象来处理该页面
IHttpHandlerFactory工厂:
{
// Methods
IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
void ReleaseHandler(IHttpHandler handler);
}
将具体的handler类换成一个工厂类有何不同呢? 本质上是一样的但这样的修改可以为我们的程序增加很多便利,首先是我们可以给不同请求使用不同的处理handler类,其次我们还能像httpModuls一样为系统handler对象事件注册我们自己的代码。
先看第一种应用,为不同请求分配不同的处理器:
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
context.Response.WriteFile(@"C:\123.jpg");
}
}
public class testHttpHandler1 : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
context.Response.Write("hello!");
}
}
public class ValidateHttpHandlerFactory : IHttpHandlerFactory
{
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
if (url.IndexOf("test.p") > 0)
{
return new testHttpHandler();
}
else
{
return new testHttpHandler1();
}
}
public void ReleaseHandler(IHttpHandler handler)
{
}
}
配置自然要改成:
<httpHandlers>
<add verb="*" path="*.p" type="ValidateHttpHandlerFactory"/>
</httpHandlers>
...
这样设置以后当我们访问test.p时会输出一张图片,其他.p请求会输出字符“hello!”。
通过设置我们还可以实现系统处理对象的事件注册。看下面代码:
{
#region IHttpHandlerFactory 成员
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
PageHandlerFactory factory = (PageHandlerFactory)Activator.CreateInstance(typeof(PageHandlerFactory), true);
IHttpHandler handler = factory.GetHandler(context, requestType, url, pathTranslated);
Page page = handler as Page;
if (page != null)
{
page.Init += new EventHandler(page_Init);
}
return handler;
}
public void ReleaseHandler(IHttpHandler handler)
{
}
#endregion
void page_Init(object sender, EventArgs e)
{
//这里来验证访问用户的合法性
}
}
配置是这样的:
<add path="*.aspx" verb="*" type="Hxj.Web.HttpHandlerFactory.ValidateHttpHandlerFactory,Hxj.Web.HttpHandlerFactory"/>
</httpHandlers>
这是网上找的例子,实际上作者用了一个“迂回”的办法,先用自定义的工厂替代aspx的请求访问,然后在工厂方法中重新申请系统默认的handler类,在返回之前给类事件注册了自定义方法。
讲到这里告一段落。需要补充一点,当我们想在自定义的handler中使用session 必须继承System.Web.SessionState.IRequiresSessionState接口,才能实现Session读写!
System.Web.SessionState的一些接口
IReadOnlySessionState 指定目标 HTTP 处理程序只需要具有对会话状态值的读访问权限。这是一个标记接口,没有任何方法。
IRequiresSessionState 指定目标 HTTP 处理程序需要对会话状态值具有读写访问权。这是一个标记接口,没有任何方法。
需要注意:有些类型的文件的访问(比如图片)即使实现接口也无法使用session
session在httpModule中访问情况比较复杂,请看下面代码:
{
public void Dispose() { }
public void Init(HttpApplication context)
{
//context.BeginRequest += new EventHandler(Application_BeginRequest);
//context.EndRequest += new EventHandler(Application_EndRequest);
context.AcquireRequestState += (o, e) =>
{
HttpApplication app = (HttpApplication)o;
// 这样就OK,说明AcquireRequestState事件触发时session是可用的
app.Session["a"] = "a";
app.Response.Write(app.Session["a"].ToString());
};
//context.Session["a"] = "ka!"; 这行代码会报错,因为此时session对象不可用
}
}
从上面代码中我们可以知道httpModule是否能操作Session对象是和注册事件是有关系的,由于Init(..)方法执行时app对象只有init被触发,此时无法操作session,只有事件AcquireRequestState 以后才可以操作。 还有一点必须了解,加假设我们请求一个图片(哪怕请求后缀不是图片格式但实际会输出图片)或其他特别的文件时,无论在何处都不允许操作session.