Asp.Net底层解析(五)——HttpHandler详解

    前言:上一篇博客详细介绍了应用程序生命周期与HttpModule。在HttpApplication对象启动HTTP管线阶段的PreRequestHandlerExecute与PostRequestHandlerExecute事件之间,实际上就是根据所请求资源的文件扩展名(在应用程序的配置文件中映射),选择实现 IHttpHandler 的类,调用该类的ProcessRequest方法,从而得到该请求资源的返回结果;接着继续执行HttpApplication的方法触发剩下的事件后将最终的结果返回给客户端。

     先来说说什么是HttpHandler。HttpHandler是实现了System.Web.IHttpHandler接口的类,该接口只有两个成员IsReusable属性与ProcessRequest(HttpContextcontext)方法。IsReusable属性指示其他请求是否可以使用 IHttpHandler 实例,如果多次请求使用同一IHttpHandler 实例而不会引起多线程问题,那么该属性就可以设置为true以提高服务器响应效率。ProcessRequest是核心方法,接受整个请求过程中的核心对象HttpContext作为参数,在方法内部根据HttpRequest中的信息结合IHttpHandler 实例本身特定的“定制”,生成一系列字符流添加到HttpResponse对象中。

    再来将HttpModule与HttpHandler进行对比:
   1, 每个请求处理中HttpModule可以有多个,而HttpHandler只有特定一个。
   2, 对相同的Web应用程序内的不同资源的请求(包括aspx页面、Webservice等等),每次请求处理的HttpModule(列表)都是相同的;而HttpHandler都是与这个目标资源相关的,是动态变化的。
   3, HttpModule在HttpApplication对象启动HTTP管线的整个阶段中都可以执行,从而对最终结果进行修改;而HttpHandler只有在PreRequestHandlerExecute与PostRequestHandlerExecute事件之间的特定阶段执行,完成之后就不再参与修改请求结果了。
   4, 如果把HttpModule与HttpHandler都比作工人的话,HttpModule是长工,HttpHandler就是短工;HttpModule是综合型人才,HttpHandler是专业型人才。



一,ASP.NET开发者从一开始就打交道的HttpHandler——System.Web.UI.Page

    常见aspx页面的后台类是继承于System.Web.UI.Page类,所以先看Page类的定义,截图如下:


    可以看到,Page类实现了IHttpHandler接口,因此Page类是可以作为HttpHandler使用的(这是废话,要是Page类不是HttpHandler,那么对Web应用程序中aspx页面的请求不就不能生效了?)。也就是说,我们在Web应用程序中创建的每个页面的后台类都是这个页面的HttpHandler,它存在的目的是在请求应用程序周期中的HTTP管线阶段对本aspx页面进行“翻译”,而这个“翻译”过程就是通过调用IHttpHandler接口的ProcessRequest方法,引发本页面的页面生命周期来实现的。

    由此可以这样思考一些问题:
   1,ASP.NET开发者在页面后台编写的所有代码都是属于该页面HttpHandler的一部分,在没有理解页面生命周期与应用程序生命周期之前,我们只知道后台的代码是控制页面的,却不了解后台类在整个从aspx页面转化为html编码的过程中的作用是什么。
   2,ASP.NET为开发者已经做了绝大部分的工作,我们只是按部就班地把一些自定义的内容填充到框里面而已。
   3,Aspx类型的页面文件之所以能被请求解析,只是因为Web应用程序中提供了相应的HttpHandler。
   4,与3同理,为了使aspx类型文件作为ASP.NET中的标准页面,System.Web程序集为此提供了System.Web.UI.Page类作为HttpHandler对aspx文件进行解析。
   5,既然aspx是ASP.NET提供的页面文件格式,但是如果我嫌这种解析页面的方式太浪费效率,页面生命周期神马的完全没必要,我完全可以自定义一种页面格式(如xiaosan),只要提供了HttpHandler来解析就可以了,然后把aspx扔在一边。


