asp.net管道模型学习(三):HttpHandler

前面两篇文章分别介绍了Http请求流程和HttpModule内容,这篇文章介绍HttpHandler。

 

HttpHandler是什么

 

在Http请求流程中讲到了,ISAPI能映射请求页面的后缀与之对应的应用程序,大部分文件都交给aspnet_isapi.dll处理,aspnet_isapi.dll对每种请求采用的方式不同,Http请求在管道模型中经过一系列的HttpModule,不同的Module会将请求交给某个具体的HttpHandler,实现了IHttpHandler的类对象称为处理器或处理程序,在一个page、.ashx程序都实现了IHttpHandler,它们都是一个处理器。我们来看看.net framework中已经配置好的Handler,打开目录C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config找到web.config文件:

1afc70f5-efae-4b70-a545-102d39f4d0fe.png

我们打开这个文件找到httpHandlers节点就是配置好的HttpHandler:

<httpHandlers>
      <add path="eurl.axd" verb="*" type="System.Web.HttpNotFoundHandler" validate="True" />
      <add path="trace.axd" verb="*" type="System.Web.Handlers.TraceHandler" validate="True" />
      <add path="WebResource.axd" verb="GET" type="System.Web.Handlers.AssemblyResourceLoader" validate="True" />
      <add verb="*" path="*_AppService.axd" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False" />
      <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False"/>
      <add path="*.axd" verb="*" type="System.Web.HttpNotFoundHandler" validate="True" />
      <add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True" />
      <add path="*.ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="True" />
      <add path="*.asmx" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False" />
      <add path="*.rem" verb="*" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="False" />
      <add path="*.soap" verb="*" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="False" />
      <add path="*.asax" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.ascx" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.master" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.skin" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.browser" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.sitemap" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.dll.config" verb="GET,HEAD" type="System.Web.StaticFileHandler" validate="True" />
      <add path="*.exe.config" verb="GET,HEAD" type="System.Web.StaticFileHandler" validate="True" />
      <add path="*.config" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.cs" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.csproj" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.vb" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.vbproj" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.webinfo" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.licx" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.resx" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.resources" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.mdb" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.vjsproj" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.java" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.jsl" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.ldb" verb="*" type="System.Web.HttpForbiddenHandler"  validate="True" />
      <add path="*.ad" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.dd" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.ldd" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.sd" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.cd" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.adprototype" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.lddprototype" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.sdm" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.sdmDocument" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.mdf" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.ldf" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.exclude" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.refresh" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />
      <add path="*.svc" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False"/>
      <add path="*.rules" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
      <add path="*.xoml" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False"/>
      <add path="*.xamlx" verb="*" type="System.Xaml.Hosting.XamlHttpHandlerFactory, System.Xaml.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="False"/>
      <add path="*.aspq" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
      <add path="*.cshtm" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
      <add path="*.cshtml" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
      <add path="*.vbhtm" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
      <add path="*.vbhtml" verb="*" type="System.Web.HttpForbiddenHandler" validate="True"/>
      <add path="*" verb="GET,HEAD,POST" type="System.Web.DefaultHttpHandler" validate="True" />
      <add path="*" verb="*" type="System.Web.HttpMethodNotAllowedHandler" validate="True" />
  </httpHandlers>

从上面的配置中可以看到不同类型的文件对应不同的处理程序即不同的Handler,如.aspx文件交给PageHandlerFactory处理,.ashx交给SimpleHandlerFactory处理,.config、.cs等都交给HttpForbiddenHandler处理,因为这些文件都是不对外公布的,访问时会被阻止。

我们也可以自己实现Handler来处理Http请求实现特定功能,在实现自己的Hander之前,我们来看下.ashx程序,因为这是最直观的Handler,如在webform项目中,我们经常使用.ash程序来生成验证码或者处理ajax请求,下面是新建的一个ashx程序:

public class Handler1 : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";
        context.Response.Write("Hello World");
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

处理程序Hander只需要实现IHttpHandler接口,这个接口定义如下:

//
// 摘要:
//     定义 ASP.NET 为使用自定义 HTTP 处理程序同步处理 HTTP Web 请求而实现的协定。
public interface IHttpHandler
{
    //
    // 摘要:
    //     获取一个值,该值指示其他请求是否可以使用 System.Web.IHttpHandler 实例。
    //
    // 返回结果:
    //     如果 System.Web.IHttpHandler 实例可再次使用,则为 true;否则为 false。
    bool IsReusable { get; }

