现在的位置: 首页 > 综合 > 正文

扩展HTPP处理程序和HTTP管道

2013年06月03日 ⁄ 综合 ⁄ 共 8612字 ⁄ 字号 评论关闭
文章目录

应用程序事件的管道不仅仅限于对 .aspx Web窗体的请求,它还可用于创建自己的处理程序来处理自定义文件类型。为什么我们要创建自己的处理程序呢?有时候使用低层接口会非常方便,通过它们仍然可以访问 Response 和 Request 之类的有用对象,但不必使用基于控件的 Web窗体模型。通过避免完整的 Web控件模型,可以节省负载,因为 ASP.NET 不必执行很多步骤(如创建网页对象、持久化视图状态等)。

 

HTTP 处理程序

对 ASP.NET 应用程序的每个请求都由成为 HTTP 处理程序的特殊组件处理。HTTP 处理程序是 ASP.NET 请求处理框架的骨架。ASP.NET 使用不同的HTTP处理程序来处理不同文件类型的请求。

 

可以通过两种方法注册 HTTP 处理程序。

1. 运行老版本的IIS,或者以经典模式运行 IIS 7.x ,则应该把 HTTP 处理程序添加到 web.config 文件中的 <system.web>元素的<httpHandlers>配置节里。在这个节里可以注册新处理程序的<add>元素以及注销已有处理程序的<remove>元素。在根web.config文件里,你可以看到核心的 HTTP 处理程序就是这样注册的。

<httpHandlers>

    <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="*.config" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />

    <add path="*.cs" verb="*" type="System.Web.HttpForbiddenHandler" validate="True" />

    ......

</httpHandlers>

对 .config 和 .cs 文件的请求由 HttpForbiddenHandler 处理,它总是产生一个异常来通知用户此文件类型无法提供。

对 .aspx 文件的请求由 PageHandlerFactory 处理。这并不是真正的 HTTP处理程序,而是一个负责创建合适 HTTP处理程序的工厂类。额外的这一层允许工厂创建不同的处理程序或根据请求的其他信息对处理程序进行不同的配置

 

2. 如果 IIS 7.x 运行于整合模式,上面这种注册 HTTP处理程序的方法就不起作用了。这时,IIS 会读取 <system.webServer>配置节并使用它的<handlers>配置节定义的处理程序。

 

创建自定义 HTTP 处理程序

如果你希望工作于 Web 窗体模型之外的低层来支持特殊格式的处理,那么可以实现自己的 HTTP处理程序。

要创建一个自定义的 HTTP处理程序,需要创建一个实现 IHttpHandler 接口的类。

可以把这个类放到 App_Code 目录,或者把它编译为某个独立的 DLL 程序集的一部分(勿忘添加一个引用)。

 

下面的示例代码很简单,只返回一段带有固定信息的 HTML 块。

using System;

using System.Web;

 

namespace HttpExtensions

{

    /// <summary>

    /// 实现 IHttpHandler 接口必须实现下面的两个方法

    /// </summary>

    public class SimpleHandler : IHttpHandler

    {

        public bool IsReusable

        {

            // 如果无法为其他请求重用托管处理程序,则返回 false。

            // 如果按请求保留某些状态信息,则通常这将为 false。

            get { return true; }

        }

 

        /// <summary>

        /// ASP.NET 获得请求时会调用这个方法。

        /// HTTP 处理程序在这里处理所有的请求。

        /// 可以通过传入的 HttpContext对象来访问 ASP.NET 固有的对象(如 Request,Response,Server)

        /// </summary>

        /// <param name="context"></param>

        public void ProcessRequest(HttpContext context)

        {

            HttpResponse response = context.Response;

            response.Write("<html><body><h1>Rendered by the SimpleHandler");

            response.Write("</body>");            

        }

    }

}

注:

如果你在一个类库中添加这个处理程序,那么需要添加对 System.Web.dll 文件的引用,它包含大量的 ASP.NET 类,没有这个引用就无法使用 IHttpHandler 接口和 HttpContext 之类的类型。

 

配置自定义的 HTTP处理程序

创建了 HTTP处理程序后,需要配置 Web应用程序的web.config文件。