二,ASP.NET默认的HttpHandlers配置

    下面的截图是ASP.NET的默认配置文件(系统内Web.config,而不是Web应用程序内)中HttpHandlers的配置信息(path="*.axd"表示该Httphandler处理后缀名为axd的文件,verb="GET”表示仅处理请求方式为Get的请求,*表示任意字符串或请求方式)。


     可以看到,ASP.NET开发者使用最多的aspx类型的页面文件对应的HttpHandler是System.Web.UI.PageHandlerFactory类;用于创建WebService的asmx文件则是System.Web.Services.Protocols.WebServiceHandlerFactory负责解析的;还有其他ASP.NET中可以解析的请求资源类型文件都有对应的HttpHandler类来处理。

    对于禁止被客户访问的类型文件,ASP.NET全部使用System.Web.HttpForbiddenHandler类来处理,实际上当Application管线阶段交给HttpForbiddenHandler类处理时,HttpForbiddenHandler并不会去判断该文件是否真的在Web应用程序中存在,而是直接返回“无法提供此类型的页”来告诉客户端该类型的文件因为没有对应的HttpHandler而禁止被访问。比如我们请求一个cs的代码文件,经HttpForbiddenHandler类处理之后返回如下截图所示:


    还有一种情况,当Web.config文件中没有对某种资源文件的HttpHandler配置,比如aaaa类型文件,当客户端请求aaaa类型资源时,该请求就交由System.Web.HttpMethodNotAllowedHandler来处理,返回“无法找到资源”,实际上是没有找到处理该类型资源的HttpHandler而已。这种处理方式通过<add path="*" verb="*"type="System.Web.HttpMethodNotAllowedHandler"validate="True" />配置来实现的,path="*"表示可以处理任何资源文件,verb="*"表示任何请求方式(Post或者Get),而ASP.NET查找Httphandler的原则是“越具体越优先”,即Httphandler描述的越接近请求资源,则越优先选择该HttpHandler,最终在其他任何Httphandler中都不能匹配该资源时,就只有选择HttpMethodNotAllowedHandler了。


三,System.Web.UI.PageHandlerFactory工作原理

    在分析PageHandlerFactory之前,必须先了解IHttpHandlerFactory接口。我们已经知道实现了IHttpHandler的类就能够作为HttpHandler,从IHttpHandlerFactory的命名上看,IHttpHandlerFactory应该就是“生产IHttpHandler”用的,该接口的定义如下:

public interface IHttpHandlerFactory
{
    IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
    void ReleaseHandler(IHttpHandler handler);
}

    IHttpHandlerFactory接口包含GetHandler方法,返回实现IHttphandler接口的Httphandler;ReleaseHandler方法用于释放系统资源。也就是说,IHttpHandlerFactory的作用在于通过请求信息(HttpContext、url等等)找到目标HttpHandler,由这个Httphandler来做最终代处理。

    我们可以猜测,PageHandlerFactory的作用在于将对某个aspx的请求“分配”该aspx页面后台的继承于Page的类,下面开始验证这个猜想。

      PageHandlerFactory的源代码如下:
public class PageHandlerFactory : IHttpHandlerFactory2, IHttpHandlerFactory
{
    private bool _isInheritedInstance;
    protected internal PageHandlerFactory()
    {
        this._isInheritedInstance = base.GetType() != typeof(PageHandlerFactory);
    }
    public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path)
    {
        return this.GetHandlerHelper(context, requestType, VirtualPath.CreateNonRelative(virtualPath), path);
    }
    private IHttpHandler GetHandlerHelper(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath)
    {
        Page page = BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page), context, true, true) as Page;
        if (page == null)
        {
            return null;
        }
        page.TemplateControlVirtualPath = virtualPath;
        return page;
    }
    public virtual void ReleaseHandler(IHttpHandler handler)
    {
    }
    IHttpHandler IHttpHandlerFactory2.GetHandler(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath)
    {
        if (this._isInheritedInstance)
        {
            return this.GetHandler(context, requestType, virtualPath.VirtualPathString, physicalPath);
        }
        return this.GetHandlerHelper(context, requestType, virtualPath, physicalPath);
    }
}

    通过分析PageHandlerFactory的源代码可知,找到该目标HttpHandler的关键方法在于BuildManager.CreateInstanceFromVirtualPath(virtualPath,typeof(Page), context, true, true) as Page中。该方法通过System.Web程序集中的很多内部方法(用到的内部方法太多,在不了解整个程序集基础构思的情况下详细探究这些内部方法实在要耗费太多的精力,而且对仅需了解HttpHandlerFactory的工作原理没有实质帮助,因此就省略掉了)最终找到属于特定aspx页面的位于页面后台的继承于Page的类实例。


