Sunday, October 10, 2004

 

Filtering HTTP Requests

1.
Filtering HTTP Requests with .NET
by Ben Lowery
from http://www.ondotnet.com/pub/a/dotnet/2003/10/20/httpfilter.html

Introduction
ASP.NET has a number of extensibility points that developers can use. One such point is response filtering, accessible via the Filter property of the HttpResponse class. Filters intercept content destined for the client and have an opportunity to modify that content prior to sending it out. Filters are unique in that they can access the raw byte stream that is going to be sent to the client. This article will show you how to create and install a set of simple filters and will expose some of the gotchas that accompany the technology.


I highly recommend you download the code examples for this article and play around with them.

http://www.blowery.org/code/HttpFilteringExamples.zip

Building Blocks

The examples and code for this article work with either the 1.0 or 1.1 versions of the .NET framework. This article assumes you are familiar with creating virtual directories and using custom HttpModules and HttpHandlers inside of an ASP.NET app. If you're not, it would be a good idea to read up on them before proceeding or installing the examples. The examples must be rooted in a web application to work. The easiest way I know of to do so is the use the "Sharing and Security" option available via right-clicking on the folder where the examples live. Inside of the "Web Sharing" tab, simply share the folder using some name. For this article, I'll name the virtual directory ourfirstfilter.

http://msdn.microsoft.com/msdnmag/issues/02/05/asp/default.aspx
http://docs.aspng.com/quickstart/aspplus/doc/httphandlers.aspx

Additionally, the ASP.NET worker process will require write permissions to the example directory. The samples perform file-based logging to illustrate how filters and the processing pipeline interact; therefore, the ASP.NET process has to be able to write out the files. Giving write access to the ASP.NET worker process is not a fantastic idea; I would highly recommend that you lock down the virtual directory to authenticated users.

Lastly, a word on scope. ASP.NET response filters will only see content flowing through the ASP.NET processing pipeline. This may seem obvious, but it catches a lot of people off guard. By default, static HTML files, static images, PHP, JSP, or any other technology that does not use the ASP.NET processing model will not be filtered. If you'd like to filter static HTML content, you could map the .html and .htm extensions onto the ASP.NET ISAPI extension and configure ASP.NET appropriately, but this comes at some performance cost. Whether the cost is too great is something only your situation can decide.

What is Filtering?
So what is filtering and why would you want to use it? Filtering allows you to intercept the content being written back to the client and do interesting things with it. Our goal with any web application is to respond to an HTTP request. This request could be formulated using a number of different methods: a user with a browser, an automated script using a library, a user at a command prompt using curl, or any number of other techniques. ASP.NET exposes the request and response via two objects, HttpRequest and HttpResponse. You can find this pair in the System.Web namespace of the System.Web.dll assembly. The HttpResponse object exposes a Filter property. That one property is what the rest of this article will discuss.

First, why would you want to filter? Why not just bake your logic into the application some other way? Truthfully, filtering is hard, and I've only seen a few really useful things done with it. One is my own HttpCompressionModule. This is an HttpModule that adds standard compression to any ASP.NET app (and is fraught with its own perils).

Another case that some people seem to pursue involves string replacement. With a filter, you could parse the output and replace certain tokens with something else. For example, you could write a censoring filter that finds and removes naughty words from a user-supplied comment. This approach can work, but it too is fraught with peril.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemwebhttprequestclasstopic.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemWebHttpResponseClassTopic.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemWebHttpResponseClassFilterTopic.asp

A Basic Filter
A filter has to derive from System.IO.Stream and follow a policy of taking ownership of the current filter, storing it, and writing filtered content into the held original filter. Filters are write-only streams, so your override of Stream should support writing, at the very least. The filtering model uses chaining to accomplish its work; you can easily install multiple filters and have each perform some work as long as you follow the model. Here's a short example of what I mean, along with some code to install the filter multiple times:

// A simple filter that does the right thing
using System.IO;

public class AnyFilter : Stream {

Stream originalStream;

public AnyFilter(Stream originalFilter) {
originalStream = originalFilter;
}
public override void Write(...) {
//perform filtering
originalStream.Write(...filtered content...);
}
}


// Installing the filter more than once
using System.Web.UI;


public class MyPage : Page {


public override OnInit(EventArgs args) {

// install filter once
AnyFilter f = new AnyFilter(Response.Filter);
Response.Filter = f;

// install filter twice!
AnyFilter g = new AnyFilter(Response.Filter);
Response.Filter = g;
base.OnInit(args);
}

}

As you can see in the code above, you have to access and store the Response.Filter property. The following code will also work, but it not recommended:

public override OnInit(EventArgs args) {
AnyFilter f = new AnyFilter(Response.OutputStream);
Response.Filter = f;
}
This code does not respect any other filters that may already be in place. Therefore, it is preferable to use the Filter property over the OutputStream.

I have provided an HttpFilter class in the example code that performs the necessary overrides and can act as a good starting point. Given HttpFilter, a basic do-nothing filter looks like this:


using System;
using System.IO;
using OurFirstFilter;


public class PassThroughFilter : HttpFilter {

public PassThroughFilter(Stream baseStream) : base(baseStream) {}

public override void Write(byte[] buffer, int offset, int count) {

if(Closed) throw new ObjectDisposedException("PassThroughFilter");
BaseStream.Write(buffer, offset, count);

}
}

Given a good base class, implementing a filter can be pretty easy.

Installing a Filter
The ASP.NET folks have given us a variety of ways to plug the filter into the HTTP processing pipeline. Anywhere we can access ASP.NET's Request object, we can insert a filter. Keep in mind that the filter must be installed before any content is written back to the client. This means that we must install the filter before flushing any content to the client. That said, the three primary places we're going to install a filter are: 1) from within a custom HttpHandler, 2) from within a custom HttpModule, and 3) from within an ASP.NET Page object. I'll cover these in reverse order.

Installing a filter from within a Page object is great if you want to scope the filter to one page on your site. Remember, you have to install the filter before flushing any content back to the client. The best place to do this with a Page is inside of OnInit(). The code looks likes this:

using System.Web.UI;

public class MyPage : Page {

protected override OnInit(EventArgs e) {
Response.Filter = new MyFilter(Response.Filter);
base.OnInit(e);
}

}
Great! However, what if we want to apply a filter to an entire site?

HttpModules fit this niche nicely. An HttpModule can sink appropriate events from the HttpApplication and install the filter as needed. A common place to install the filter is in a handler for the BeginRequest event. This ensures that the filter is installed before any content is written to the client. By using an HttpModule, you can easily filter content without having change your content-generating pages. Additionally, any other requests that flow through the ASP.NET HTTP pipeline will also use the filter. This includes web services and any custom HttpHandlers you may be using. Check out the FilteringModule in the downloadable example code for an example of HttpModule-level filtering. This is probably the most common way to install a filter.

HttpHandlers have the easiest time installing a filter. You can set it directly into the Response exposed by the HttpContext passed into the ProcessRequest method. This gives you the ability to scope the filter to one handler, instead of an entire site. This is very similar to Page-based installation, but you don't have to worry about the Page processing model. Realistically, you're probably not going to do this; you're already controlling the entire process of responding to an HTTP request and there are usually simpler methods to accomplish the same result. Of course, most of the examples in the provided code are written against custom HttpModules.

Considerations
So far, filtering is sounding pretty nice. You can easily install a filter, it doesn't require any setup inside of IIS, and it can fit nicely into an xcopy-based deployment. In many ways, filtering inside of ASP.NET is a great alternative to writing an ISAPI filter. But with any technology, there are drawbacks. With filters, the drawbacks center on state management, inefficiency, and a number of strange interactions with other parts of the ASP.NET framework.

Simply put, writing a good filter can be very hard. Take a look at the Catch22Filter in the provided sample. It's a pretty simple replacement filter. This filter looks for words in the output buffer and wraps them in some nice HTML that makes them appear as censored text in the final output. Hit censor.ashx and give it a shot. Notice that with the default word list, not only are the words in the body of the document censored, the words in the title, and possibly inside of tags, are also censored. To properly filter an HTML document, you have to keep track of where you are in the document and only apply the filter at the appropriate time. This may not seem too hard, but remember two things: 1) that you have to take into account different text encodings, and 2) looking at each character imposes a performance penalty, especially in terms of working set.

You may not realize it, but the byte[] being passed into the Write(byte[] buffer, int offset, int count) override has already been encoded into the target text encoding. That's right, it's already UTF-8 with the default ASP.NET install. If you're looking for any kind of token in the output stream, you have to encode the token using the same encoding and search for its byte sequence. You could convert the entire buffer back into a string, but that can create large amount of garbage and negatively impact performance and working set. Be sure to profile your memory usage if you take this route. Additionally, for situations like the Catch22 filter where you're trying to find given words, you have to fully understand the character comparison rules for your target language. In the face of internationalization, it can get rather messy.