<system.web>

  <compilation debug="true" targetFramework="4.0"/>

  <trace enabled="true" requestLimit="10" pageOutput="false" traceMode="SortByTime" localOnly="true" />

  <httpHandlers>

    <add verb="*" path="test.simple" type="HttpExtensions.SimpleHandler,HttpExtensions"/>

  </httpHandlers>

</system.web>

注册 HTTP程序,需要指定 3 个重要的细节

1. verb 特性表明请求类型是 POST 还是 GET,* 表示所有请求类型。

2. path 特性表明对哪种扩展名类型的文件进行处理。这个示例里指定了完全文件名 test.simple ,也可以使用如 *.simple 。

3. type 特性标识了 HTTP处理程序类。它分为 2 部分,以逗号间隔开:

   (a)完全限定类名,本例中是:HttpExtensions.SimpleHandler

   (b)包含类的 DLL 程序集的名字,本例是:HttpExtensions.dll;dll 扩展名是默认的,你不必也不能写上它。

 

运行效果:

image

 

 

无需配置的 HTTP处理程序

无论哪个版本的 IIS ,都支持以 .ashx 为扩展名的一般处理程序,它们不需要在配置文件中注册,所有对 ashx文件的请求都自动被识别为自定义 HTTP处理程序。

 

.ashx 文件以 WebHandler 指令开头,它表示类应该通过这个文件来显示。

<%@ WebHandler language="C#" Class="HttpExtensions.SimpleHandler" >

在 .ashx 文件中你可以直接定义类,如果把前面的示例保存为 simple.ashx,那么无论什么时候客户请求 simple.ashx 文件时,相应的处理程序都将被执行。

 

究竟是使用配置文件还是以 .ashx 文件只是个人偏好问题。不过 .ashx 文件通常用于为单个 Web 应用程序设计的一些简单的扩展,配置文件给你的灵活性更大一些。可以把 HTTP 处理程序注册到多个应用程序里(通过在 web.config 文件里注册它或者把程序集放到 GAC 里)。如果要使用 .ashx 文件获得同样的效果,就必须把 .ashx 文件复制到每个虚拟目录。

 

创建高级的 HTTP处理程序

先前的示例,只是简单的返回一个静态的 HTML块。不过你可以创建更富想象力的处理程序。例如可以读取发送到页面或者查询字符串中所提供的数据,并根据这些数据来定制将要呈现的输出。

下面这个示例,它显示请求文件的源代码:

using System;

using System.Web;

using System.IO;

 

namespace HttpExtensions

{

    public class SourceHandler : IHttpHandler

    {

        public bool IsReusable

        {

            get { return true; }

        }

 

        public void ProcessRequest(HttpContext context)

        {

            // 很容易就能获取一些 Page 的固有对象

            HttpResponse response = context.Response;

            HttpRequest request = context.Request;

            HttpServerUtility server = context.Server;

 

            response.Write("<html><body>");

 

            // 获取查询字符串

            string file = request.QueryString["file"];

            try

            {

                // 显示文件内容

                response.Write("<b>Listing " + file + "</b><br />");

                StreamReader sr = File.OpenText(server.MapPath(Path.Combine("./", file)));

                string line = "";

                while (line != null)

                {

                    line = sr.ReadLine();

                    if (line!=null)

                    {

                        // 对字符串进行 HTML 编码

                        line = server.HtmlEncode(line);

 

                        line = line.Replace(" ", "&nbsp;");

                        line = line.Replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");

                        response.Write(line + "<br />");

                    }

                }

                sr.Close();

            }

            catch (Exception err)

            {

                response.Write(err.Message);

            }

            response.Write("</body>");

        }

    }

}

注册配置文件:

<httpHandlers>

  <add verb="*" path="test.simple" type="HttpExtensions.SimpleHandler,HttpExtensions"/>

  <add verb="*" path="source.simple" type="HttpExtensions.SourceHandler,HttpExtensions"/>

</httpHandlers>

验证一下程序的功能:

http://localhost:16261/WebSite1/Chapter05/source.simple?file=SimpleHandler.cs

image

总结:

这段代码找到请求的文件,一行行的读取它的内容,并进行了字符串的替换(将空格换为 &nbsp等)和 HTML编码创建一个可以被浏览器安全显示的内容。

 

 

为非 HTML 内容创建 HTTP 处理程序

       最有趣的是HTTP处理程序当中有一些并不生成HTML,而是呈现不同类型的内容,如图片。这种方式提供了通过编程来获取或生成内容的灵活性,而不必依赖于固定的文件,例如,可以从数据库读取大型的 ZIP 文件内容,用 Response.BinaryWrite() 方法把它发送到客户端。

       请求图片时,HTTP处理程序检查请求的 referrer 首部。referrer 首部提供主机名,它表明对图片的链接是来自网站内部的页面还是来自其他人的网站。如果是来自其他人的网站将会有一些潜在的问题。这个页面不仅在偷窃你的图片,还给你的 Web 服务器带来额外的负荷,如果出现在流行的网站上,还会产生显著的额外负荷并缩减你自己页面可用的带宽,这个问题俗称盗链。

       以下是在 ASP.NET 中实现这一解决方案的 HTTP 处理程序。为了使这段代码可用,必须导入 System.Globalization 和 System.IO 命名空间。

using System;

using System.Web;

using System.IO;

using System.Globalization;

 

namespace HttpExtensions

{

    public class ImageGuardHandler : IHttpHandler

    {

        public bool IsReusable

        {

            get { return true; }

        }

 

        public void ProcessRequest(HttpContext context)

        {

            HttpResponse response = context.Response;

            HttpRequest request = context.Request;

            string imagePath = null;

 

            // 获取有关客户端上次请求的 URL 的信息,该请求链接到当前的 URL。

            if (request.UrlReferrer != null)

            {

                // 比较当前请求 Url.Host 和上一次请求的 Host,判断是否来自网站内部

                if (String.Compare(request.Url.Host, request.UrlReferrer.Host,

                    true, CultureInfo.InvariantCulture) == 0)

                {

                    // 获取与当前请求 Url 对应的物理文件系统路径

                    imagePath = request.PhysicalPath;

                    if (!File.Exists(imagePath))

                    {

                        response.Write("Image not found!");

 

                        // 获取或设置返回给客户端的输出的 HTTP 状态代码。

                        // 表示返回到客户端的 HTTP 输出状态的整数。默认值为 200 (OK)。

                        response.StatusCode = 404;

                        return;

                    }

                }

            }

 

            // 盗链,将地址重置为错误图片地址

            if (imagePath == null)

            {

                imagePath = context.Server.MapPath("./Images/notAllowed.gif");

            }

 

            // 获取或设置输出流的 HTTP MIME 类型。

            // 输出流的 HTTP MIME 类型。默认值为“text/html”。

            response.ContentType = "image/" + Path.GetExtension(imagePath).ToLower();

 

            // 将指定文件的内容作为文件块直接写入 HTTP 响应输出流。

            response.WriteFile(imagePath);

        }

    }

 

    // Path 类中包括对文件或目录路径操作的大量方法

    // Path.GetExtension() 方法返回指定路径字符串的扩展名

}

注册配置文件:

<httpHandlers>

  <add verb="*" path="*.gif" type="HttpExtensions.ImageGuardHandler,HttpExtensions"/>

  <add verb="*" path="*.png" type="HttpExtensions.ImageGuardHandler,HttpExtensions"/>

</httpHandlers>

注意:

      这种防盗链的方式远远不够完美,但也可用阻止一般的盗链者。了解编程的用户可以通过一点 JavaScript 代码轻易的回避这种限制。一些 Web 开发人员创建了更精细的系统。

      例如,可以动态产生时间戳代码添加到图片链接,如果时间戳已经过期,这表明链接已经被复制并在它被创建很长时间后用于其他的网站

      不过,不管哪种技术都不能避免用户创建图片的副本并直接在他们的网站使用

 