四,自定义HttpHandler

    前面说到ASP.NET通过配置文件已经默认设置了许多Web应用程序中资源类型的HttpHandler(或HttpHandlerFactory),如aspx页面、asmx—Webservice、ashx—一般处理文件等等。一般情况下,使用ASP.NET默认的这些HttpHandler就能够满足开发者的需求,但是在特殊情况下,开发者可能需要对Web应用程序中的特定的资源类型进行解析,使之可以被客户端请求访问。

    这里先做一个简单的Httphandler,这个Httphandler处理的资源类型为自定义的dragon类型文件。创建一个Web应用程序,并添加一个辅助类库HttpHandlers用于保存各个HttpHandler及HttpHandlerFactory,为这个类库添加对System.Web程序集的引用,该解决方案的资源管理器截图如下:


    SimpleHttpHandler.cs代码文件中编写的就是专门处理dragon类型资源的HttpHandler,其代码如下:

using System;
using System.Web;
using System.IO;
namespace HttpHandlers
{
    public class SimpleHttpHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }
        public void ProcessRequest(HttpContext context)
        {
            if (File.Exists(context.Request.PhysicalPath))
            {
                using (TextReader tr = File.OpenText(context.Request.PhysicalPath))
                {
                    string formaltext = tr.ReadToEnd();                                  //这里读取原文件文本
                    context.Response.Write("文件原内容:" + formaltext);
                    context.Response.Write("<h1><b>从这以下都是原文件外添加的内容,用于测试这个简单的Httphandler</b></h1>");
                    context.Response.Write("<h1><b>Hello HttpHandler</b></h1>");
                    context.Response.Write("<br/>随机数:" + new Random().Next(100).ToString());
                }
            }
            else { context.Response.Write("对不起,该请求文件在服务器中不存在!"); }
        }
    }
}

    接下来将SimpleHttpHandler注册到Web应用程序中,在Web.config配置文件中进行配置,方法如下:

<system.web>
  <httpHandlers>
    <add verb="*" path="*.dragon" type="HttpHandlers.SimpleHttpHandler, HttpHandlers"/>
  </httpHandlers>
</system.web>
    SimpleHttpHandler的处理过程是:先判断请求的dragon文件是否在服务器中存在,如果存在则读取该文件的所有文本并添加一些“标识”信息后返回;如果不存在,则告知客户端该文件不存在。Web应用程序中的TestSimpleHttpHandler.dragon 文件内容被设置为“这是TestSimpleHttpHandler.dragon文件的原始文本”,运行该Web应用程序请求TestSimpleHttpHandler.dragon文件,得到结果的截图如下:

     如果将请求文件名改为errorFile.dragon(请求文件不存在),则得到如下结果:


    当然,这里的SimpleHttpHandler功能十分简单,只是为了测试Httphandler的基本用法而已。极端情况下,如果你认为微软的System.Web.UI.Page作为HttpHandler执行效率太低了,搞那么复杂的生命周期完全是浪费执行时间,你完全可以自定义一个页面类型,比如前面我所使用的dragon(本人属龙,话说本命年快过了),然后弄一个Httphandler专门将dragon文件解析为html编码,相当于自己开发了一套Web页面框架了。当然,如果你对自己的技术水平还没有达到那样的自信程度,我建议你还是老老实实的用aspx吧。


五,自定义HttpHandlerFactory

    在大多数情况下,多个相同类型的请求资源都不会只由一个Httphandler来处理,因为各个资源文件虽然类型一样,但它们之间的内部执行过程是不会一样的,最常见的如aspx页面了。虽然aspx页面的后台的Httphandler都继承于System.Web.UI.Page,有着一致的页面生命周期模型,但是各个aspx页面上的服务器控件都不同,在后台对这些服务器控件的操作也不同,因此只能是各个页面只对应由自己后台的Httphandler来处理。

    这种情况下就需要HttpHandlerFactory来解决了。前面已经说到HttpHandlerFactory是实现了IHttpHandlerFactory接口的类,该接口有两个方法GetHandler和ReleaseHandler,其中GetHandler的作用在于根据客户端的请求信息而为之分配Httphandler,最终将每次请求都交由特定的Httphandler来处理。

    下面就自定义一个HttpHandlerFactory,用于处理snake文件(马上就蛇年了),继续使用上面的测试Httphandler的解决方案,在HttpHandlers类库中添加SimpleHttpHandlerFactory.cs类文件,代码如下:

using System;
using System.Web;
using System.IO;
namespace HttpHandlers
{
    /// <summary>
    /// 根据请求资源名称的最后字符中的数字确定Handler,1返回Handler1,其它返回Handler2
    /// </summary>
    class SimpleHttpHandlerFactory : IHttpHandlerFactory
    {
        public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
        {
            string resourceName = url.Substring(0, url.IndexOf(".snake"));  
            char num = resourceName[resourceName.Length - 1];    //这里寻找请求文件的数字编号
            if (num == '1') { return new Handler1(); }
            else { return new Handler2(); }
        }
        public void ReleaseHandler(IHttpHandler handler) { }
    }
    public class Handler1 : IHttpHandler
    {
        public bool IsReusable { get { return true; } }
        public void ProcessRequest(HttpContext context)
        {
            context.Response.Write("<h1>来自Handler1的信息。</h1>");
            if (File.Exists(context.Request.PhysicalPath))
            {
                using (TextReader tr = File.OpenText(context.Request.PhysicalPath))
                {
                    string formaltext = tr.ReadToEnd();//这里读取原文件文本
                    context.Response.Write("文件原内容:" + formaltext);
                    context.Response.Write("<br/>这是Handler1特有的内容:我是Handler1,不是Handler2");
                }
            }
            else { context.Response.Write("对不起,该请求文件在服务器中不存在!"); }
        }
    }
    public class Handler2 : IHttpHandler
    {
        public bool IsReusable { get { return true; } }
        public void ProcessRequest(HttpContext context)
        {
            context.Response.Write("<h1>来自Handler2的信息。</h1>");
            if (File.Exists(context.Request.PhysicalPath))
            {
                using (TextReader tr = File.OpenText(context.Request.PhysicalPath))
                {
                    string formaltext = tr.ReadToEnd();//这里读取原文件文本
                    context.Response.Write("文件原内容:" + formaltext);
                    context.Response.Write("<br/>这是Handler2特有的内容:我是Handler2,不是Handler1");
                }
            }
            else { context.Response.Write("对不起,该请求文件在服务器中不存在!"); }
        }
    }
}

    在Web应用程序中添加内容为“这是TestHandlerFactory1.snake的原文本”的TestHandlerFactory1.snake文件以及内容为“这是TestHandlerFactory2.snake的原文本”的TestHandlerFactory2.snake文件,运行该Web应用程序。请求该TestHandlerFactory1.snake文件,得到结果如下:


    然后请求TestHandlerFactory2.snake得到结果如下:


    可以看出,当请求TestHandlerFactory1.snake时,SimpleHttpHandlerFactory为该此请求分配了Handler1作为HttpHandler来解析该snake文件;而当请求TestHandlerFactory2.snake时,SimpleHttpHandlerFactory则为该此请求分配了Handler2。从而实现了为不同的请求资源分配不同HttpHandler的目的。


“ASP.NET底层解析”系列博客尾言

    写完HttpHandler,“ASP.NET底层解析”系列博客算是告一段落了,从ASP.NET最基本的form表单实现形式到视图状态、页面生命周期、应用程序生命周期、HttpModule,最后到HttpHandler,不得不说其中的信息量是不小的。这五篇文章是我在工作之余完成的,基于我在工作和学习的过程中日积月累对ASP.NET的理解以及从网上各个不知姓名的前辈们发表文章中的学习。我很希望这几篇文章能为刚踏入不久的ASP.NET同行们起到一点点作用,也希望借此与各位较资深的同行们进行技术上的交流。最后要说明的是,虽然我极力保证本系列文章的在技术上的正确性,但是由于本人技术能力有限,语言表达能力也稍有欠缺,因此难免会有一些疏落及错误,如果读者在阅读的过程中遇到任何疑虑,欢迎留言。

本篇博客的测试代码下载地址: 点击打开链接






评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值