Intercepting Filter(截取筛选器)
我们在建立Web应用程序时需要更多的控制权限,或者需要在Web请求前或后,插入自己的业务逻辑。
如何围绕 Web页面请求来实现公共的预处理和后处理步骤?
有许多方法可以解决此问题,因此您需要考虑这会涉及哪些影响因素和权衡因素:
A、常见的做法是,将较低级别的功能(如处理 HTTP 头、Cookie 或字符编码)与应用程序逻辑分离,这样有助于限制更改的扩散。
B、许多预处理和后处理任务是所有 Web 页面公共的,应该在中心位置实现这些功能,以避免代码重复。
C、许多较低级别的功能不相互依赖,这样不会互相影响
D、能够在部署时而不是编译时添加或删除模块
E、各模块可以互相组合
解决方案
创建一串可组合的筛选器,以便在 Web 页面请求期间实现公共的预处理和后处理任务。
图 1:一串可组合筛选器
筛选器构成了一系列独立模块,在将页面请求传递到控制器对象之前,这些模块可以链接在一起以执行一组公共的处理步骤。因为各个筛选器实现的是完全相同的接口,所以它们彼此之间没有显式依赖性。因此,可以在不会影响现有筛选器的情况下添加新的筛选器。方法是基于配置文件动态地对它们进行实例化。
筛选器链
Intercepting Filter 的直接实现方式是一个筛选器链,这个筛选器链可以用来遍历一个由所有筛选器组成的列表。Web 请求处理程序在将控制权传递到应用程序逻辑之前将首先执行筛选器链。
当 Web 服务器收到页面请求时,请求处理程序首先将控制权传递给 FilterChain(筛选器链)对象。此对象维护着一个包含所有筛选器的列表,并按顺序调用每个筛选器。FilterChain 可以从配置文件中读取筛选器顺序,以实现部署时的可组合性。每个筛选器都有机会修改传入请求。例如,它可以修改 URL,或添加应用程序要使用的头字段。执行完所有筛选器之后,"请求处理程序"将控制权传递给控制器,后者将执行应用程序功能
图 3:Intercepting Filter 序列关系图
Decorator
Intercepting Filter 模式的一个有趣的替代实现是使用 Decorator 模式来包装 Front Controller。Decorator 可以包装一个对象,使其提供与原始对象相同的接口。因此,对于引用原始对象的任何其他对象来说,此包装是透明的。因为原始对象的接口和包装是完全相同的,所以可以添加用来包装该包装器的其他包装器,以创建与筛选器链很类似的包装器链。在每个包装器内,可以执行预处理和后处理功能。
下图显示如何使用此概念实现 Intercepting Filter。每个筛选器都实现了Controller 接口。它还包含对实现了 Controller 接口的下一个对象的引用,该对象可能是实际的控制器 (concreteController) 或另一个筛选器。即时筛选器可以相互调用,筛选器之间也没有直接的依赖性,因为每个筛选器仅引用 Controller 接口,而不是下一个筛选器类。
在筛选器将控制权传递给下一个筛选器之前,它有机会执行预处理任务。同样,在筛选器链中的其余筛选器处理完请求之后,筛选器也有机会执行后处理任务。
事件驱动的筛选器
此模型与 Observer模式中所述的事件模型有一些类似之处。在这两种情况下,对象都可以在原始对象不依赖于观察器的情况下"预订"事件。对象不依赖于任何特定观察器,因为它通过抽象接口调用观察器。Intercepting Filter 和 Observer 的主要不同是,观察器通常不修改源对象,只是被动地"观察"源对象中发生的事件。另一方面,Intercepting Filter 的用途是截取和修改在调用它时所处的上下文。上图 还很好地说明了每个筛选器如何截取 Web 服务器框架内的事件序列。
变体
在大多数情况下,由于筛选器虽然操作了上下文但不会影响执行流,在这个意义上,筛选器是被动的。但是,如果筛选器截取了 Web 请求,通常必须把筛选器设计为将请求重定向到其他页面。例如,如果验证失败,则验证筛选器可能将请求重定向到错误页面或登录页面。
为了演示这些筛选器如何影响 Web 请求的流程,下图显示了一个典型的筛选器方案序列,其中的截取筛选器不会干涉消息流。
不干涉消息流的截取筛选器
显示一个替代序列,其中 Filter One 根据请求的类型将消息流重定向到其他页面。
重定向消息流的截取筛选器
在此方案中,未呈现任何页面,但是产生了一个重定向头(HTTP 响应 302),并返回给客户端。此头信息导致客户端向在重定向头中所指定的 URL 发出新的请求。因为此类型的重定向需要来自客户端浏览器的另一个请求,所以它通常称为客户端重定向。主要的缺点是,客户端浏览器必须发出两个请求才能检索页面。这样,就减慢了页面显示速度,还会导致书签的复杂化,因为客户端将为重定向的 URL 创建书签,而这通常是不好的。
另一方面,服务器端重定向将把请求转发到其他页面,而不要求到客户端的往返行程。它们通过将控制权返回给 httpRunTime 对象做到这一点,而 httpRunTime 对象将沿请求上下文直接调用其他Page Controller。此传递发生在服务器内部,不会涉及客户端。因此,您不必对请求重复执行任何公共预处理任务。
服务器端重定向在两个常见情况下使用:可以在 Intercepting Filter 中 使用 URL 操作,以允许客户端使用虚拟 URL 将参数传递给应用程序。另一常见方法是使用 Front Controller。Front Controller 在一个中心组件中处理所有页面请求,然后将控制权传递给适当的命令。如果 Web 应用程序具有可动态配置的导航路径,则 Front Controller 是很有用的。
Intercepting Filter 模式具有下列优缺点:
优点
1、分隔任务。筛选器中包含的逻辑与应用逻辑相互分隔。因此,在低级功能发生改变(例如,如果从 HTTP 迁移到 HTTPS,或者将会话管理从 URL 重写迁移到隐藏的窗体域)时,应用程序代码不受影响。
2、灵活性。各个筛选器是相互独立的。因此,可以将任何筛选器的组合链接在一起,而不必对任何筛选器进行代码更改。
3、中心配置。由于筛选器具有可组合性,因此您可以使用单个配置文件来加载筛选器链。您可以修改单个配置文件以确定要插入到请求处理中的筛选器列表,而不需要使用许多源代码。
4、部署时可组合性。在运行时,可以根据配置文件构造 Intercepting Filter 链。因此,在部署过程中可以更改筛选器的顺序,而不必修改代码。
5、重用性。由于筛选器不依赖于其操作环境(它们所操作的上下文除外),因此可以在其他 Web 应用程序中重用各个筛选器。
缺点
1、顺序依赖性。截取筛选器不显式依赖于任何其他筛选器。但是,筛选器可能对传递给它们的上下文相关信息进行假定。例如,一些筛选器可能期望在调用它们之前已发生某个处理。 在配置筛选器链时,请考虑这些隐式依赖性。一些框架可能不会保证筛选器之间的执行顺序。如果程序要求严格的顺序,则硬编码的方法调用可能比动态筛选器链更好。
2、共享状态。除了操作上下文以外,筛选器没有用于彼此共享状态信息的显式机制。对于将信息从筛选器传递到另一个筛选器,这也是正确的。例如,如果筛选器基于头字段的值来分析浏览器类型,则无法简单地将此信息传递到应用程序控制器。最常用的方法是 将虚设的头字段添加到包含筛选器输出的请求上下文中。然后,控制器可以提取虚设头字段,并根据其值作出决定。遗憾的是,您失去了筛选器和控制器之间的任何 编译时检查或类型安全性。这是松耦合的不利方面。
截取筛选器与控制器
因为截取筛选器是正好在控制器之前和之后执行的,所以有时可能很难确定是在截取筛选器内、还是在控制器内实现功能。下列准则为作出此决定提供了一些指导:
筛选器更适合于处理与传输有关的低级功能,如字符集解码、解压缩、会话验证、客户端浏览器类型识别和流量日志记录。这些类型的操作往往是封装良好的、高效的、无状态的。因此,将这些操作链接在一起是很容易的,一个操作不必将状态信息传递给另一操作。
与模型进行交互的真实应用程序功能最好在控制器或控制器的帮助器内实现。这些类型的功能通常不拥有筛选器所要求的那种可组合性。
在大多数情况下,筛选器内部的处理不依赖于应用程序的状态;无论如何,都会执行它。即使页面控制器可能包含常见功能,最好也要保留逐个覆盖行为的机会。控制器比筛选器链更适合于此任务。
许多筛选器实现(例如,IIS ISAPI 筛选器)在应用程序服务器内的较低层上执行。这为筛选器提供了很大控制(在调用筛选器之前发生的事件不多),但会阻止它们访问应用程序层提供的许多功能(如会话管理)。
因为筛选器是针对每个 Web 页面请求而执行的,所以性能是至关重要的。因此,框架可能限制实现语言的选择。例如,大多数 ISAPI 筛选器是使用已编译语言(如 C++)实现的。如果可以方便地使用 Microsoft Visual Basic® 开发系统或 Microsoft Visual C#® 开发工具来编写这些代码,并能完整地访问 .NET Framework,您就不必使用 C++ 编写复杂应用程序逻辑的代码。
在ASP.NET 中使用 HTTP 模块实现 Intercepting Filter
Microsoft ASP.NET 中利用许多不同类型的请求来构建 Web应用程序。有些请求被转到适当的网页,而其他请求必须在处理之前以某种方式进行记录或修改。
Intercepting Filter (截取筛选器)模式的 ASP.NET 实现是该模式中所描述的事件驱动型筛选器的一个例子。ASP.NET 提供了应用程序可以在请求处理期间钩挂的一系列事件。这些事件保证了请求的状态。各个筛选器都是通过一个 HTTP 模块实现的。HTTP 模块是一个实现 IHttpModule 接口并确定应该何时调用筛选器的类。ASP.NET 包括一组可由应用程序使用的 HTTP 模块。例如,SessionStateModule 由 ASP.NET 提供,以便向应用程序提供会话状态服务。您可以创建自己的自定义 HTTP 模块,以便根据应用程序的需要筛选请求或响应。
编写自定义 HTTP 模块的一般过程是:
实现 IHttpModule 接口。
处理 Init 方法并注册到您需要的事件。
处理事件。
如果必须清理,也可以选择实现 Dispose 方法。
在 web.config 文件中注册模块。
事件
下表显示了可以使用 ASP.NET 截取的、在处理请求期间产生的事件。所有事件都是按照发生的顺序列出的。
第一个列表显示了处理请求之前产生的事件。
BeginRequest. 此事件标志着这是一个新请求;每个请求都必须产生该事件。
AuthenticateRequest. 此事件标志着所配置的身份验证机制已经验证了请求。附加到此事件可向筛选器确保请求已通过身份验证。
AuthorizeRequest. 与 AuthenticateRequest 一样,此事件标志着现在请求在处理过程中又前进了一步,并且请求已经得到授权。
ResolveRequestCache. 输出缓存模块使用此事件来简化对已经缓存的请求所进行的处理。
AcquireRequestState. 此事件标志着应该获得各个请求状态。
PreRequestHandlerExecute. 此事件标志着请求处理程序将要执行。这是在调用此请求的 HTTP 处理程序之前您可以参与的最后一个事件。
下一个列表显示了处理请求之后产生的事件。这些事件是按照发生的顺序列出的:
PostRequestHandlerExecute. 此事件标志着 HTTP 处理程序已经完成了对请求的处理。
ReleaseRequestState. 此事件标志着应该存储请求状态,因为应用程序已经完成了对请求的处理。
UpdateRequestCache. 此事件标志着代码处理已完成,可以将文件添加到 ASP.NET 缓存中。
EndRequest. 此事件标志着已完成对请求的所有处理。这是应用程序结束时所调用的最后一个事件。
此外,以下三个请求处理前事件可以按不确定顺序引发:
PreSendRequestHeaders.此事件标志着 HTTP 头将要发送给客户端。因此可以在发送之前添加、删除或修改头信息。
PreSendRequestContent. 此事件标志着内容将要发送给客户端。这为发送之前修改内容提供了一个机会。
Error. 此事件标志着有未处理的异常。
下面是内置在 Microsoft .NET 中的截取筛选器的示例:
DefaultAuthenticationModule. 此筛选器确保 Authentication 对象出现在 HttpContext 对象中。
FileAuthorizationModule. 此筛选器验证远程用户是否拥有访问所请求的文件时所需的 Microsoft Windows NT 权限。
FormsAuthenticationModule. 此筛选器使 ASP.NET 应用程序能够使用窗体验证。
PassportAuthenticationModule.此筛选器提供了包装 PassportAuthentication 服务以便进行 Passport 身份验证的包装器。
SessionStateModule. 此筛选器为应用程序提供会话状态服务。
UrlAuthorizationModule. 此筛选器提供基于 URL 的授权服务,以便允许或拒绝对指定 URL 进行访问。
WindowsAuthenticationModule. 此筛选器使 ASP.NET 应用程序能够使用 Microsoft Windows或 Internet 信息服务 (IIS) 的身份验证机制。
测试考虑事项
如果没有 ASP.NET 运行库,就不可能测试 HTTP 模块。因此,必须采用稍微不同的实现策略,尽可能将更多的功能与实现 IHttpModule 接口的类分开。在前面的示例中,记录用户名的代码不需要 ASP.NET 运行库。此功能可以放在名为 UserLog 的类中,该类独立于 ASP.NET。实现 IHttpModule 接口的 UserLogAdapter 类可以使用 UserLog 类。这样,其他类就可以使用 UserLog 类,而且,您也可以在没有 ASP.NET 环境的情况下对它进行测试。
Intercepting Filter 模式的实现具有下列优缺点:
优点
1、使用事件驱动的筛选器。ASP.NET 运行库提供了许多事件,这使程序员能够钩挂到适当的位置来添加功能。这是一个优点,因为程序员可以根据事件来假设请求的当前状态。例如,如果事件是 AuthenticateRequest,您可以假设在调用您的筛选器之前请求已通过了身份验证。
2、实现灵活的配置。通过编辑 web.config 文件可添加或删除模块。不必更改源代码,不必重新启动 ASP.NET 运行库。
3、降低了对顺序的依赖性。
缺点:
1、如果不测试整个 ASP.NET 运行库,对实现了 IHttpModule 接口的类进行测试将很困难或不可能。
2、筛选器不应该与顺序相关。因为 ASP.NET 实现会使用事件,它通过使用事件来指示某个处理已经发生来缓解该问题。