ASP.NET管道处理模型(一)

本章将和大家分享ASP.NET中的管道处理模型。

所谓管道处理模型,其实就是后台如何处理一个Http请求,定义多个事件完成处理步骤,每个事件可以扩展动作(IHttpModule), 最后有个IHttpHandler完成请求的处理,这个过程就是管道处理模型。

还有一个全局的上下文环境HttpContext,无论参数、中间结果、最终结果,都保存在其中。

下面我们将结合部门源码(通过ILSpy反编译得到)进行讲解:

首先我们先来看下 请求到程序响应 的示例图:

从图中可以看出Http请求需要经过一系列的步骤才会进入到我们的ASP.NET入口System.Web.HttpRuntime.ProcessRequest(HttpWorkerRequest wr)

接下来我们就从请求进入ASP.NET入口开始讲解:

我们通过反编译工具ILSpy找到ASP.NET的入口System.Web.HttpRuntime.ProcessRequest(HttpWorkerRequest wr):

// System.Web.HttpRuntime
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
public static void ProcessRequest(HttpWorkerRequest wr)
{
    if (wr == null)
    {
        throw new ArgumentNullException("wr");
    }
    if (HttpRuntime.UseIntegratedPipeline)
    {
        throw new PlatformNotSupportedException(SR.GetString("Method_Not_Supported_By_Iis_Integrated_Mode", new object[]
        {
            "HttpRuntime.ProcessRequest"
        }));
    }
    HttpRuntime.ProcessRequestNoDemand(wr);
}

接着我们沿 HttpRuntime.ProcessRequestNoDemand(wr)  一直往里找:

会找到System.Web.HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr)方法,如下所示:

// System.Web.HttpRuntime
private void ProcessRequestInternal(HttpWorkerRequest wr)
{
    Interlocked.Increment(ref this._activeRequestCount);
    if (this._disposingHttpRuntime)
    {
        try
        {
            wr.SendStatus(503, "Server Too Busy");
            wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
            byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Server Too Busy</body></html>");
            wr.SendResponseFromMemory(bytes, bytes.Length);
            wr.FlushResponse(true);
            wr.EndOfRequest();
        }
        finally
        {
            Interlocked.Decrement(ref this._activeRequestCount);
        }
        return;
    }
    HttpContext httpContext;
    try
    {
        httpContext = new HttpContext(wr, false);
    }
    catch
    {
        try
        {
            wr.SendStatus(400, "Bad Request");
            wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
            byte[] bytes2 = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
            wr.SendResponseFromMemory(bytes2, bytes2.Length);
            wr.FlushResponse(true);
            wr.EndOfRequest();
            return;
        }
        finally
        {
            Interlocked.Decrement(ref this._activeRequestCount);
        }
    }
    wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, httpContext);
    HostingEnvironment.IncrementBusyCount();
    try
    {
        try
        {
            this.EnsureFirstRequestInit(httpContext);
        }
        catch
        {
            if (!httpContext.Request.IsDebuggingRequest)
            {
                throw;
            }
        }
        httpContext.Response.InitResponseWriter();
        IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(httpContext);
        if (applicationInstance == null)
        {
            throw new HttpException(SR.GetString("Unable_create_app_object"));
        }
        if (EtwTrace.IsTraceEnabled(5, 1))
        {
            EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, httpContext.WorkerRequest, applicationInstance.GetType().FullName, "Start");
        }
        if (applicationInstance is IHttpAsyncHandler)
        {
            IHttpAsyncHandler httpAsyncHandler = (IHttpAsyncHandler)applicationInstance;
            httpContext.AsyncAppHandler = httpAsyncHandler;
            httpAsyncHandler.BeginProcessRequest(httpContext, this._handlerCompletionCallback, httpContext);
        }
        else
        {
            applicationInstance.ProcessRequest(httpContext);
            this.FinishRequest(httpContext.WorkerRequest, httpContext, null);
        }
    }
    catch (Exception e)
    {
        httpContext.Response.InitResponseWriter();
        this.FinishRequest(wr, httpContext, e);
    }
}