Filtering sometimes feels more like a tacked-on feature than a first-class citizen in the processing pipeline. Filtering doesn't work if your code calls the End method on the HttpResponse, either directly or indirectly. When End is called, the HTTP pipeline bypasses the filter and sends whatever is currently in the real output buffer to the client. Unfortunately, Server.Transfer calls End as part of its processing. Therefore, if you make a call to Server.Transfer, your filter is going to be bypassed, and any work you expected it to perform will be skipped. To prove the point, hit the transfer.ashx HttpHandler in the provided samples. If you take a look at the log file, you'll see that a TracingFilter is constructed, but then nothing further happens. The writes by the transferred-to page are not passed into the filter, and the document escapes uncensored. A workaround is to use Server.Execute instead of Server.Transfer. The semantics are a bit different, as Server.Execute returns execution to the original page after the called page is executed, but it does work with filters.

The biggest problem with using an HttpModule is figuring out where to add yourself into the processing pipeline. There are a number of events you could sink, but which ones really make sense? For example, what if you want to only filter HTML content? To do this, you would probably check the ContentType header and then decide whether or not to install the filter. From looking at the docs, it would seem to make sense to sink the ReleaseRequestState event, check the ContentType, and then install the filter if we have a ContentType we like. The code looks something like this:

using System;
using System.Web;

namespace OurFirstFilter {

public class HtmlOnlyFilteringModule : IHttpModule {

public void Init(HttpApplication context) {
context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState);
}

public void Dispose() { }

private void context_ReleaseRequestState(object sender, EventArgs e) {
HttpResponse response = HttpContext.Current.Response;

if(response.ContentType == "text/html") {
response.Filter = new TimestampFilter(response.Filter);
}
}
}
}
This works wonderfully until you call the Flush method on the HttpResponse object from within your page. When you flush, content is written to the client before ReleaseRequestState is fired, so the filter is installed after some content has been written. To fix this, we need to also sink PreSendRequestHeaders(which should be called PreSendResponseHeaders) and remember who installed the filter. Here's the fixed code:

using System;
using System.Web;

namespace OurFirstFilter {

public class HtmlOnlyFilteringModule : IHttpModule {

const string CONTEXT_KEY = "HttpOnlyFilteringModuleInstalled";

public void Init(HttpApplication context) {
context.ReleaseRequestState += new EventHandler(HandleOpportunityToInstall);
// have to sink PreSendRequestHeaders too to handle calls to Flush
// comment this next line out and hit Flusher.ashx to see it fail
context.PreSendRequestHeaders += new EventHandler(HandleOpportunityToInstall);
}

public void Dispose() { }

private void HandleOpportunityToInstall(object sender, EventArgs e) {

if(!HttpContext.Current.Items.Contains(CONTEXT_KEY)) {

HttpResponse response = HttpContext.Current.Response;

if(response.ContentType == "text/html") {
response.Filter = new TimestampFilter(response.Filter);
}

HttpContext.Current.Items.Add(CONTEXT_KEY, new object());
}
}
}
}
Notice that we remember if the filter has been installed by setting an item in the HttpContext's Items collection. We can't reliably use a member variable, because one instance of the HttpModule is reused among all requests. We really need to scope the "installed" memento to the current request, and the HttpContext is a great way to do that.

Alternatives
If the limited abilities of ASP.NET's HttpResponse filter don't meet your needs, there are a few other options. If you have your heart set of filtering the output of the ASP.NET HTTP pipeline, your only real option is to write an ISAPI filter. The ISAPI filter can do anything you desire and it is completely decoupled from ASP.NET. An ISAPI filter would work for any content flowing through IIS, including normal HTML files, PHP, JSP, and any other technology that can sit inside IIS. The downside is that you have to write an ISAPI filter, no small task.

Another alternative is to redesign your application to include the functionality you're trying to provide from within the existing ASP.NET framework. Be sure you understand all of the extensibility points of the framework before you latch onto the response filter as a golden hammer. There are often better ways to include common functionality. Check out user controls, the RegEx classes, and custom HttpHandlers to make sure you can't accomplish your goal some other way. This is a bit of a cop out, as you're not really filtering, you're refactoring.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemiostreamclasstopic.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemWebHttpApplicationEventsTopic.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemwebhttpapplicationclasstopic.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemwebhttpapplicationclasstopic.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemWebHttpApplicationClassPreSendRequestHeadersTopic.asp

(按:原作者给出了相关Link,赞)

2.
by dudu
from http://www.cnblogs.com/dudu/archive/2004/10/16/53085.aspx

