• 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:WindowsMicrosoft.NETFramework64v4.0.30319Configweb.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

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

  • 相关阅读:
    【dp专题1】hdu1003 D
    #网络之宙斯盾航母#-----占位--------linux
    面向过程6-----#过程之王#----占个位-----汇编语言----(面向机器)
    面向过程5----c#
    高级语言学习总结
    js实现求二元一次方程的根
    小知识点总结
    js小案例,判断年龄是否成年
    js实现结婚吧小案例
    面向过程4-----perl
  • 原文地址:https://www.cnblogs.com/xyh9039/p/15201368.html
Copyright © 2020-2023  润新知