从源码可以看出首先它是使用HttpWorkerRequest打包出一个HttpContext,然后再使用HttpContext创建一个IHttpHandler实例,最后用这个IHttpHandler实例来处理请求。

接下来我们沿着  HttpApplicationFactory.GetApplicationInstance(httpContext) 往里找:

// System.Web.HttpApplicationFactory
internal static IHttpHandler GetApplicationInstance(HttpContext context)
{
    if (HttpApplicationFactory._customApplication != null)
    {
        return HttpApplicationFactory._customApplication;
    }
    if (context.Request.IsDebuggingRequest)
    {
        return new HttpDebugHandler();
    }
    HttpApplicationFactory._theApplicationFactory.EnsureInited();
    HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context);
    return HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context);
}

其中 HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context)  这句话就是用来启动我们的网站完成项目初始化的,它会去调用我们的Global.asax里面的Application_Start方法。

我们继续往  HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context) 里面找:

// System.Web.HttpApplicationFactory
private HttpApplication GetNormalApplicationInstance(HttpContext context)
{
    HttpApplication httpApplication = null;
    if (!this._freeList.TryTake(out httpApplication))
    {
        httpApplication = (HttpApplication)HttpRuntime.CreateNonPublicInstance(this._theApplicationType);
        using (new ApplicationImpersonationContext())
        {
            httpApplication.InitInternal(context, this._state, this._eventHandlerMethods);
        }
    }
    if (AppSettings.UseTaskFriendlySynchronizationContext)
    {
        httpApplication.ApplicationInstanceConsumersCounter = new CountdownTask(1);
        Task arg_8A_0 = httpApplication.ApplicationInstanceConsumersCounter.Task;
        Action<Task, object> arg_8A_1;
        if ((arg_8A_1 = HttpApplicationFactory.<>c.<>9__34_0) == null)
        {
            arg_8A_1 = (HttpApplicationFactory.<>c.<>9__34_0 = new Action<Task, object>(HttpApplicationFactory.<>c.<>9.<GetNormalApplicationInstance>b__34_0));
        }
        arg_8A_0.ContinueWith(arg_8A_1, httpApplication, TaskContinuationOptions.ExecuteSynchronously);
    }
    return httpApplication;
}

可以看到该方法就是为了得到一个HttpApplication的实例,但是它并不是简单的创建HttpApplication的实例,HttpApplication有可能是重用的(对象池--Stack--会重用)

我们点击HttpApplication进去看下:

可以看到它是实现 IHttpHandler和IHttpAsyncHandler 接口的。

到这里我们大概知道,任何一个Http请求一定是有一个IHttpHandler来处理的,任何一个Http请求就是一个HttpApplication对象来处理

我们知道处理请求的过程一般包括固定步骤,例如:权限认证/缓存处理/Session处理/Cookie处理/生成html/输出客户端等,

与此同时,千千万万的开发者,又有各种各样的扩展诉求,任何一个环节都有可能要扩展,该怎么设计?

这里用的是观察者模式,把固定的步骤直接写在Handler里面,在步骤前&后分别放一个事件, 然后开发者可以对事件注册动作,等着请求进来了,然后就可以按顺序执行一下。

HttpApplication里面定义了一系列的事件,最终会按一定的顺序去执行这些事件,我们可以通过反编译工具来看下这些事件的执行顺序。

通过反编译工具找到System.Web.HttpApplication.ProcessEventSubscriptions方法(处理事件订阅的方法):