为了优化博客园的访问速度,准备采用HttpCompressionModule 6对传输数据进行压缩,下载了HttpCompressionModule 6 , 并按照示例程序中的web.config配置了博客园程序的web.config。示例程序可以运行正常, 可是访问博客园的程序http://localhost/blog 却出现错误:“索引和计数必须引用该字符串内的位置。参数名: count”,错误的代码在“System.String.Remove(Int32 startIndex, Int32 count) +0”, 检查了一下配置, 没发现问题。 要解决问题, 看来只有去看HttpCompressionModule的源代码。这时就感到开放源代码的好处, 有问题可以自己去修改源代码, 不然只能向软件的作者提交Bug, 并等待下一版本,显然这样效率是很低的。打开源代码, 找到出错的代码行,立即发现问题所在,原来的代码是这样的:

string realPath = app.Request.Path.Remove(0, app.Request.ApplicationPath.Length+1);
当我访问http://localhost/blog 时, app.Request.Path与app.Request.ApplicationPath的值都是blog,这时上面的语句肯定会产生异常。这样的写法只考虑了http://localhost/blog/ 的地址形式,显然是不全面的。我进行了这样的修改,解决了问题:

string realPath="";
string appPath=app.Request.ApplicationPath;
string path=app.Request.Path;
if(!appPath.StartsWith("/"))
{
appPath = "/" + app;
}
if(!appPath.EndsWith("/"))
{
appPath += "/";
}
if(path.StartsWith(appPath))
{
realPath = path.Remove(0,appPath.Length);
}
使用效果:
使用HttpCompressionModule自带的Fetch工具进行测试,测试结果如下:


测试结果说明:
第一行数据是未使用HttpCompressionModule的测试结果。
第二行数据是使用deflate压缩算法进行压缩后的测试结果。
第二列数据是Web服务器传递到浏览器的文件大小。很明显,压缩后传输数据大大减少,有效地节约了带宽。
TTFB—首字节平均响应时间(Gets the number of milliseconds that have passed before the first byte of the response was received.)
TTLB—末字节平均响应时间(Gets the number of milliseconds that passed before the last byte of the response was received. )
Transit—传输数据到浏览器的时间。
从测试结果可以看出, 采用HttpCompressionModule后访问速度有明显改善。

(按:http://www.blowery.org/code/HttpCompressionModule.html
真的是好东西。blowery的确大牛。


3.
在Asp.Net中采用HttpModules来重写URLs
by 简单生活
http://www.cnblogs.com/rrooyy/archive/2004/10/24/56041.html
http://www.cnblogs.com/rrooyy/archive/2004/10/26/56804.html

据说通过HttpModules可以将类似于:http://www.infotouch.cn/detail.aspx?id=120 的URL地址重写为:http://www.infotouch.cn/detail/120.aspx 。这样最直接的好处就是可以让搜索引擎搜索到页面,因为搜索引擎对?之后的参数不太理睬。

今天尝试了一下,发现一个需要注意的问题,就是HttpModules只能对特定扩展名的URL进行重写(注:只能对“映射”-“应用程序扩展”中指定交给Asp.Net处理的扩展名文件进行处理),从Google找了些英文资料,发现这种情况是由IIS处理请求的机理决定的。

IIS对于没有扩展名的URL处理请求的机理:

如果请求的路径(path)有扩展名,IIS首先查找是否已设定了对应的应用程序扩展,有则将控制权交给该应用程序:
所以对于 http://www.infotouch.cn/detail.aspx?id=120 这样的情况很容易处理,只要处理为 http://www.infotouch.cn/detail/120.aspx 即可。因为IIS根据*.aspx的扩展名将控制权交给了Asp.Net,进而转给了HttpModules。

如果请求的路径(path)没有扩展名,例如:http://www.infotouch.cn/detail 这样的路径。IIS首先检查该虚拟路径是否对应到一个本地目录,如果具有对应的本地目录,再查找该目录下是否具有缺省文件,如果找到,就重定向为该缺省文件的路径。否则,IIS报告一个Http404-文件未找到错误。

首先写一个处理URLs重写的类,并且这个类必须继承IHttpHandler接口,以博客园的程序为例:

public class UrlReWriteModule : System.Web.IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest +=new EventHandler(context_BeginRequest);
}

public void Dispose()
{
}
}

UrlReWriteModule类就是处理URLs重写的类,继承IHttpHandler接口,实现该接口的两个方法,Init和Dispose。在Init方法里注册自己定义的方法,如上例所示:

content.BeginRequest +=new EventHandler(content_BeginRequest);

BeginRequest是一个事件,在收到新的Http请求时触发,content_BeginRequest就是触发时处理的方法。另外说明一点,HttpModules能注册的方法还有很多,如:EndRequest、Error、Disposed、PreSendRequestContent等等。