思考:

       你也许可以想到使用 HTTP 处理程序的多种方式。可以呈现自定义的图片,执行即时的数据库查询或者返回二进制数据。这些示例扩展了 ASP.NET 结构但略过了 Web 页面模型,结果产生了更轻量,更高效的组件。

       还可以创建异步运行的 HTTP 处理程序,这里不作详细说明。但要知道的是,它们创建新的线程执行自己的工作,而不是使用 ASP.NET 的工作者线程。这在执行耗时但并不大量使用 CPU 的任务时提升了扩展性。ASP.NET 只允许固定数量(通常是25个)的工作者线程同时运行。达到这一极限时,即使还有CPU资源可用,额外的请求也只能在队列中等待

 

HTTP处理程序和会话状态

       默认情况下,HTTP 处理程序不能访问客户特定的会话状态。因为 HTTP 处理程序一般用于较低层任务,跳过需要序列化和读取会话状态信息的步骤可用略微提升性能。

       不过你却是需要访问会话状态的话,只要实现下面两个接口之一即可:

System.Web.SessionState.IRequiresSessionState:要修改或添加会话信息时

System.Web.SessionState.IReadOnlySessionState:只要求对会话进行读取时

       这两个接口只是标记接口,不包含任何方法,不必编写额外的代码区起用会话支持。

       ProcessRequest(HttpContext context) 中的 HttpContext 对象提供一个 Session 属性

 

 

HTTP 模块

       ASP.NET 处理页面时还使用另一个被称为 HTTP 模块的元素。HTTP 模块通过处理应用程序事件来参与请求的处理,这和 global.asax 文件非常相似。一个请求会经过数个模块然后到达处理程序,然后又从刚才经过的模块层层返回给客户。

创建自定义 HTTP 模块

       创建一个实现 System.Web.IHttpModule 接口的类,把自定义模块注册到 web.config 的<httpModules>节。不必配置 IIS 就可以使用自己的 HTTP 模块。这是因为模块被自动应用于每个 Web 请求。

       那么,HTTP 模块是如何把自己插入到 ASP.NET 请求处理管道的呢?

       它实现的方式和 global.asax 文件相同。当一个 HTTP 模块被创建时,它注册自己接收特定的全局应用程序事件,当这些事件发生时,ASP.NET 会调用所有对事件感兴趣的 HTTP 模块。

 

       看下面的示例:

using System;

using System.Web;

using System.Diagnostics;

 

namespace HttpExtensions

{

    public class LogUserModule : IHttpModule

    {

        public void Dispose()

        {

 

        }

 

        /// <summary>

        /// 这个方法允许 HTTP 模块注册自己的事件处理程序

        /// 以接收 HttpApplication 对象的事件

        /// </summary>

        /// <param name="context"></param>

        public void Init(HttpApplication context)

        {

            context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);

        }

 

        void context_AuthenticateRequest(object sender, EventArgs e)

        {

            // 获取当前用户的标识

            string name = HttpContext.Current.User.Identity.Name;

 

            // EventLog 类提供与 Windows 事件日志的交互。

            EventLog log = new EventLog();

 

            // 获取或设置在写入事件日志时要注册和使用的源名称。

            log.Source = "Log User Module";

 

            // 将信息类型项与给定的消息文本一起写入事件日志。

            log.WriteEntry(name + " was authenticated.");

        }        

    }

}

注册配置文件节:

<httpModules>

  <add name="LogUserModule" type="HttpExtensions.LogUserModule,HttpExtensions" />

</httpModules>

请求 Web 应用程序任意页面可验证这个模块:

image

 

 

处理来自其他模块的事件

       HttpApplication 类并没有提供某些全局事件,但他们也非常重要。这包括由其他 HTTP 模块引发的事件,如会话开始和结束时引发的事件。

 

       幸好,我们可以再 Init() 中处理这些事件,只要使用一个稍微不同的方式即可。

public void Init(HttpApplication context)

{

    // 先引入命名空间 System.Web.SessionState

    SessionStateModule sessionMod = 

context.Modules["Session"] as SessionStateModule

;

    sessionMod.Start += new EventHandler(OnSessionStart);

}

       HttpApplication 类通过 Modules 集合提供当前 HTTP 管道的所有模块!你可以通过名称读取模块,然后使用委托代码来连接事件处理程序!

抱歉!评论已关闭.