// System.Web.HttpApplication
private void ProcessEventSubscriptions(out RequestNotification requestNotifications, out RequestNotification postRequestNotifications)
{
    requestNotifications = (RequestNotification)0;
    postRequestNotifications = (RequestNotification)0;
    if (this.HasEventSubscription(HttpApplication.EventBeginRequest))
    {
        requestNotifications |= RequestNotification.BeginRequest;
    }
    if (this.HasEventSubscription(HttpApplication.EventAuthenticateRequest))
    {
        requestNotifications |= RequestNotification.AuthenticateRequest;
    }
    if (this.HasEventSubscription(HttpApplication.EventPostAuthenticateRequest))
    {
        postRequestNotifications |= RequestNotification.AuthenticateRequest;
    }
    if (this.HasEventSubscription(HttpApplication.EventAuthorizeRequest))
    {
        requestNotifications |= RequestNotification.AuthorizeRequest;
    }
    if (this.HasEventSubscription(HttpApplication.EventPostAuthorizeRequest))
    {
        postRequestNotifications |= RequestNotification.AuthorizeRequest;
    }
    if (this.HasEventSubscription(HttpApplication.EventResolveRequestCache))
    {
        requestNotifications |= RequestNotification.ResolveRequestCache;
    }
    if (this.HasEventSubscription(HttpApplication.EventPostResolveRequestCache))
    {
        postRequestNotifications |= RequestNotification.ResolveRequestCache;
    }
    if (this.HasEventSubscription(HttpApplication.EventMapRequestHandler))
    {
        requestNotifications |= RequestNotification.MapRequestHandler;
    }
    if (this.HasEventSubscription(HttpApplication.EventPostMapRequestHandler))
    {
        postRequestNotifications |= RequestNotification.MapRequestHandler;
    }
    if (this.HasEventSubscription(HttpApplication.EventAcquireRequestState))
    {
        requestNotifications |= RequestNotification.AcquireRequestState;
    }
    if (this.HasEventSubscription(HttpApplication.EventPostAcquireRequestState))
    {
        postRequestNotifications |= RequestNotification.AcquireRequestState;
    }
    if (this.HasEventSubscription(HttpApplication.EventPreRequestHandlerExecute))
    {
        requestNotifications |= RequestNotification.PreExecuteRequestHandler;
    }
    if (this.HasEventSubscription(HttpApplication.EventPostRequestHandlerExecute))
    {
        postRequestNotifications |= RequestNotification.ExecuteRequestHandler;
    }
    if (this.HasEventSubscription(HttpApplication.EventReleaseRequestState))
    {
        requestNotifications |= RequestNotification.ReleaseRequestState;
    }
    if (this.HasEventSubscription(HttpApplication.EventPostReleaseRequestState))
    {
        postRequestNotifications |= RequestNotification.ReleaseRequestState;
    }
    if (this.HasEventSubscription(HttpApplication.EventUpdateRequestCache))
    {
        requestNotifications |= RequestNotification.UpdateRequestCache;
    }
    if (this.HasEventSubscription(HttpApplication.EventPostUpdateRequestCache))
    {
        postRequestNotifications |= RequestNotification.UpdateRequestCache;
    }
    if (this.HasEventSubscription(HttpApplication.EventLogRequest))
    {
        requestNotifications |= RequestNotification.LogRequest;
    }
    if (this.HasEventSubscription(HttpApplication.EventPostLogRequest))
    {
        postRequestNotifications |= RequestNotification.LogRequest;
    }
    if (this.HasEventSubscription(HttpApplication.EventEndRequest))
    {
        requestNotifications |= RequestNotification.EndRequest;
    }
    if (this.HasEventSubscription(HttpApplication.EventPreSendRequestHeaders))
    {
        requestNotifications |= RequestNotification.SendResponse;
    }
    if (this.HasEventSubscription(HttpApplication.EventPreSendRequestContent))
    {
        requestNotifications |= RequestNotification.SendResponse;
    }
}

通过上面的源码,我们就能很清楚的看出各个事件的执行顺序了。

下面我们可以通过一张图来更直观的了解这些事件的执行顺序,如下所示:

 

而对HttpApplication里面的事件进行动作注册的,就叫IHttpModule,下面我们就来看下如何实现一个自定义HttpModule

首先我们先来看下Demo的目录结构:

本Demo的Web项目为ASP.NET Web 应用程序(目标框架为.NET Framework 4.5) MVC项目。 

其中Home控制器:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace AspNetPipeline.Controllers
{
    /// <summary>
    /// 1 Http请求处理流程
    /// 2 HttpApplication的事件
    /// 3 HttpModule
    /// 4 Global事件
    /// 
    /// Runtime--运行时
    /// Context--上下文
    /// 任何一个Http请求一定是有一个IHttpHandler来处理的 ashx aspx.cs  MvcHttpHandler 
    /// 任何一个Http请求就是一个HttpApplication对象来处理
    /// 然后处理过程固定包含:权限认证/缓存处理/Session处理/Cookie处理/生成html/输出客户端
    /// 与此同时,千千万万的开发者,又有各种各样的扩展诉求,任何一个环节都有可能要扩展,该怎么设计?
    /// 这里用的是观察者模式,把固定的步骤直接写在Handler里面,在步骤前&后分别放一个事件,
    /// 然后开发者可以对事件注册动作,等着请求进来了,然后就可以按顺序执行一下
    ///  
    /// 对HttpApplication里面的事件进行动作注册的,就叫IHttpModule
    /// 自定义一个HttpModule--配置文件注册--然后任何一个请求都会执行Init里面注册给Application事件的动作
    /// 学习完HttpModule,我们可以做点什么有用的扩展?
    ///  1 日志-性能监控-后台统计数据
    ///  2 权限
    ///  3 缓存
    ///  4 页面加点东西
    ///  5 请求过滤--黑名单
    ///  6 MVC--就是一个Module扩展
    ///  
    /// 不适合的(不是全部请求的,就不太适合用module,因为有性能损耗)
    ///  1 跳转到不同界面--也不适合
    ///  2 防盗链--针对一类的后缀来处理,而不是全部请求--判断--再防盗链
    ///  
    /// HttpModule里面发布一个事件CustomHttpModuleHandler,在Global.asax增加一个动作,
    /// MyCustomHttpModule_CustomHttpModuleHandler(配置文件module名称_module里面事件名称),请求响应时,该事件会执行
    ///  
    /// HttpModule是对HttpApplication的事件注册动作,而Global则是对HttpModule里面的事件注册动作
    /// 
    /// 
    /// C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config
    /// .NetFramework安装路径,是一个全局的配置,是当前电脑上任何一个网站的默认配置,不要去修改它
    /// 
    /// 
    /// 1 HttpHandler及扩展,自定义后缀,图片防盗链等
    /// 2 RoutingModule,IRouteHandler、IHttpHandler
    /// 3 MVC扩展Route,扩展HttpHandle
    /// 
    /// 配置文件指定映射关系:后缀名与处理程序的关系(IHttpHandler---IHttpHandlerFactory)
    /// Http任何一个请求一定是由某一个具体的Handler来处理的,不管是成功还是失败
    /// 以前写aspx,感觉请求访问的是物理地址,其实不然,请求的处理是框架设置的
    /// 
    /// 所谓管道处理模型,其实就是后台如何处理一个Http请求,定义多个事件完成处理步骤,每个事件可以扩展动作(HttpModule),
    /// 最后有个HttpHandler完成请求的处理,这个过程就是管道处理模型。
    /// 还有一个全局的上下文环境HttpContext,无论参数、中间结果、最终结果,都保存在其中。
    /// 
    /// 自定义Handler处理,就是可以处理各种后缀请求,可以加入自己的逻辑
    /// 如果没有--请求都到某个页面--传参数---返回图片
    /// 防盗链---加水印---伪静态---RSS--robot--trace.axd
    /// 
    /// MVC里面不是controller action?其实是由 MvcHandler来处理请求,期间完成对action调用的
    /// 网站启动时---对RouteCollection进行配置
    /// 把正则规则和RouteHandler(提供HttpHandler)绑定,放入RouteCollection,
    /// 请求来临时---用RouteCollection进行匹配
    /// 所谓MVC框架,其实就是在Asp.Net管道上扩展的,在PostResolveCache事件扩展了UrlRoutingModule,
    /// 会在任何请求进来后,先进行路由匹配,如果匹配上了,就指定HttpHandler;没有匹配就还是走原始流程
    /// 
    /// 扩展自己的Route,写入RouteCollection,可以自定义规则完成路由
    /// 扩展HttpHandle,就可以为所欲为,跳出MVC框架
    /// </summary>
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

对应的 /Home/Index 视图:

@{
    ViewBag.Title = "Home Page";
}

<h2>
    This is Home/Index View
</h2>
<hr />

未进行HttpModule注册前我们先来访问下 /Home/Index ,运行结果如下所示:

下面我们自定义一个HttpModule如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace AspNetPipeline.Pipeline
{
    /// <summary>
    /// 自定义HttpModule
    /// </summary>
    public class CustomHttpModule : IHttpModule
    {
        public event EventHandler CustomHttpModuleHandler;

        public void Dispose()
        {
            Console.WriteLine("This is CustomHttpModule.Dispose");
        }

        /// <summary>
        /// 注册动作context
        /// </summary>
        /// <param name="context"></param>
        public void Init(HttpApplication context)
        {
            context.BeginRequest += (s, e) =>
            {
                this.CustomHttpModuleHandler?.Invoke(context, null);
            };

            //为每一个事件,都注册了一个动作,向客户端输出信息
            context.AcquireRequestState += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "AcquireRequestState        "));
            context.AuthenticateRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "AuthenticateRequest        "));
            context.AuthorizeRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "AuthorizeRequest           "));
            context.BeginRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "BeginRequest               "));
            context.Disposed += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "Disposed                   "));
            context.EndRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "EndRequest                 "));
            context.Error += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "Error                      "));
            context.LogRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "LogRequest                 "));
            context.MapRequestHandler += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "MapRequestHandler          "));
            context.PostAcquireRequestState += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostAcquireRequestState    "));
            context.PostAuthenticateRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostAuthenticateRequest    "));
            context.PostAuthorizeRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostAuthorizeRequest       "));
            context.PostLogRequest += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostLogRequest             "));
            context.PostMapRequestHandler += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostMapRequestHandler      "));
            context.PostReleaseRequestState += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostReleaseRequestState    "));
            context.PostRequestHandlerExecute += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostRequestHandlerExecute  "));
            context.PostResolveRequestCache += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostResolveRequestCache    "));
            context.PostUpdateRequestCache += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PostUpdateRequestCache     "));
            context.PreRequestHandlerExecute += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PreRequestHandlerExecute   "));
            context.PreSendRequestContent += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PreSendRequestContent      "));
            context.PreSendRequestHeaders += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "PreSendRequestHeaders      "));
            context.ReleaseRequestState += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "ReleaseRequestState        "));
            context.RequestCompleted += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "RequestCompleted           "));
            context.ResolveRequestCache += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "ResolveRequestCache        "));
            context.UpdateRequestCache += (s, e) => context.Response.Write(string.Format("<h2 style='color:#00f'>来自CustomHttpModule 的处理,{0}请求到达 {1}</h2><hr>", DateTime.Now.ToString(), "UpdateRequestCache         "));
        }
    }
}