在content_BeginRequest方法中具体处理URLs重写的细节,比如,将 http://www.cnblogs.com/rrooyy/archive/2004/10/24/56041.html 重写为 http://www.cnblogs.com/archive.aspx?user=rrooyy&id=56041 (注:我没有仔细看DuDu的程序,这里只是举例而已)。然后将重新生成的Url用HttpContext.RewritePath()方法重写即可,如下:

private void context_BeginRequest(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
// 获取旧的Url
string url = context.Request.Path.ToLower();
// 重新生成新的Url
string newUrl = ...; // 具体过程略
// 重写Url
context.RewritePath(newUrl);
}

提醒:newUrl的格式不是http://www.infotouch.com/user/archive.aspx,而是从当前应用程序根目录算起的绝对路径,如:user\archive.aspx,这一点请特别注意。

最后要web.config中注册重写URLs的类,格式如下:







采用标签可以注册一个类;可以移除某个类,如果某个子目录不希望继承父目录的某个Http Module注册,就需要使用这个标签;可以移除所有的Http Module注册。

4.
By 宝玉
From http://blog.joycode.com/dotey/archive/2004/11/30/40010.aspx

HttpModule的移除
HttpModule 是个好东西,很多程序都有用到,例如DotText、Asp.Net Forums等,但是有一个问题,当我们将使用了HttpModule 的程序设置为站点根目录(以将DotText设置为站点目录为例),然后再在根目录下建虚拟目录,运行虚拟目录里的asp.net程序会提示出错:

分析器错误信息: 找不到文件或程序集名称“Dottext.Web”,或找不到它的一个依赖项。

源错误:



行 145:
行 146:
行 147:
行 148:

行 149:


也就是说对于虚拟目录中的asp.net应用程序,会首先去加载其所在站点中的HttpModule,而一般虚拟目录中的bin目录是不会有HttpModule所在的DLL的,所以就会出如上的错误。
注意,其中的错误提示信息(找不到文件或程序集名称“Dottext.Web”,或找不到它的一个依赖项。)很有误导性,一开始让我以为是需要每个虚拟目录拷贝一个Dottext.Web.dll进去呢,但是将dll拷贝过去后,其HttpModule会对虚拟目录中的asp.net应用程序产生影响,因此这个问题曾让我郁闷了很久。

后来偶然查msdn中,发现web.config里面的HttpModule是有三种子节点的:





子标记 说明
将 HttpModule 类添加到应用程序。 请注意,如果以前已指定了相同的谓词/路径组合(例如在父目录的 Web.config 文件中),则对 的第二个调用将重写以前的设置。
从应用程序移除 HttpModule 类。
从应用程序移除所有 HttpModule 映射。


唉,其实看到add也应该想到remove的,在虚拟目录中的web.config中Remove一下不需要的HttpModule,发现果然没有问题了!如:




注意一般表用,因为用会把machine.config中定义的httpModule一起清除了从而导致asp.net程序不能正常运行。

有一点我很疑惑的是既然都是用remove移除这个HttpModule了,为什么还需要将该HttpModule对应的dll拷贝到虚拟目录的dll下。

httpHandlers的处理方法也类似。

Comments From bestcomy

出现问题的原因是在站点级别建立的属于dottext特有的httpmodule和httphandler并不在cnforums中用到,而该场景却将使得dottext的web.config所注册的httpmodule均应用到其下的所有虚拟目录中,根据machine.config=>站点web.config=>虚拟目录web.cofing的继承关系,这是正确的。正是因为这些配置信息应用到了cnforums虚拟目,而比如出错的httphandler

在访问cnforums时,Dottext.Common.dll就无法找到了,因为它不存在于全局缓存中,也不存在于cnforums下的bin目录中,所以产生了宝玉描述的错误。因此,解决的方案正如宝玉所提供的,在不需要应用站点级别的某些特定的httpmodule和httphandler的虚拟目录中将其移除。

Comments From Lostinet

asp.net在处理这个add的配置节的时候就立刻检查type是否存在。
因为是放在次要的config中的,
所以主要config文件里的要比次要文件的提早被处理,
那么如果在处理的时候,并没有这个type(没有把dll复制过去或者放GAC),那么ASP.NET就会把找不到类型的异常直接抛出。

所以,这个需要把相关的DLL都要部署一下的。而的作用只是取消加载相关的东西,但是却无法跳过类型检查。

(remove原理与clear相同)

httpHandler则有点不同,它是在需要创建指定的handler时才去检查类型是否能加载的。

5.
ASP.Net中自定义Http处理及应用

