原文地址:http://siyebocai.blog.163.com/blog/static/103316426200810297512408/
目前由于汉字内码的不统一,互联网上的中文站点为了实现对于不同用户的支持,一般采取建立两套主页,分别用GB和BIG码来编写。这样做显然要增加站点的维护工作,更新主页时要同时更新两部分。而且如果主页内容是实时更新的,采用手工维护两套主页的方法显然不行了。本文介绍了用ISAPI过滤器来动态产生另外一套内码主页的方法,这样就可以只制作一套主页就同时支持GB码和 BIG5码。
基本的思路,编写一个ISAPI过滤器,对于所有最终返回给用户的HTML文本,实行内码转换。这样用户看到的将是他期望的编码方式。ISAPI过滤器可以作为WEBServer横向功能扩展。当某个预先定义好
的服务器端的事件发生时,IIS就调用用户定义好的过程,此时就可以通过修改IIS传来的数据来改变IIS的行为。IIS预定义的事件如下:
SF_NOTIFY_READ_RAW_DATA 当IIS要从用户读入数据时发生。过滤器可以在IIS处理他们之前检查甚至修改用户输入的原始数据。
SF_NOTIFY_PREPROC_HEADERS IIS预处理HTTP请求包头后发生。过滤器可以检查修改增加包头。
SF_NOTIFY_AUTHENTICATION IIS试图验证用户身份时发生。过滤器可以实现自己的验证方案。
SF_NOTIFY_URL_MAP IIS试图将URL解释为物理文件时。过滤器可以将请求重定向到其他的文件。
SF_NOTIFY_ACCESS_DENIED 当身份验证失败时发生。 SF_NOTIFY_SEND_RAW_DATA 当其他程序处理完,IIS准备将数据发回给用户时发生。我们的过滤器就通过此事件,转换内码。
SF_NOTIFY_LOG 当IIS写记录到LOG文件时。过滤器可以搜集更多的信息写入记录文件中。
SF_NOTIFY_END_OF_REQUEST 当一个HTTP请求结束时发生。过滤器可以实现基于请求的处理。由于这是在IIS3.0中新增的,Delphi中的ISAPI2.pass单元中没有相应的定义可以手工加入SF_NOTIFY_END_OF_REQUEST=$80
SF_NOTIFY_END_OF_NET_SESSION 连接结束时。注意如果浏览器支持"keep-alive",一次连接可能包含几个HTTP请求。过滤器可以用他来释放一些用户的资源。
我们要实现动态的内码转换,只要过滤器处理SF_NOTIFY_SEND_RAW_DATA事件,将IIS处理好的数据转换成需要的内码就可以实现内码的动态转换。具体程序有两个问题需要注意:
1.过滤器只能处理返回是HTML格式的,其他图片等二进制请求无须也不允许转换。 2.对于返回的HTML,只处理实际数据,其他HTTP协议的包不应该处理。
编程
每个ISAPI过滤器DLL必须输出两个供IIS使用的函数:
GetFilterVersion()和HttpFilterProc()。下面分别讲述。
GetFilterVersion()
用于初始化和处理事件的登记。例程没有初始工作要做,只是简单的登记了要处理的两个事件和其他一些标志。
function GetFilterVersion(var pVer:THTTP_FILTER_VERSION):BOOL;stdcall; begin //过滤器要处理的事件和其他一些标志 pVer.dwFlags:=(SF_NOTIFY_NONSECURE_PORT //过滤器只在一般端口上使用
or SF_NOTIFY_SEND_RAW_DATA //处理发送数据事件
or $80//SF_NOTIFY_END_OF_REQUEST //处理请求结束事件
or SF_NOTIFY_ORDER_DEFAULT //过滤器使用缺省优先级
);
//过滤器使用的版本HTTP_FILTER_REVISION
返回当前版本
pVer.dwFilterVersion:=HTTP_FILTER_REVISION;
//过滤器的描述
pVer.lpszFilterDesc[0]:=’A’;pVer.lpszFilterDesc[1]:=#0;
result:=true;//初始化成功
end;
HttpFilterProc()
由IIS回调,是过滤器的实际处理部分。 其中参数Notificationtype是该调用的事件类型,如果过滤器处理多个事件,可以检查该值来区分事件。 PvNotification是一个根据事件类型可变结构的参数。对于SF_NOTIFY_SEND_RAW_DATA,他的结构如下:
THTTP_FILTER_RAW_DATA=record
pvInData:Pointer;//指向数据区
cbInData:DWORD;//数据大小
cbInBuffer:DWORD;//缓冲的大小
dwReserved:DWORD;//保留
end;
其中的pvInData就是要发送的数据指针。其他的结构请参看有关资料这里不再详述。
第一个参数var pfc:THTTP_FILTER_CONTEXT是过滤器的环境指针,其中的pFilterContext是一个用户使用的指针,用来保存和一个 HTTP连接相关的信息,这样过滤器可以区分出正在处理的是否是以前曾处理过的连接。因为一个请求将会产生多个 SF_NOTIFY_SEND_RAW_DATA事件,过滤器必须能够区分他们。
程序的流程是:当连接建立后,pFilterContext被IIS初始化为NIL(0),第一次SF_NOTIFY_SEND_RAW_DATA调用时,过滤器要检查返回的MIME,如果不是HTML则将pFilterContext置为pointer(2)(将指针当作变量用,因为我们只要一个标志),随后的发送事件调用将直接返回。请求结束后,发生SF_NOTIFY_END_OF_REQUEST事件,过滤程序将pFilterContext 复位为nil。
如果是HTML,则将pFilterContext置为pointer(1),随后的调用就将对数据进行内码的转换,然后将 pFilterContext置为pointer(3)。如果还有后续的调用,则再将pFilterContext置为pointer(1),直到全部数据发送完成。
function HttpFilterProc(var pfc:THTTP_FILTER_CONTEXT;Notificationtype:DWORD;pvNotification:Pointer):DWORD;stdcall;
var p:PHTTP_FILTER_RAW_DATA; i:integer; pc:pchar; begin if Notificationtype=$80 then //是SF_NOTIFY_END_OF_REQUEST将pFilterContext复位 begin pfc.pFilterContext:=nil; end else begin p:=PHTTP_FILTER_RAW_DATA(pvNotification); pc:=p^.pvInData; case integer(pfc.pFilterContext) of 0://第一次调用,要检查MIME begin pfc.pFilterContext:=pointer(2); i:=0; while i<p^.cbInBuffer-4-1do begin if (pc^[i]=’/’)and(pc^[i+1]=’h’)and(pc^[i+2]=’t’)and(pc^[i+3]=’m’)
then begin
pfc.pFilterContext:=pointer(1);
//是HTML
break;
end;
inc(i);
end;//endofwhile
end;
1://HTML数据
begin
pfc.pFilterContext:=pointer(3);
//将pc转换内码
gb2big(pc,p^.cbInBuffer);
end;
3://http1.1100contimue
begin
pfc.pFilterContext:=pointer(1);
end;
end;//endofcase
end;
//总是返回成功,并且如果有其他过滤器的话,还将继续调用
result:=SF_STATUS_REQ_NEXT_NOTIFICATION;
end;
下面是完整的程序文件(gb2bigfiler.dpr),其中的u_gb2big_tab单元完成GB码到BIG5,码的转换,这里不再细述,有兴趣的读者可以到后文提到的笔者的站点去下载源码。
library gb2bigfiler;
uses
SysUtils,math,Classes,windows,
isapi2,//delphi中ISAPI过滤器单元
u_gb2big_tab;//包含将GB码转换成BIG5码的过程gb2big
//下面两个函数的定义见上文
function HttpFilterProc(...);begin...end;
function GetFilterVersion(...);begin...end;
exports
HttpFilterProc index 1,GetFilterVersion index 2;
Begin end.
读者一定注意到了,这个过滤器将所有返回的HTML都转换成了BIG5码,那么GB码又如何看到呢?当然可以在过滤器中检查一些环境变量来决定用户所要求的是GB还是BIG5,可是这样做除了比较麻烦外,还存在效率问题,因为每个请求都要被过滤器处理。
笔者采用的方法是利用IIS4.0中可以设置多个站点的功能,设置两个站点。一个不含过滤器,所以GB内容高效直接的返回给GB用户;而另一个站点使用另外一个端口比如81,所有虚拟目录和前一个站点一样,将过滤器加载在该站点上,这样所有向81端口的请求,都将被过滤器转换成BIG5码返回给用户。
下面简述一下具体配置过程。首先在delphi中选择新建一个DLL,输入程序源码,编译后生成gb2bigfiler.DLL文件。在 IIS4.0 的管理控制台中,选"新建站点",主目录和缺省站点一样,端口设为81,在ISAPI过滤器中选择"添加",将gb2bigfiler.DLL加入。
设置好后,可以浏览81端口(例如:http://www.yoursite.com:81/your.html),这时原来GB码的内容就变成了BIG5码了。
有兴趣的读者可以访问http://202.96.122.45/qq,起始页可以选择内码,随后浏览的内容包括静态的HTML文本,放在数据库中的《红楼梦》、《唐诗》、完全动态更新的BBS,都可以做到用两种内码显示。
|
请发表评论