可以在IHttpModule.Init方法内部为HttpApplication事件注册动作。

然后我们需要在Web.config里面配置下这个HttpModule节点,如下所示:

<!--托管管道模式为集成时使用这个配置-->
<system.webServer>
  <modules>
    <add name="MyCustomHttpModule" type="AspNetPipeline.Pipeline.CustomHttpModule,AspNetPipeline"/>
  </modules>
</system.webServer>

其中type值为【类的完整名称 + 英文逗号 + 项目名称】。

此处,我们还在CustomHttpModule里面定义了一个CustomHttpModuleHandler事件,那么我们要在哪里给这个事件注册动作呢?

可以在Global.asax里面为CustomHttpModuleHandler事件绑定动作,如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace AspNetPipeline
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }

        /// <summary>
        /// 为HttpModule里面的事件注册动作
        /// 配置文件module名称_module里面事件名称
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void MyCustomHttpModule_CustomHttpModuleHandler(object sender, EventArgs e)
        {
            HttpContext.Current.Response.Write("<h2>This is MvcApplication/MyCustomHttpModule_CustomHttpModuleHandler</h2>");
        }
    }
}

最后我们再来访问下 /Home/Index,看下运行结果:

其中“This is Home/Index View”这句话就是由某一个具体的IHttpHandler处理器对象来处理的。