By 孙亚民
From
http://developer.ccidnet.com/pub/disp/Article?columnID=322&articleID=34991&pageNO=1
http://developer.ccidnet.com/pub/disp/Article?columnID=322&articleID=34994&pageNO=1

在开发基于Microsoft IIS的应用时,开发者除了可以编写ASP程序外,还可以使用Visual C++等开发工具,开发ISAPI应用,以获取更为强大的功能。可以编写两种ISAPI扩展:一种是ISAPI Server Extention,另一种是ISAPI Filter,但是,ISAPI扩展应用的编写通常对开发者有比较高的要求,开发和部署的难度比较大。

在开发ASP.Net应用时,我们仍然可以编写ISAPI应用,以扩充IIS的功能,但是,ASP.Net为我们提供了另外一种选择——使用HTTP Handler 和HTTP Module。这是通过使用IHttpHandler 和 IHttpModule接口来实现的。HTTP Handler提供了类似于ISAPI Server Extention的功能,而HttpModule实现了类似于ISAPI Filter的功能,并且,比ISAPI,在开发和部署上都要简单的多。

应用HttpHandler和HttpModule,使应用程序可以与IIS Web服务器的低级别请求和响应服务交互。本文首先介绍HttpHandler和HttpModule的概念和基本使用方法,并介绍了一个应用HttpModule实现权限系统的案例。


HTTP 处理管道的基本模型


要对HttpModule和IHttpHandler进行研究,必须先对ASP.Net的处理管道有一个了解。

在ASP.Net应用程序中,系统使用一组相关的类,通过一定的顺序来处理客户端的请求(Request),ASP.NET应用程序的处理模式可称之为HTTP处理管道。HttpModule和IhttpHandler就是这个处理管道上的两个处理环节。

HTTP处理管道中的类在System.Web名称空间中定义,主要有以下类型:

· HttpWorkerRequest 抽象类定义了ASP.Net页面处理请求的基本方法;

· HttpRuntime 提供了处理应用的一组服务;

· HttpContext 保存了处理一次请求的所有相关上下文信息;

· HttpApplicationFactory 提供相关目录的应用程序;

· HttpApplication 定义了所有ASP.Net应用程序的通用的方法、属性和事件。这个类也是在用户在global.asax文件中定义的应用的基类;

· Modules 处理请求前和响应后的事件;

· HandlerFactories 提供应用程序中的Handlers;

· Handlers 处理请求和响应。

HTTP处理管道的模型如下:






图1:HTTP 处理管道


在Windows平台上,HTTP Pipline需要IIS的支持。为了运行ASP.NET应用,IIS需要以下两个文件:ASPNET_ISAPI.DLL和ASPNET_WP.EXE

· ASPNET_ISAPI.DLL是一个ISAPI Extention他将发向IIS的请转交ASPNET_WP.EXE处理

· ASPNET_WP.EXE使用HttpRuntime对请求进行具体处理

处理的过程可以用图表示如下:






图2:IIS上的HTTP处理管道



HttpHandler的实现


HttpHandler实现了类似于ISAPI Extention的功能,他处理请求(Request)的信息和发送响应(Response)。HttpHandler功能的实现通过实现IHttpHandler接口来达到。实际上,我们在编写ASP.Net页面时,ASP.Net页面所继承的基类——System.Web.UI.Page——也实现了HttpHandler接口,也是一个HttpHandler,看一下它的定义就知道了(C#):

public class Page : TemplateControl, IhttpHandler



接口IHttpHandler的定义如下:

interface IHttpHandler
{
void ProcessRequest(HttpContext ctx);
bool IsReuseable { get; }
}



接口中ProcessRequest是添加自己的代码,进行相应处理的地方。IsReuseable属性指明该HttpHandler的实现类是否需要缓存。

下面的示例展示了HttpHandler的基本使用:

1、建立一个名为MyNameSpace的工程,添加一个类,名称为MyHandler,代码如下:

例1:
namespace MyNameSpace
{
public class MyHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
HttpResponse Response
Response.Write("This is my handler");}
public bool IsReusable
{
get { return true; }
}
}
}



2、将上面的代码编译,生成MyNameSpace.Dll文件;

3、建立一个新的WebApplication项目,或打开一个WebApplication项目,将文件MyNameSpace.Dll添加到项目的引用中,或复制到项目的bin目录下;

4、修改Web.Config,添加如下内容:

configuration>
system.web>
httpHndlers>
add verb="*" path="*.aspx"
type=" MyNameSpace.MyHandr, MyNameSpace" />
/httpHndlers>
/system.web>
/configuration>

配置文件中的选项说明:

· verb可以是"GET"或"POST",表示对GET或POST的请求进行处理。"*"表示对所有请求进行处理。