    //
    // 摘要:
    //     通过实现 System.Web.IHttpHandler 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。
    //
    // 参数:
    //   context:
    //     System.Web.HttpContext 对象,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session
    //     和 Server)的引用。
    void ProcessRequest(HttpContext context);
}

只有两个成员变量,一个是bool类型的属性默认设置为false,另一个是程序的入口方法ProcessRequest。

我们只需要在ProcessRequest方法中来写代码处理Http请求即可。.ashx程序.net framework已经配置好,我们自己通过创建一个继承IHttpHandler接口的普通类形式的Handler则需要在项目的web.config中注册。

 

实现自己的Handler

 

在前面的文章《在asp.net中如何实现图片防盗链》演示了360doc网站的图片防盗链的功能,在asp.net中这个功能就通过自己实现一个Handler来实现,我们在前面文章HttpModule学习中我们搭建了一个测试项目,我们接着在这个项目中测试,在Web.Core类库中添加MyTestHandler类:

/// <summary>
/// 测试用于图片防盗链
/// </summary>
public class MyTestHandler : IHttpHandler
{
    public bool IsReusable { get { return true; } }

    public void ProcessRequest(HttpContext context)
    {
        string fileName = context.Server.MapPath(context.Request.FilePath);
        //直接访问图片地址 context.Request.UrlReferrer=null
        if (context.Request.UrlReferrer==null||context.Request.UrlReferrer.Host == null)//这样直接访问图片和在别的网站引用该图片都显示不出来
        {
            context.Response.ContentType = "image/JPEG";
            context.Response.WriteFile(context.Server.MapPath("~/Content/images/error.jpg"));
        }
        else
        {
            // 如果 UrlReferrer中不包含自己站点主机域名,则显示一张默认的禁止盗链的图片
            if (context.Request.UrlReferrer.Host.IndexOf("test2.com") >= 0)
            {
                context.Response.ContentType = "image/JPEG";
                context.Response.WriteFile(fileName);
            }
            else
            {
                context.Response.ContentType = "image/JPEG";
                context.Response.WriteFile(context.Server.MapPath("~/Content/images/error.jpg"));
            }
        }

    }
}

在web网站中要使用这个Handler必须在web.config中进行注册,在<system.webServer>节点的<handlers>几点中添加:

<handlers>
      <add name="jpg" path="*.jpg" verb="*" type="Web.Core.Pipeline.MyTestHandler,Web.Core" />
</handlers>

上面的配置表示对于.jpg图片的请求需要通过我们的MyTestHandler来处理,type由两部分组成,第一部分表示命名空间+类名,第二部分表示handler的程序集。

我们创建页面来测试,在AspNetPipelineTest网站项目中PipelineTestController新建一个视图方法:

public ActionResult MyTestHandlerTest()
{
    return View();
}

新建视图,在视图页中只引用一张图片:

@{
    ViewBag.Title = "MyTestHandlerTest";
}

<img src="~/Content/images/starface.jpg" />

将这个web项目部署到IIS上,绑定到www.test2.com:

732908bd-ac93-4e33-ae1d-a2d7e180ded9.png

再新建一个web项目TestWeb,在Home控制器中新建视图方法:

/// <summary>
/// 测试图片防盗链的Handler
/// </summary>
/// <returns></returns>
public ActionResult HandlerTest()
{
    return View();
}

新建视图页,引用一张图片:

@{
    ViewBag.Title = "HandlerTest";
}

<h2>HandlerTest</h2>
<img src="http://www.test2.com/Content/images/starface.jpg" />

将TestWeb部署到IIS上,绑定域名www.test1.com:

9dedc155-f81d-43bd-8d79-2b58011f89ce.png

在浏览中访问test2网站图片正常显示:

d4da00f8-3997-45a8-a8e8-140cc8545efc.png

直接访问该图片:b4da5d81-fd31-423a-a261-2a751f632821.png

在test1.com中访问,也被阻止了:7739d05d-3adb-497c-adb9-b6df4257661d.png

图片防盗链Handler生效了。

 

Handler工厂

 

