找回密码
 注册
搜索
查看: 4511|回复: 0

IIS的ISAPI接口简介

[复制链接]
发表于 2003-10-13 13:22:17 | 显示全部楼层 |阅读模式
作者:netguy (mailto:netguy@nsfocus.com)
' A, F7 s7 X: @$ y6 j- y4 s
$ N* E& v* }' x  A% V  [& y2 EISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。5 T) ?. W0 B- R' x& Z

: I2 P* q0 l  _! p1 l1 N一、ISAPI接口和CGI接口的不同。1 y* M! w$ ^' n

5 a2 C/ \, Z" w8 c; B1 b) \2 JISAPI程序和CGI程序完成类似的功能,但是实现方法不同。
% D  ?& G. s% V& T; b* {6 R, `3 d4 w! W8 ?( {+ z* D, m7 O1 k
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。
! c0 N6 T# n& t( A1 _% F) h  ~# F3 N$ }- r$ ]5 `* k* M$ V# W
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。1 U. Y+ y/ F- h! Z! {9 J5 y

: Z3 ]: |( d0 U$ A& aISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。9 }: O: q' E& {; C6 R+ a: V

$ L  i2 S; s$ [  I6 x" t+ y3 X1 N
. A: l8 {& M' Q* R: T/ i% W二、ISA
; `: c- t! ?- g" S
+ H. ~& }6 {. U$ M0 q7 `ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):
5 @1 N$ N/ P2 shttp://www.abc.com/Scripts/function.dll?
6 v( e& c( o- f+ ?8 |% w9 s- B9 \; z1 j6 m' u! `' ^$ |
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。3 d7 Y! M: Z( E2 z0 @& G- D1 H* L
: S* ?! b9 H$ i% e9 u& T2 Q5 Z
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
5 k8 A# \7 G( t4 ?/ O1 @# x9 o! A8 a$ E6 m. d& o  m
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
  }. I- q& K2 g8 f1 F: _4 t% jtypedef struct _HSE_VERSION_INFO; t& ^) c4 ~) O  m: p7 q
{
! X. l) |2 h- ^6 c6 {% X/ d. wDWORD dwExtensionVersion; //版本号
9 M5 ]# m* }/ `& lCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
  L/ g5 j+ C5 w- f} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;2 |. d) [7 _" I+ S! {8 I+ j

, m3 f! L% o, k5 y8 L, {2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:/ K0 T9 r2 i0 V- H/ b
5 ?3 a  w, `8 Z3 |
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );8 }6 b, w! a% d. A

1 W& n- m- \4 p, N' bECB的结构定义如下(IN表示入口参数,OUT表示出口参数):5 F5 G& \4 B) u  G

& m& A. ~( s( C  G* U: qtypedef struct _EXTENSION_CONTROL_BLOCK $ Z- o1 H* [2 I( l+ w
{
8 O) Z* U- C2 s9 ?2 QDWORD cbSize; //IN,本结构的大小,只读
. f2 S& t  c. s; b5 aDWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
4 Q. b- j- C8 j: uHCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值; q: y2 Y+ c9 U% V1 X
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态
4 M2 i2 D4 l2 z+ w, A5 l) tCHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
. ~/ X1 C, m# u' M' OLPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD# X2 S8 q8 Q0 g0 c$ j7 X
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING* e$ y% X1 n" B0 {& x! q
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
1 ]8 ]' ]4 m! G. `LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED& F" H  Q0 v& G* r+ v
DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH
& j* U6 P, `: J& vDWORD cbAvailable; //IN,缓冲区中的可用字节数
1 x# |8 x9 r4 ^# SLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据$ ^$ m! P% B5 l& f2 H* v: x
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE
; e7 e* x! E+ `1 e1 ^
  X" s* m: Q3 g//回调函数,用于返回服务器的连接信息或特定的服务器详细情况' A. U' e3 t/ I3 c7 Y
BOOL ( WINAPI * GetServerVariable ) ) u3 B$ Y0 M4 @: u7 q
( HCONN hConn,
  y4 V/ C, p' u1 @% m" kLPSTR lpszVariableName,8 B7 R1 |7 h3 Q7 W
LPVOID lpvBuffer,
3 t+ O' Y) J6 O! t% }LPDWORD lpdwSize );% @2 u/ E2 H' T0 Q* y# E7 `9 e
' f# [( F) D$ R5 Q  H2 e$ U
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据; c9 b1 I3 C3 o7 k' E
( HCONN ConnID,
& k1 N, w# [2 X% `1 |, ]" gLPVOID Buffer,
. J9 B1 I0 I- D! e+ e" ~LPDWORD lpdwBytes,  k  g- i. n4 J) ^3 P
DWORD dwReserved );
, p3 i5 W+ Y1 z) H5 e% U' x3 k0 x4 z- E- P+ ]
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据( n3 }/ |4 J; x7 d  ~, D$ H
( HCONN ConnID,
9 ~  v7 @/ j3 n" wLPVOID lpvBuffer,7 d" [7 ^& `0 v4 N) P% E
LPDWORD lpdwSize );/ E. J# L. K  X; y) h

' X  B  S: ~5 EBOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能
) ^2 G4 f( A' x. U* S- J( HCONN hConn,
) @3 P/ c6 ~+ {" |DWORD dwHSERRequest,5 B$ W6 W6 [7 R, z
LPVOID lpvBuffer,% J+ A" b5 D: c1 |' }( Q- v, z
LPDWORD lpdwSize,: B3 G5 I1 M1 t  z
LPDWORD lpdwDataType );
9 X  s. a! Y3 V. U" M- Q
4 r* y0 u- Y& T2 r} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
) r  U; H+ ], v5 {) S" J2 I7 m: O" t: d! A  r8 o! B
在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。
0 D* x6 R/ p4 p! k( q  a0 b3 F5 T, S. u: w. h; d/ T! q& `# l
三、ISAPI Filter) c0 N6 u7 |/ p) I' U- K
6 Y, V: }0 P+ K' Z  d1 W) B8 S
ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。1 u( m! Q9 a. |* N5 u  s

, Q- T  @  D% Y2 `& z  UISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
& i4 W$ l9 z/ W" n& aISAPI Filter都必须引出这两个函数以供服务器调用。
: u7 B; a; J9 _5 _6 `# C
) `* A& D& ]+ z( Y% f* I* q* q1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
* }+ g, J4 Z$ `) J9 w* w" nFilter的文件名并加载它们。9 S0 d, F  l! f! D6 [" g
' u: p. _8 B. E9 a; v
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL- ^* E) }5 @0 d2 o
5 O1 l9 a0 a* b% X  P4 F  [
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:3 P# k6 G2 p; O

/ ^8 W5 p( p* z6 P7 h4 X* OBOOL WINAPI GetFilterVersion(
; z' T1 H7 [1 n2 e3 tDWORD dwServerFilterVersion; //IN,服务器使用的版本规范
% g, d1 n8 K# I; U' U) `+ pDWORD dwFilterVersion; //OUT,过滤器使用的版本规范1 _6 E7 r1 e' z0 p4 ^* X/ {
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串7 \/ k( X# s* Z4 o( W
DWORD dwFlags //OUT,事件和优先级标志
' k9 A3 e. m9 i; [& [# `8 ?+ @);$ l; C; e3 C* a3 ]6 A) D+ P

( l, C' t; r0 [2 a事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
2 R9 ]& n4 y% K( o; L6 ^* N
/ w: A. C* d0 ]/ `5 ]6 n1 a3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。& E! m% [4 p, G* ]2 l' s6 O$ \, Y
/ L6 G! L7 z/ p' L
typedef struct _HTTP_FILTER_CONTEXT; q3 Q8 k% y/ i! a7 f# `
{
6 I- f) l+ M# k0 e6 T+ E0 t
. N7 g: @4 i& D8 j/ m0 a2 bDWORD cbSize; //IN,本参数块的大小
6 e! v' t* z( WDWORD Revision; //IN! D2 a) [& H% D( [3 M
PVOID ServerContext; //IN,由server使用本参数0 M+ W; Y5 A* J( w0 K
DWORD ulReserved; //IN,由server使用本参数9 b" Q' j/ g5 X+ U
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上8 e* M: o* {4 x7 D3 h8 m* E: |% ^
PVOID pFilterContext; //IN/OUT,与本次请求相关的上下文
" N& U0 a1 [6 i& b8 ]' K7 k9 P1 [8 C# W( y7 U# {0 s
//回调函数,取得关于服务器和本次连接的信息
# j- O* K8 s# |# q0 E& RBOOL (WINAPI * GetServerVariable) (
0 u5 k4 q1 ?* r8 @8 G2 hstruct _HTTP_FILTER_CONTEXT * pfc,8 s! F: z7 u4 x/ w( f& W' _9 `, W
LPSTR lpszVariableName,8 ^+ w" }5 z5 a/ r0 W4 x
LPVOID lpvBuffer,
/ v2 e# v7 l. G6 H/ r# a' fLPDWORD lpdwSize. p$ H# _$ f- N* ?: y: j
); 4 z0 g9 d! h4 T$ c3 v
: ^( t( m& [. R4 |
BOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头
' ~. b6 I6 a1 l( Kstruct _HTTP_FILTER_CONTEXT * pfc,0 Z: c5 S/ D* V  Z8 W1 R+ @( n
LPSTR lpszHeaders,
+ |0 v3 i+ M- tDWORD dwReserved& [1 Q1 V; o9 F+ R
);
3 O% e7 b+ k0 c: d+ ?7 R  q. g1 S4 }* D! ]& L4 j7 A
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端# u" U7 s4 s4 e7 G0 ^7 x
struct _HTTP_FILTER_CONTEXT * pfc,' w- J' z0 K1 N2 z- j3 z8 }
LPVOID Buffer,
/ F. V0 e* x: c* |& a8 OLPDWORD lpdwBytes,
) a" [5 s( }2 J( {DWORD dwReserved
; v' A1 J. Z, R" [, c" {2 p);
( ~% V  p8 x5 R9 i; \2 T/ `; e. B# |6 e" O/ F
VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
5 _' O; S  `: u8 w/ [$ B; T% Kstruct _HTTP_FILTER_CONTEXT * pfc,
, S' i8 E' \4 k- H$ dDWORD cbSize,
7 N% E( j* }* _0 O& KDWORD dwReserved
) Z3 r. F, J4 Z4 v);
' |' p4 a1 J! B5 G* F$ K1 U4 U: v" L$ g$ U. E
BOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能5 @) L% w7 s5 g2 i$ H
struct _HTTP_FILTER_CONTEXT * pfc,
. }7 d1 r" Z3 c5 }- h' E- }, jenum SF_REQ_TYPE sfReq,
7 y+ o! A& W" }; Q) p$ H/ R- vPVOID pData,
) _6 A- }$ ~/ M  \, B. x3 kDWORD ul1,8 w  g/ ^! I$ m% B1 u
DWORD ul29 U6 H) r& p' e& U. p
);
  f3 d/ U" p0 l/ ~
  C* s; }$ ?, q8 J} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;