PS

1、对HttpApplication里面的事件进行动作注册的,就叫IHttpModule。

2、自定义一个HttpModule--配置文件注册--然后任何一个请求都会执行Init里面注册给HttpApplication事件的动作。

3、HttpModule里面发布一个事件CustomHttpModuleHandler,在Global.asax增加一个动作, MyCustomHttpModule_CustomHttpModuleHandler(配置文件module名称_module里面事件名称),请求响应时,该事件会被执行。

4、HttpModule是对HttpApplication里面的事件注册动作,而Global则是对HttpModule里面的事件注册动作。

 

介绍到这里,我们知道Http的任何一个请求最终一定是由某一个具体的HttpHandler来处理的,不管是成功还是失败

竟然如此,那我们能不能自定义一个HttpHandler来处理一些特殊的请求呢?答案:可以的。

例如:我们想要实现某个特定后缀(如.log后缀)的所有请求都指派给我们自定义的HttpHandler来处理,那这个要如何实现呢?

下面我们就带大家来实现这一想法,先来看下示例所涉及到的代码的目录结构:

自定义HttpHandler:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;

namespace AspNetPipeline.Pipeline
{
    /// <summary>
    /// 自定义HttpHandler
    /// 
    /// 我们可以从请求级出发,避开默认机制,动态响应 .log(自定义)后缀的请求
    /// </summary>
    public class CustomHttpHandler : IHttpHandler
    {
        public bool IsReusable => true;

        /// <summary>
        /// 处理请求
        /// </summary>
        /// <param name="context"></param>
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/html";
            context.Response.WriteFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web.config"));
        }
    }
}

在Web.config里面进行配置说明:

<!--托管管道模式为集成时使用这个配置-->
<system.webServer>
  <!--可以给自己留个后门,比如读个日志文件啥的-->
  <handlers>
    <!--这句话的意思就是.log后缀的所有请求就指派给我们的CustomHttpHandler来处理-->
    <add name="ReadLog" verb="*" path="*.log" type="AspNetPipeline.Pipeline.CustomHttpHandler,AspNetPipeline"/>
  </handlers>

  <modules>
    <add name="MyCustomHttpModule" type="AspNetPipeline.Pipeline.CustomHttpModule,AspNetPipeline"/>
  </modules>
</system.webServer>

此时我们去访问一下 /log.log 会发现报错了,如下所示:

这是因为此时它被MVC的路由匹配了,所以无法找到资源。