Handler工厂用来处理多个Handler情况,假设有这样的情形,我们要处理多个类型的文件,分别对应多个Handler的实现,如.gif图片需要GifHandler处理,.abc文件需要AbcHandler处理,按上面介绍的只需要在web.config中分别注册即可,但是Handler很多时,添加一个<add>节点是web.config文件显得有点冗长,只有在程序运行的时候才知道要使用哪个Handler,针对这种情况我们可以创建Handler工厂,该工厂类只需实现IHttpHandlerFactory接口,我们来看看这个接口的定义:

//
// 摘要:
//     定义类工厂为创建新的 System.Web.IHttpHandler 对象而必须实现的协定。
public interface IHttpHandlerFactory
{
    //
    // 摘要:
    //     返回实现 System.Web.IHttpHandler 接口的类的实例。
    //
    // 参数:
    //   context:
    //     System.Web.HttpContext 类的实例,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session
    //     和 Server)的引用。
    //
    //   requestType:
    //     客户端使用的 HTTP 数据传输方法(GET 或 POST)。
    //
    //   url:
    //     所请求资源的 System.Web.HttpRequest.RawUrl。
    //
    //   pathTranslated:
    //     所请求资源的 System.Web.HttpRequest.PhysicalApplicationPath。
    //
    // 返回结果:
    //     处理请求的新的 System.Web.IHttpHandler 对象。
    IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
    //
    // 摘要:
    //     使工厂可以重用现有的处理程序实例。
    //
    // 参数:
    //   handler:
    //     要重用的 System.Web.IHttpHandler 对象。
    void ReleaseHandler(IHttpHandler handler);
}

只有两个方法成员,GetHandler方法返回实现了IHandler接口的类的实例,ReleaseHandler方法使工厂可以重复使用一个已经存在的Handler实例。

我们创建一个Factory:

public class MyHandlerFactory : IHttpHandlerFactory
{
    public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
    {
        string path=context.Request.PhysicalPath;
        switch (Path.GetExtension(path))
        {
            case "gif":
                return new MyTestHandler();
            case "abc":
                return new AbcHandler();
            default:
                return new DefaultHttpHandler();
        }
    }

    public void ReleaseHandler(IHttpHandler handler)
    {
    }
}

在web项目中添加注册:

<handlers>
  ...
  <add name="gifabc" path="*.gif,*.abc" verb="*" type="Web.Core.Pipeline.MyHandlerFactory,Web.Core" />
</handlers>

通过上面的步骤即可实现一个Handler工厂。

 

ASP.NET MVC中的Handler

 

在asp.net mvc中是一个页面请求由哪些handler处理的呢?在说明这个问题之前,先通过代码来展示下。

新建一个视图方法:

public ActionResult ShowMvcHandler()
{
    List<string> processList = new List<string>();
    processList.Add("1 请求已经生成HttpApplication,经过一系列的Module处理后");

    RouteData routeData = base.RouteData;
    processList.Add($"2 请求到达Module:{typeof(UrlRoutingModule)},该模块会解析当前的请求,找到当前请求的{routeData.GetType().Name}");
    processList.Add($"3 该RouteData的内容为{JsonHelper.ToJson<RouteData>(routeData)}");
    //IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(base.Request.RequestContext);//当前请求信息为参数  
    //这里会抛异常  Additional information: 只能在引发“HttpApplication.AcquireRequestState”之前调用“HttpContext.SetSessionStateBehavior”。
    //这个时候handler已经明确了,不能再次获取了
    IHttpHandler handler = base.HttpContext.Handler;
    processList.Add($"4 然后调用该RouteData.RouteHandler.GetHttpHandler去获取该请求的处理handler:{handler.GetType().FullName}");
    processList.Add($"5 该handler默认是{handler.GetType().FullName},这个IHttpHandler会根据this.RequestContext.RouteData的控制器信息去找控制器的工厂,如默认的{typeof(DefaultControllerFactory)},通过反射来完成控制器的激活 ");

   return View(processList);
}

新建视图页,展示后台组成的内容:

@model List<string>
@{
    ViewBag.Title = "ShowMvcHandler";
}

<h2>ShowMvcHandler</h2>

@foreach (string process in Model)
{
    <p>@process</p>
}

渲染效果如下:

57b6aac4-9fb5-45fe-b947-b6a1e32266c2.png

 
posted @ 2019-09-18 20:49  James.Yu  阅读(187)  评论(0)    收藏  举报