· Path指明对相应的文件进行处理,"*.aspx"表示对发给所有ASPX页面的请求进行处理。可以指明路径,如"/test/*.aspx",表明只对test目录下的ASPX文件进行处理。

· Type属性中,逗号前的字符串指明HttpHandler的实现类的类名,后面的字符串指明Dll文件的名称。

现在,请求项目中的任何ASPX页面,页面上显示的始终只有如下一行字:

This is my handler

因为,我们自定义的Handler截获了所有发向ASPX页面的请求,并且用自己的的方法来处理这些请求了。

为了使我们的ASPX页面能够顺利运行,我们需要修改Web.Config文件:

configuration>
system.web>
httpHndlers>
add verb="*" path="*.foo"
type=" MyNameSpace.MyHandr,hander" />
/httpHndlers>
/system.web>
/configuration>

为了让对后缀名为.foo的文件的请求能够被我们的Handler截获运行,我们还需要一些额外的工作。打开IIS的管理控制台,又键单击站点,选择"属性",跳出站点的属性对话框。选择主目录选项。如图3:

图3:Web站点属性对话框


选择配置,弹出应用程序配置对话框,将".foo"添加到应用程序映射中,如图4:

图4:添加应用程序映射


好了,我们现在可以在项目中添加一个.foo文件,当向该文件发送请求时,浏览器显示:

This is my handler

而对其他ASPX文件的访问不受影响。

实现Handler Factory

实现HttpHandler功能的另外一个选择是实现一个Handler Factory,这是通过实现IHttpHandlerFactory接口来实现的。

IHttpHandlerFactory接口的定义如下:

interface IHttpHandlerFactory
{
IHttpHandler GetHandler(HttpContext ctx,
string requestType,
string url,
string pathTranslated);
void ReleaseHandler(IHttpHandler handler);
}

GetHandler方法在请求开始的时候被调用,而ReleaseHandler在请求结束,所有的Handler都不再需要的时候被调用。

使用HttpHandlerFactory的过程一般如下:

首先定义实际处理HttpHandler的类,这个类会在HandlerFactory中被调用以进行实际的处理:

public class BasicHandler : IHttpHandler { ... }

然后,定义自己的HandlerFactory:

public class BasicHandlerFactory : IHttpHandlerFactory
{
public IHttpHandler GetHandler(HttpContext ctx,
string requestType,
string url,
string pathTranslated)
{
return new BasicHandler();
}
public void ReleaseHandler(IHttpHandler handler) {}
}

最后,在Web.Config文件中注册这个Factory:

configuration>
system.web>
httpHandlers>
add verb="POST" path="*.foo"
type="MyNamespace.BasicHandlerFactory, MyAssembly" />
/httpHandlers>
/system.web>
/configuration>

异步Handler

通过实现IHttpAsyncHandler可以实现对HTTP请求的异步处理。IHttpAsyncHandler接口继承IHttpHandler,也需要实现ProcessRequest 方法和 IsReusable 属性,同时,需要实现 BeginProcessRequest 和 EndProcessRequest 方法。BeginProcessRequest 启动异步调用以处理单个的 HTTP 请求,而 EndProcessRequest 则在该进程结束时执行清理代码。

IHttpAsyncHandler的实现和注册同IHttpHandler类似,读者可以参考MSDN的相关文档。

现在,大家是否对HTTP Handler的概念和应用有了一定的了解?在下一篇文章中,我们将主要介绍HTTP Module的的应用,并给出使用HttpModule实现权限系统的实例。

HttpHandler实现了类似于ISAPI Extention的功能,他处理请求(Request)的信息和发送响应(Response)。HttpHandler功能的实现通过实现IHttpHandler接口来达到。而HttpModule实现了类似于ISAPI Filter的功能。


HttpModule的实现

HttpModules实现了类似于ISAPI Filter的功能,在开发上,通常需要经过以下步骤:

1.编写一个类,实现IhttpModule接口

2.实现Init 方法,并且注册需要的方法

3.实现注册的方法

4.实现Dispose方法,如果需要手工为类做一些清除工作,可以添加Dispose方法的实现,但这不是必需的,通常可以不为Dispose方法添加任何代码。

5.在Web.config文件中,注册您编写的类

下面是一个HttpModules的示例,在这个示例中,只是简单的注册了HttpApplication 的BeginRequest 和 EndRequest事件,并且通过这些事件的实现方法,将相关的信息打印出来。