我们需要到MVC路由配置那边把它忽略掉:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace AspNetPipeline
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            //忽略路由  正则表达式  {resource}表示变量   a.axd/xxxx   resource=a   pathInfo=xxxx
            //.axd是历史原因,最开始都是WebForm,请求都是.aspx后缀,IIS根据后缀转发请求;
            //MVC出现了,没有后缀,IIS6以及更早版本,打了个补丁,把MVC的请求加上个.axd的后缀,然后这种都转发到网站
            //新版本的IIS已经不需要了,遇到了就直接忽略,还是走原始流程
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //该行框架自带的

            //.log后缀的请求忽略掉,不走MVC流程,而是用我们自定义的CustomHttpHandler处理器来处理
            routes.IgnoreRoute("{resource}.log/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

最后我们再来访问下 /log.log 运行结果如下所示:

可以发现此时访问正常了,我们右键查看网页源代码,会发现输出了我们想要的东西,如下所示:

之前我们在访问 .aspx 页面时可能有个错觉,感觉就是访问物理路径,然而从上面这个例子可以看出这是不对的。

它应该是由我们的配置文件来指定映射关系:后缀名与处理程序的关系(IHttpHandler---IHttpHandlerFactory) 

自定义HttpHandler处理,就是可以处理各种后缀请求,可以加入自己的逻辑。

为了加深印象,下面我们就再举个防盗链的例子:

防盗链HttpHandler:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace AspNetPipeline.Pipeline
{
    /// <summary>
    /// 防盗链HttpHandler
    /// </summary>
    public class ImageHttpHandler : IHttpHandler
    {
        public bool IsReusable => true;

        public void ProcessRequest(HttpContext context)
        {
            // 如果UrlReferrer为空,大部分都是爬虫,则显示一张默认的禁止盗链的图片
            if (context.Request.UrlReferrer == null || context.Request.UrlReferrer.Host == null)
            {
                context.Response.ContentType = "image/JPEG";
                context.Response.WriteFile("/Content/Image/Forbidden.jpg");
            }
            else
            {
                // 如果UrlReferrer中不包含自己站点主机域名,则显示一张默认的禁止盗链的图片
                if (context.Request.UrlReferrer.Host.Contains("localhost"))
                {
                    // 获取文件服务器端物理路径
                    string fileName = context.Server.MapPath(context.Request.FilePath);
                    context.Response.ContentType = "image/JPEG";
                    context.Response.WriteFile(fileName);
                }
                else
                {
                    context.Response.ContentType = "image/JPEG";
                    context.Response.WriteFile("/Content/Image/Forbidden.jpg");
                }
            }
        }
    }
}

在Web.config里面进行配置:

<!--托管管道模式为集成时使用这个配置-->
<system.webServer>
  <!--可以给自己留个后门,比如读个日志文件啥的-->
  <handlers>
    <!--这句话的意思就是.log后缀的所有请求就指派给我们的CustomHttpHandler来处理-->
    <add name="ReadLog" verb="*" path="*.log" type="AspNetPipeline.Pipeline.CustomHttpHandler,AspNetPipeline"/>

    <!--防盗链处理-->
    <add name="gif" path="*.gif" verb="*" type="AspNetPipeline.Pipeline.ImageHttpHandler,AspNetPipeline" />
    <add name="png" path="*.png" verb="*" type="AspNetPipeline.Pipeline.ImageHttpHandler,AspNetPipeline" />
    <add name="jpg" path="*.jpg" verb="*" type="AspNetPipeline.Pipeline.ImageHttpHandler,AspNetPipeline" />
    <add name="jpeg" path="*.jpeg" verb="*" type="AspNetPipeline.Pipeline.ImageHttpHandler,AspNetPipeline" />
  </handlers>

  <modules>
    <!--自定义HttpModule-->
    <!--<add name="MyCustomHttpModule" type="AspNetPipeline.Pipeline.CustomHttpModule,AspNetPipeline"/>-->
  </modules>
</system.webServer>

PS:此处需要将自定义的CustomHttpModule这个配置节点给注释掉,因为在CustomHttpModule中注册的动作有向客户端输出字符串,这会导致图片输出异常。

图片存放路径如下所示:

访问 /content/image/scenery.jpg 运行结果如下所示:

可以发现此时返回的并不是我们访问的真实图片,而是防止盗链的图片。 

至此本文就全部介绍完了,如果觉得对您有所启发请记得点个赞哦!!! 

 

Demo源码: 

链接:https://pan.baidu.com/s/1Rb4uq0yB_iB3VsonwiCFKw 
提取码:68r6

此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/15201368.html

版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!

posted @ 2021-09-05 18:14  谢友海  阅读(596)  评论(0编辑  收藏  举报