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

[转载]IIS的ISAPI接口简介

[复制链接]
发表于 2004-6-9 04:43:55 | 显示全部楼层 |阅读模式
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。
8 D5 x/ i4 j( ]. `! q4 `+ X" X* \; R: M9 E& a) x& ]' Y- C
一、ISAPI接口和CGI接口的不同。 ; W. V5 ~1 C- H; N

! ], W4 i1 r5 X8 |4 W+ p) gISAPI程序和CGI程序完成类似的功能,但是实现方法不同。
" _& @8 S, F) f. d2 H. h% Q# m; R$ p5 ]' ?
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。 ) V* [, U( x  o0 E
3 d3 n$ B4 c4 `# l' l5 S$ `. g
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
. ~8 V% `. H# e: ]9 X9 `
7 B, ^+ a5 P6 s+ ~# R4 ?4 T: vISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。 ' x+ f3 b/ C/ L& s1 G( x

" s/ K* z0 ?( G. }0 e+ R
& |# b9 m2 p8 n2 e2 n二、ISA
3 H/ }/ @. I8 @$ H0 c: X/ R
8 M1 k. y. A5 H0 mISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下): - f8 t& i4 S: S% A/ K- }2 F! z
http://www.abc.com/Scripts/function.dll? 6 p+ h! k; w5 B$ N5 i
4 F1 d( ^% M8 m8 K$ _
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。 2 g! h# B8 j  y; v! G

4 P2 T( _! i4 V5 ~# G1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下: 7 c$ c# r1 O+ M& |2 T1 E

* E. K& f3 c; Q5 s! v1 R8 kBOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
1 [9 \+ m: v+ P9 v+ m8 btypedef struct _HSE_VERSION_INFO % |: T: j6 Q5 l/ J: t: A6 h1 [  t
{ 5 @, e# k3 z! r8 R
DWORD dwExtensionVersion; //版本号 ! }. _/ P, F1 T( v; o* V- J) b  p
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串 2 x8 ^  i0 n2 ^! O
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
! @7 A# [4 ?' Z( Y7 X8 O( Y8 n  H4 M) F9 k% S% x/ ~" [
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下: 2 B$ l9 f4 p7 u# W* {' d$ y, n( C

; L5 S& p+ J6 c% J* M# Z* D" kDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB ); 7 O& ]$ o' l$ h
! a! m  O9 |4 v1 S& C9 ~
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数): 8 r$ i" p* V' L
- F; G9 z/ @. Q
typedef struct _EXTENSION_CONTROL_BLOCK
7 y7 y1 [2 W  u9 L$ N" M{ 6 T0 b( Y5 B* _
DWORD cbSize; //IN,本结构的大小,只读
/ t4 N5 W, }  ZDWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号 : i) |( E$ F  O2 s! C" \
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值 ! Y+ N: |& L% j7 Z* }8 U2 V
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态 . C! x, |: z: q! b& s2 ]7 V
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容 ! }% s% D: V% f6 P) q
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD & |5 d8 S1 Y) t
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING
0 P- o, E$ K* U) A% o( i- Y; JLPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
( p7 I6 R7 k6 c& M4 U4 K7 \LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
& k, M7 ^0 @+ lDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH
' D( A2 n: L- X/ N; A2 U: K5 N  tDWORD cbAvailable; //IN,缓冲区中的可用字节数
9 H$ y3 g: p- {+ FLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据 9 V4 }( m$ n2 U3 w' }( u$ T! Z2 Q
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE 7 f4 Z( y( f" t
  `3 D" z) _" b' h, B/ L# c7 ?0 b. _
//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
! N1 v+ C* {, ABOOL ( WINAPI * GetServerVariable )
! R8 n7 j& y4 v- F3 {. q# _  z( HCONN hConn, & M+ K$ [+ r0 f2 q+ P
LPSTR lpszVariableName,
7 s8 z' I# U& U+ u. d& B7 iLPVOID lpvBuffer,
1 \/ _0 T6 p4 [, _7 sLPDWORD lpdwSize );
* H* D/ M* |# k6 H
, U( S. ~  _, R" ABOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据
, h$ ~8 ]+ l$ ?5 ~( HCONN ConnID, . ^9 R$ A+ g( t4 d1 V5 h
LPVOID Buffer,
, {) `1 l* x' U6 E' C0 W/ `LPDWORD lpdwBytes, ( {3 h7 ^3 i3 A2 l% A, f
DWORD dwReserved ); - d* q' S! k6 \5 w$ j0 c
0 s+ i  i  }/ e1 E0 ]! y
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据 : s( F2 W* x" N1 @- w
( HCONN ConnID, , G. ^$ s( X# G# R
LPVOID lpvBuffer,
6 e( [. D8 R7 v4 E7 \7 U: |$ B0 q/ vLPDWORD lpdwSize ); & F  O4 d& j& k) v

' `% c+ D) M. E+ ZBOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能 ) y1 W  O& N" p* I
( HCONN hConn, ( z1 ~% R3 v1 i6 n3 {
DWORD dwHSERRequest, ! R/ w! m) |1 `0 z& _3 ]
LPVOID lpvBuffer, 6 i/ L: C" P2 a  L; m0 {* ]. l
LPDWORD lpdwSize,
0 R9 O( Q4 v; p* H& R- jLPDWORD lpdwDataType );
& z2 F$ a' F* j5 V: A$ y1 b: v' ?$ q
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
  C6 C/ r5 n! R: ?
( ]- B; v1 a( f  t* F( I' F; i在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。
6 }! @& K# g5 |) {- b4 D  L2 G4 ^- c& G- I0 G5 W
三、ISAPI Filter . @0 F! T, Z: L- U3 U
+ K7 w& }7 x- j6 ?9 h8 l7 j
ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。 , k# y9 _1 l2 J* d4 C, y$ l

. ~) |; d, T# {9 zISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
  j0 o; V  ~$ R3 T3 F2 }ISAPI Filter都必须引出这两个函数以供服务器调用。 " F  V4 f: F9 t/ k; d* E7 h
( b3 r9 M# g+ M
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得   s: b3 x) b( T/ d  U5 ^; x5 t
Filter的文件名并加载它们。
  M  l6 Z) `" q) g" ?" ^" q
  ~6 F2 u: `$ L! b$ M+ i9 d) Y" dHKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL : ?) J& \% N+ l# B

: ]/ F4 }1 C1 B  b3 k8 J2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下: ' f8 s6 v, i$ _! }$ z0 {, H

; `/ U7 f3 ?' a3 XBOOL WINAPI GetFilterVersion(
" N; Y' M& ~  ~* B) NDWORD dwServerFilterVersion; //IN,服务器使用的版本规范
$ s  [2 I4 t3 t" {5 {4 S( MDWORD dwFilterVersion; //OUT,过滤器使用的版本规范 , @9 J- P% X9 }$ K
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串 * G: Z$ J" ]" P! Y" c/ p
DWORD dwFlags //OUT,事件和优先级标志 9 P4 G0 X; ^+ x- j! @
); $ c: R( K: j+ N% r

9 A7 s! v! e: r  ^2 q6 d! i事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
- l7 n3 u% Q( L! w- v% r+ o; Y; h9 I, _# x' y* @7 A
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
9 a9 T: ~* E$ F: g! L3 Z2 k
) x8 ?8 M0 }: j3 \& \( Etypedef struct _HTTP_FILTER_CONTEXT 1 Z( e( u$ g! {/ }- [8 z
{ " Z* r% |! d6 u4 d0 m

$ \- T1 z4 Y3 aDWORD cbSize; //IN,本参数块的大小 5 }6 a; T+ W( Y+ o) n5 G) q
DWORD Revision; //IN
; T9 b0 E+ Q7 B% T5 PPVOID ServerContext; //IN,由server使用本参数
# e' P  |- R$ R2 @DWORD ulReserved; //IN,由server使用本参数 " s1 m3 N, }& l; k
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上
0 ~8 x6 a& F* v2 x7 R9 @  n7 v) XPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文 5 S$ i4 n1 p: O0 k* z. K

9 T' y9 Q: H; ]# e& p. r% l- }5 }//回调函数,取得关于服务器和本次连接的信息
0 k" O& F  @9 }* b# v; d. E5 W' hBOOL (WINAPI * GetServerVariable) ( & W# }% N9 O) |7 x
struct _HTTP_FILTER_CONTEXT * pfc,
# d2 S( ]0 [: ^% U# P* x- s, ?& b3 QLPSTR lpszVariableName, : ~& x9 D; F5 l* F* G
LPVOID lpvBuffer,
$ A& x* X* s, E# L# oLPDWORD lpdwSize & t2 p& I4 a: K7 K6 K
);
# _, t* w4 r# q  i0 o3 P
6 v2 z( r+ o7 A8 z4 R" q, ^* M- aBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头
3 ~1 _1 }/ a; K0 y3 n# Jstruct _HTTP_FILTER_CONTEXT * pfc, 6 U/ C/ T- E' m9 m- K
LPSTR lpszHeaders, / l6 \( C& n% ~5 n5 U" O; H
DWORD dwReserved # ?$ U- f; [9 U, u1 o2 b2 z
);
2 ]2 L! l7 I. Y% m1 r7 G5 F  ^+ c5 p  B( \# s5 w( ?  m
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端 5 Z7 b* k! C2 E6 N2 i7 s9 j
struct _HTTP_FILTER_CONTEXT * pfc, 2 A9 O1 m# W! T
LPVOID Buffer, ) x( P& _! v, D5 N1 M* z
LPDWORD lpdwBytes, 3 b' d& F' b% e; m
DWORD dwReserved
; L/ ^6 \" U; s5 \7 a);
) r) P8 h% S: l# p& k. a+ K+ h8 j$ }) b$ S( b, C6 T. I; l
VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。 $ u8 v* t3 p( h  O$ u) O
struct _HTTP_FILTER_CONTEXT * pfc, 4 G: [7 @! p3 R: r" U5 W! s0 W7 L
DWORD cbSize, 4 E+ W8 D& I. ^  w/ C/ s0 K$ c
DWORD dwReserved * N% L% X3 a* m  j2 Z
);
7 T& z' U$ H( o9 c/ f
) l9 a9 f, K: V1 D0 h9 T3 w9 dBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能
/ w( D! T5 _8 x1 T" T" B- Jstruct _HTTP_FILTER_CONTEXT * pfc,
% b: V! j5 k. M2 ~, n. y6 l% {enum SF_REQ_TYPE sfReq,
% V9 L% V* Q# g6 z2 C" v& cPVOID pData,
, P; e  t. I  K: C. Z) _3 nDWORD ul1, ' {& L0 P; [  ~% t  v, D; B
DWORD ul2 , k* H& }( J, [7 p5 C. h
);
3 n5 I7 A/ C3 `5 g6 e& M( \! E/ x% Q' K6 n
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;
  D. P+ }' O6 ?5 j2 {' v. K9 }3 _# A7 ^
四、VC++ 6.0中对ISAPI的支持
! s  V# h7 j/ g& g0 x
! e$ H3 o" _: t1 z; ZVC++ 6.0中定义了5个相关的类以简化ISAPI的编程工作:CHttpServer、CHttpServerContext、CHttpFilter、CHttpFilterContext、CHtmlStream,这5个类都没有父类。其中CHttpServer和CHttpServerContext主要用来编写ISA,CHttpFilter和CHttpFilterContext则用来编写ISAPI Filter,而CHtmlStream则用来操作内存中的HTML文件,为其它的4个类提供服务。CHttpServer在每个ISA中只能有一个实例,一个CHttpServer可以对应多个CHttpServerContext实例,每个
9 Y3 y% w! g2 |7 N: e9 j2 ~2 uCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。 & T" C9 {' x2 H) ~$ H
5 h# A8 V5 r0 w; ?; S- \
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。 7 D) |9 q! r# q; E+ i. B& ~3 k. \/ N

  K! `* g  i+ w. X$ ~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为例,该例中有下面这样一个表单:
8 f# [$ n' D; k7 r7 F0 d  Y( M0 p8 N8 U: H. E, A
<form method=get action="pinball.dll?">
# H# s3 O9 ~9 O' H3 r# s( i% S& _+ @; C<input type="hidden" name="MfcISAPICommand" VALUE="GetImage"> 0 M  Q" C( j0 }
<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br> 3 ]( _8 H: C5 k
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>
# l9 @7 M) b: d2 A$ h, U<input type="radio" name="Favorite" value="3"> The Addams Family<br> 7 k3 G* c5 J- ^  k, @
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
0 B' Z* Z( K, q; [<input type="radio" name="Favorite" value="0"> I don't see it here<br> 8 }- G8 L8 V4 F) I8 ^* H2 w3 v
<br>
; g) ^0 k2 |" |7 L, ~6 t! e<input type="submit" value="Show Me!"> - M- L( j8 E, R; _, @. {; C9 a$ G
</form> / B0 S( ~5 Q* n4 q
  b# {7 C9 G/ Z7 Z( e
当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端
3 A' m: A3 x# L; R: a/ U6 L最终将得到如下的URL串: ; D4 |# `1 C8 i1 q7 T

6 }( |  D' u. C  }' phttp://www.abc.com/pinball.dll?M ... mage&Favorite=1 ! ]  n& X2 R/ s* [

2 n  `0 V& L7 x+ L5 X0 V1 N: H在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
8 l) f  X! M) }# y5 ]  V6 p将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:
! p- m" j1 }, @$ T* q
/ J# y2 n5 d' m8 wvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice); 4 w* }: u2 [% H4 S0 X( N! n. D

" \) r7 }5 m, }: U+ ~; j5 w而parse map需要按照下面的形式定义:
$ ]$ K3 m! Q) }1 ]" G; o& m7 Y0 m% I$ s- n# n
//CPinballExtension从CHttpServer派生而来
% J8 ^4 X( K% P% ~5 iBEGIN_PARSE_MAP(CPinballExtension, CHttpServer) 2 E) L6 c: ?- J0 G$ N' W' S% f
3 K1 N  k. W; w% ?3 ]% F: ~2 r
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice $ D: K1 _  m* M: d& j* c5 ~
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
% `/ V( }3 j' I7 l- S
% h/ o6 I' D8 M- {//该参数在URL中的名字为Favorite # N0 I! E' h7 C! Z0 _# E0 V
ON_PARSE_COMMAND_PARAMS("Favorite")
3 t/ \, T. j* x, f! {$ }8 G; A1 ?6 n  [
END_PARSE_MAP(CPinballExtension) 3 E: x& P" i- r

) ?  _4 q' l* k5 o3 T# k3 F$ l而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:
: b. O0 d* U* q/ a3 U- T
* R  W2 z0 [9 E: `; Z! L* QOnPreprocHeaders 2 i% S8 P8 u. W
OnAuthentication
3 v& G; S- n% ?6 ^9 KOnUrlMap % j: U4 ^7 x4 G5 {. L0 F8 _
OnSendRawData % Z8 |( E. w8 Q7 d
OnReadRawData
8 r5 q. t* [5 Y7 E% J. G3 dOnLog + u0 N& s5 s/ J, q+ w2 J
OnEndOfNetSession
2 `2 F0 X0 @8 q5 M6 ]+ W9 R4 N+ n3 ~
' B# L( Z2 P9 ~5 ?) j1 c: qMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。 / z+ O! n, a; h6 M9 Q

/ s) ]: o5 n1 X4 [; K参考资料:
& {/ H5 I" Y3 Y6 @* j3 N+ r+ R1 p7 `8 j3 X' f
1、MSDN
* S5 x+ c1 {1 J# B2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-11-14 19:57 , Processed in 0.017737 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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