例1:
using System;
using System.Web;
namespace MyModule
{
public class MyModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += (new
EventHandler(this.Application_BeginRequest));
application.EndRequest += (new
EventHandler(this.Application_EndRequest));
}
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpApplication Application = (HttpApplication)source;
HttpResponse Response=Application.Context.Response;
Response.Write("

Beginning of Request


");
}
private void Application_EndRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpResponse Response=Application.Context.Response;
Response.Write("

End of Request


");
}
public void Dispose()
{
}
}
}

程序的开始引用了如下名称空间:

using System;
using System.Web;


因为HttpApplication、HttpContext、HttpResponse等类在System.Web中定义,因此,System.Web名称空间是必须引用的。

MyModule类实现了IhttpModule接口。在Init方法中,指明了实现BeginRequest 和EndRequest 事件的方法。在这两个方法中,只是简单的分别打印了一些信息。

下面,在Web.config文件中注册这个类,就可以使用这个HttpModule了,注册的方法如下:

configuration>
system.web>
httpModules>
add name=" MyModule " type=" MyModule, MyModule" />
/httpModules>
/system.web>
/configuration>

现在来看一下效果。编写一个Aspx页面test.aspx,内容如下:

%
Response.Write("

This is the Page


");
%>

运行以后的界面如图所示:

深入研究HttpModule


HttpModule通过对HttpApplication对象的一系列事件的处理来对HTTP处理管道施加影响,这些事件在HttpModule的Init方法中进行注册,包括:

BeginRequest
AuthenticateRequest
AuthorizeRequest
ResolveRequestCache
AcquireRequestState
PreRequestHandlerExecute
PostRequestHandlerExecute
ReleaseRequestState
UpdateRequestCache
EndRequest

其中部分事件同Global.asax中的事件相对应,对应关系如下:

HttpModule Global.asax
BeginRequest Application_BeginRequest
AuthenticateRequest Application_AuthenticateRequest
EndRequest Application_EndRequest

在例1中,处理了BeginRequest和EndRequest事件,其他事件的处理方式基本上类似。

同HttpHandler对应来看,这些事件,有些在HttpHandler之前发生,有些在HttpHandler处理完后发生。了解事件发生的顺序非常重要,因为,服务器端的对象在不同的时间段有着不同的表现。例子之一是Session的使用。不是所有的事件中都能对Session进行处理,而只能在有限的几个事件中进行处理。详细的过程可以参考下面的HTTP Request处理生命周期图。

使用HttpModule实现权限系统

我们在开发应用系统的时候,应用系统的权限控制是非常重要的一个部分。在ASP中,要实现权限的控制是比较麻烦的事情,因为我们必须在每个需要控制权限的ASP页面中添加权限控制代码,从而控制客户对页面的访问。这样带来的问题,除了编写大量重复代码外,由于权限控制部分同业务处理部分的模块紧密耦合在一起,对权限控制模块的修改,往往又会带来大量的修改工作,甚至造成大量的Bug。

所以,我们现在需要将权限控制和业务处理模块进行解耦,使得两个部分可以独立开发和修改,而不会互相影响,或者,将影响减到最低。在Jsp程序中,这个目的可以通过引入一个前端控制器来实现权限过滤(关于前端控制器模式,可以参见《J2EE核心模式一书》)。在ASP.Net中,我们可以利用HttpModule实现同样的效果。下面来看一下实现的过程。

首先,我们会构建一个权限处理系统,可以检测某个用户对某个模块功能是否有访问权限(具体的结构,我想,读者都应该接触过这个部分的编程,所以不再赘述),其中,暴露给客户端调用的权限校验类的定义如下:

public class RightChecker
{
public static bool HasRight(User user,Module module)
{
//进行权限校验,
}
}

然后,我们利用HttpModule编写一个过滤器:

using System;
using System.Web;
namespace MyModule
{
public class MyModule : IHttpModule
{
public void Init(HttpApplication application)
{
application. AcquireRequestState += (new
EventHandler(this.Application_AcquireRequestState));
}
private void Application_AcquireRequestState (Object source,
EventArgs e)
{
HttpApplication Application = (HttpApplication)source;
User user=Application.Context.Sesseion["User"] //获取User
string url=Application.Context.Request.Path;
//获取客户访问的页面
Module module= //根据url得到所在的模块
If(!RightChecker.HasRight(user,module))
Application.Context.Server.Transfer("ErrorPage.aspx");
//如果没有权限,引导到错误处理的页面
}
public void Dispose()
{
}
}
}

将这个类按照前面介绍的方法,在Web.Config中注册后,我们的应用系统就具备权限管理的功能了。怎么样,比原来的方式好很多吧?



<< Home

This page is powered by Blogger. Isn't yours?