Delphi编写ISAPI过滤器详解之汉字内码

目前由于汉字内码的不统一,互联网上的中文站点为了实现对于不同用户的支持,一般采取建立两套主页,分别用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=$80then

//是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码了。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注