9 y7 X# i8 _6 |, L. U
6 h( I4 T  ~6 V" U! c' D% n四、VC++ 6.0中对ISAPI的支持' C8 \# X: }3 P$ n5 R" d( C4 ]
# D! O( e# D" w9 j7 f& n
VC++ 6.0中定义了5个相关的类以简化ISAPI的编程工作:CHttpServer、CHttpServerContext、CHttpFilter、CHttpFilterContext、CHtmlStream,这5个类都没有父类。其中CHttpServer和CHttpServerContext主要用来编写ISA,CHttpFilter和CHttpFilterContext则用来编写ISAPI Filter,而CHtmlStream则用来操作内存中的HTML文件,为其它的4个类提供服务。CHttpServer在每个ISA中只能有一个实例,一个CHttpServer可以对应多个CHttpServerContext实例,每个7 K! }8 @1 r. }, |  z, `
CHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。2 Q# M# h. z3 B% c

. ?+ R5 P  m% n3 I. U一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。  ?* J+ L7 C4 K3 b8 M  g) N; g  x
6 I; ^+ L7 p' }0 T) F  F3 W
Parse map类似MFC中的Windows消息分发机制,通过使用VC提供的DECLARE_PARSE_MAP、BEGIN_PARSE_MAP、ON_PARSE_COMMAND、ON_PARSE_COMMAND_PARAMS、DEFAULT_PARSE_COMMAND、END_PARSE_MAP等宏,可以实现对不同的命令的处理。每个CHttpServer中只能建立一个parse map,当客户端给ISA发来命令的时候,parse map可以分析HTTP请求中的命令名及其参数,将该命令与相应的成员函数关联起来,即由该成员函数处理该命令。以MSDN中的例子程序pinball为例,该例中有下面这样一个表单:
3 d& y7 F- S. l5 \
9 G" m# `% l; R9 t2 ?$ ?<form method=get action="pinball.dll?">$ ~3 u0 H0 g1 s6 v
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
9 |' f: ~, d4 Y4 b, z/ S<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>
* P( r! k5 I: H3 _7 u9 [7 B' K<input type="radio" name="Favorite" value="2"> Twilight Zone<br>8 q* t3 D+ k* N
<input type="radio" name="Favorite" value="3"> The Addams Family<br>4 s% H3 `  u0 `' ?
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>9 h8 C1 e! \% p/ v6 }
<input type="radio" name="Favorite" value="0"> I don't see it here<br>0 B/ W! J" X) W' i! k
<br>' M$ L  ^% W, h' N
<input type="submit" value="Show Me!">
$ f3 `9 [% h' T) _* L* d) v</form>
6 S+ c7 f) }0 y8 ~& d! S4 g6 r0 I( K$ P$ n- a3 _3 I
当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端
6 u% X: w9 Z; R+ e) o) ]6 D最终将得到如下的URL串:5 _: w: G  X) k0 n) h
7 L) n3 P8 K$ V. N$ N% v: ?
http://www.abc.com/pinball.dll?M ... GetImage&Favorite=1: B( k9 F) W2 @; I& d# ^
: D# d8 i' a- [$ v9 b' k
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
, l  \: p2 R' M2 U: [1 B将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:
" c: E2 E: p  [- d
" v+ k6 O+ R. z0 Zvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);* z% ~2 R4 A# e4 J
8 q% b# n. `( b. l
而parse map需要按照下面的形式定义:! Y' F) R2 i( Y, `7 Z0 {

( Y. }6 ?/ o$ z" o/ d//CPinballExtension从CHttpServer派生而来9 B! v, R+ a4 ^" o  ^; m9 L3 }4 g
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer) ( u9 b( E; v7 ]4 N
. @% t- _$ H% _5 [) w6 b
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice+ n7 f$ }5 v( x* p- i: b% c' t
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) - k" m( D8 ]& a0 a
2 b* W( o8 a4 v% C5 N9 g) G/ l
//该参数在URL中的名字为Favorite& a' r, Y: B0 K0 M" a3 B# A8 v8 {6 g
ON_PARSE_COMMAND_PARAMS("Favorite")
4 C2 d) b+ v! |' T/ n* Q
* S6 s, k% t8 ^0 SEND_PARSE_MAP(CPinballExtension)
+ t4 {1 H" y1 m9 Z5 w' [( t" b% r- ]5 Q/ f7 U  l/ k. [1 z9 r6 z
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:
( R- f0 y( i; v- o. P! I$ J0 ?6 Y( J7 ]3 {$ Z+ P, |6 C! `
OnPreprocHeaders0 ~* H- N: Q/ ^6 y
OnAuthentication- D1 c9 X1 x4 u6 X1 |$ K+ }
OnUrlMap& x) v- U  c) d4 U6 r' [0 H! U
OnSendRawData
' ?& f* o, s' R1 g: ]3 j+ bOnReadRawData2 Y4 e2 F6 K2 J1 N- J) w
OnLog
/ f) B: k5 Z; Q  dOnEndOfNetSession
7 H  M0 g9 Q5 d, e* o& V
; n! P) U( w, s2 SMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
3 H7 Z; ?) P$ [1 L3 d  {1 w
& Q$ M9 u$ B/ a# o/ R0 i参考资料:
8 ~3 J1 g8 B0 P  v2 y+ Z) m& m$ a+ U/ x
7 e# L- t/ R4 c1、MSDN- d% i5 _  {2 R, e2 w# B
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|宁德市腾云网络科技有限公司 ( 闽ICP备2022007940号-5|闽公网安备 35092202000206号 )

GMT+8, 2026-6-18 08:15 , Processed in 0.017905 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表