|
|
作者:netguy (mailto:netguy@nsfocus.com)
( ]- n& u: E7 W! H" b8 ^
1 i, N5 \' a4 H* k2 }- S+ D* pISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。
0 i% P+ t, ~0 s+ Q) F5 G
6 t9 T/ ^5 Y# _$ s# n. ]& g; ]- {一、ISAPI接口和CGI接口的不同。
) E J( J: R" s, v. g% @) V# z. z! p+ `8 ?2 L4 y$ ]" i
ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。 d6 n3 I: R2 h* ?: V
0 r d1 j* I f6 @6 S# w1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。
, w, @" b, C$ u& }- R' M8 k' s' L! R _* l& g/ u, {
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。) t7 J6 o: Y- u1 Z& ?
+ A d, ]9 |* U" ], W! n3 cISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。2 g8 G5 A+ w. _- e+ R, Y9 l
) u& I5 L' T% |
# I: z5 f! f4 c3 ~+ F( W+ v2 f
二、ISA8 O2 [; P& D- W
4 a" W3 t0 k3 i4 T# E* [. h
ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):. R* o* w* ~$ A2 ~5 _
http://www.abc.com/Scripts/function.dll?2 |1 s& l C4 Y
. Y$ [1 o1 u+ W4 M3 i
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。9 @/ j+ {) j& h. S4 _
9 r+ \0 l3 |# A( r/ T# l2 {% b. O1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:& j" o& N0 ?/ T+ u0 \
' A2 S9 k; [2 O- Z) E
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);; d% `0 i. K; x% G* m
typedef struct _HSE_VERSION_INFO
* D/ ~# u& P& N' Q{
" g8 Q6 [6 e. M! d* ZDWORD dwExtensionVersion; //版本号
( P: }2 `: V" x* I$ |1 N# _5 y! fCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串" y" ?8 A4 u, O( H4 V
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;* c: X! t1 e4 Z4 s* ? n
7 F/ K8 i& G( N# U- E
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:' H8 z! ?- |* T# n# y2 \0 o( A
L7 e6 u4 [7 o& L9 X% Q. A6 GDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
$ w& a, p& [. K9 x' i) x+ t& A( x& k) Q: R9 F" t
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):
/ p- k5 |# e( R0 H5 ~ W4 U+ ?- Q& v
typedef struct _EXTENSION_CONTROL_BLOCK
* Y( z/ W+ _( I/ p8 Y5 {3 e{4 M9 i1 p5 W8 U/ b4 `5 t( g
DWORD cbSize; //IN,本结构的大小,只读 U8 d4 T! D$ }' l8 v! P: c
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
3 K- v$ v. i$ U( q; Q/ w2 W- K( o9 ~HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值' N$ S% {9 v; F! D
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态
0 k; ]$ i; O9 g/ rCHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容2 B. M" [, C" v* ?* @" Y( b
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD0 _) N: O/ W9 t3 O" U9 r0 x& a9 S
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING, m: Z _5 I" n
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
' M- o! s4 f6 \- _3 o. @LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED* z' W* ~% M9 Y: J( e
DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH% i4 q% w3 e) y: }
DWORD cbAvailable; //IN,缓冲区中的可用字节数
6 N. p- s8 X; e$ C8 k' KLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
0 ^7 ^3 P. h9 K: I$ Y8 BLPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE* j( C% W9 i" M V, i1 {& E
7 x7 K' j- l: l1 [1 Q//回调函数,用于返回服务器的连接信息或特定的服务器详细情况( E) M* x D5 ~1 [% Q3 O
BOOL ( WINAPI * GetServerVariable ) 8 g4 R6 U& \8 O* ?9 ^& U- }
( HCONN hConn,- O* u5 T' ^/ Q" Q! r, r
LPSTR lpszVariableName,
7 g# F' Q' X! ^9 X8 {LPVOID lpvBuffer,
7 j5 n5 C# h5 v: Z: |, F: L& }LPDWORD lpdwSize );3 T2 ]2 B( i& I- Q! R
" e6 c/ r1 q) [9 w. r# m3 GBOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据9 Y$ S; B2 `- X2 {, _8 N
( HCONN ConnID,
6 X( E" a5 H0 q3 o" KLPVOID Buffer,
! c% Y0 x4 y9 f& K+ i7 K+ h! bLPDWORD lpdwBytes,
0 z- }4 k; A9 G: |DWORD dwReserved );
( m; s: c& r; \% F. J' o& k( ~6 k0 g; y2 T
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据
( F; w, a0 E& h4 R( HCONN ConnID,
! z/ [$ e F" PLPVOID lpvBuffer,
6 D* G9 @7 B d: ^+ aLPDWORD lpdwSize );5 A4 M k3 T' `# `$ v4 N
: J/ R/ `/ [( C" a5 d- s2 sBOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能
0 U# R" [7 \! [& E4 g( HCONN hConn,
9 d& O; J5 A, q! TDWORD dwHSERRequest,
5 E0 t: z. l- l4 Q* d7 w# m' V7 }LPVOID lpvBuffer,. ~& H: v# r5 P8 L5 I* u' a! g
LPDWORD lpdwSize,) X4 o t S8 U @' i
LPDWORD lpdwDataType );
- z% o, c2 M- l6 o" u y
& x# Q% P% O: W! y+ c# Z; u} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;, w/ }& l9 ^4 e
, V1 a6 G* e: ^& m% F在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。0 g" ^% P5 W0 j8 A
: z6 U8 O8 Q7 }+ \% L u- o2 A* l三、ISAPI Filter4 r3 \# t5 f; p r% l: F2 L
5 Y% D4 T, C- r" }1 K# N3 F2 AISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
6 E4 j8 S7 e- `
( a6 |& x) w- h- h6 |0 P+ fISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
9 U- z& S [- J0 E# T' x9 k0 IISAPI Filter都必须引出这两个函数以供服务器调用。
7 D; @! v1 ^8 A* a' }0 ] q Z3 G3 P
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
9 e9 R/ T, |4 y; |Filter的文件名并加载它们。% Y6 m/ E3 G+ |5 H5 e; P
2 ^4 N5 _% N9 k: ]% ?HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
3 T' X7 E s3 X( L5 b' C2 m7 e" E* a7 W. a/ h% w
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
9 n, F$ j# ^ n: c7 }" |, e! l/ L) a1 q( ~0 ^
BOOL WINAPI GetFilterVersion(
, E# s! x4 ]. |# w2 \8 YDWORD dwServerFilterVersion; //IN,服务器使用的版本规范
7 K4 X8 \6 F! H; M% G7 k: W" \DWORD dwFilterVersion; //OUT,过滤器使用的版本规范
8 Q# y0 ? \' g/ O# c) q9 ~* rCHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
7 a! c, t# I% ^+ wDWORD dwFlags //OUT,事件和优先级标志7 o: G8 P" _. S: A% v
);! |4 @9 N+ Y& P* V6 h* x& I6 k
8 L: k0 | o% w1 V
事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
7 p. v. U' k( O. V5 [) Y! D* Q5 E8 S( C! }+ o: m+ `! e$ r
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
2 s0 N* I4 [7 V% g: ]2 k+ Z) x- w- v9 A# B
typedef struct _HTTP_FILTER_CONTEXT* E# F7 X4 {" t" U
{
2 h( d$ a9 ^9 G2 N8 c
! G+ }) l% h* o. ?: |DWORD cbSize; //IN,本参数块的大小7 p1 V6 L' C6 o/ w, X& U
DWORD Revision; //IN9 b7 b5 _$ K- g. L2 J8 g8 v
PVOID ServerContext; //IN,由server使用本参数' K% k3 y5 C9 p
DWORD ulReserved; //IN,由server使用本参数
' [# O" C# c. R0 q# wBOOL fIsSecurePort; //IN,事件是否发生在安全端口上; b1 n5 S& t# k, L9 g
PVOID pFilterContext; //IN/OUT,与本次请求相关的上下文$ O; R; b: t$ w0 x% p1 W6 h
- {) Q8 M3 ?) O* F0 M5 P//回调函数,取得关于服务器和本次连接的信息
( M+ o; p) ` v. p0 tBOOL (WINAPI * GetServerVariable) (
% K1 z! p. T) P, ?7 U+ w8 \2 D5 \struct _HTTP_FILTER_CONTEXT * pfc,$ D: m3 V, X* A# P
LPSTR lpszVariableName,% Q+ m/ M; Z8 [; b
LPVOID lpvBuffer,8 [9 Y+ Y( F1 O8 w% E
LPDWORD lpdwSize( ^* O( l3 L$ P8 z% U
); & ~/ G5 r" a f- _
/ a) b5 h$ \( S$ {BOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头6 q b# d! y1 P4 f" c! ?
struct _HTTP_FILTER_CONTEXT * pfc,: b; ~0 d! |$ h7 {4 J8 M3 P. K# T, s
LPSTR lpszHeaders,
: B8 k% M f: @& TDWORD dwReserved
4 Q5 e6 N5 Y6 t7 C0 X); 8 z6 {! R; T6 h2 n# s% }
6 c" R& W; u; b3 S% I. L, MBOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端- h( x1 O) m" u. j0 W9 q) v/ A
struct _HTTP_FILTER_CONTEXT * pfc,
& w8 m3 X# e) L0 \) cLPVOID Buffer," E, ^2 X2 b% k& j) `) I* ?, {
LPDWORD lpdwBytes,
9 p ~9 @3 e2 T/ }DWORD dwReserved, _* ]% a- t: Z6 e% D
); $ p! Y% D6 m. R/ ~8 c( j+ q
" J [$ X9 z: Y! }4 MVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
- |* m6 \) G% s6 c1 ^+ }$ S! D0 Nstruct _HTTP_FILTER_CONTEXT * pfc,1 t8 v: p6 b* p9 e
DWORD cbSize,5 B: y+ I: }' B
DWORD dwReserved) @8 w& ^" H3 k) G
); ; F+ V2 E' b6 Q1 Z5 D
$ A& w% Y+ Y" b A. B+ Y3 i
BOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能1 L/ w. Q2 N( X2 S) y5 G J) O
struct _HTTP_FILTER_CONTEXT * pfc,
" S c6 K5 w- Lenum SF_REQ_TYPE sfReq,
& X# ]" b9 ^4 V* ]# a sPVOID pData,
6 T* O2 M# U# WDWORD ul1,0 n4 k1 V( z5 l% [, [& _
DWORD ul2
0 y( i) V/ T$ R2 A d) D! F);
$ u m- ]/ v# s2 N
4 N$ I3 |: c4 z1 i* `$ W3 r} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;& W8 r1 j% V# F0 n( G
. A3 i* I" I4 b) ?* P
四、VC++ 6.0中对ISAPI的支持
8 m3 f( @8 a" S: ~6 G3 ~
8 j, X+ B* w$ t. L; C) F7 d$ O( kVC++ 6.0中定义了5个相关的类以简化ISAPI的编程工作:CHttpServer、CHttpServerContext、CHttpFilter、CHttpFilterContext、CHtmlStream,这5个类都没有父类。其中CHttpServer和CHttpServerContext主要用来编写ISA,CHttpFilter和CHttpFilterContext则用来编写ISAPI Filter,而CHtmlStream则用来操作内存中的HTML文件,为其它的4个类提供服务。CHttpServer在每个ISA中只能有一个实例,一个CHttpServer可以对应多个CHttpServerContext实例,每个
/ }( I. ]) E, ^0 q4 ACHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
7 n! L3 Q0 a; ~+ M' ^5 i6 \9 @
& s2 O/ G+ |. F' k' f3 j. Q一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
- X: e4 s# F- ^% R y; h2 @6 w/ I1 b% T, j4 _
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为例,该例中有下面这样一个表单:
! V, I1 ?6 i! W8 O6 j* A2 h+ a" }; H
) C6 ^/ \+ Q/ h<form method=get action="pinball.dll?">; k F& W6 b* v( W
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
& n! k" d& i, h<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>( f! J4 M. Z( w1 n* U% F
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>( E; n7 q3 Y1 y. h/ B: Y$ c, o( O
<input type="radio" name="Favorite" value="3"> The Addams Family<br>
/ D. u4 O. [" |, Z<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
" M9 h, w3 A5 B; J) I6 t0 |0 J<input type="radio" name="Favorite" value="0"> I don't see it here<br>) ]9 b0 o; C) C: {# ?* r
<br>
8 ?! L9 i T/ d L7 G' O5 r* Z( h<input type="submit" value="Show Me!">% ^- S3 l+ z, e" V1 q8 F. }; j2 p$ R' V
</form>
9 ~ R& q/ b, Z' `# H
! f% u, h$ U$ Q- F% o% G( Q! a当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端# ^5 t. O1 s$ V" h
最终将得到如下的URL串:& I9 d- L7 b) g& E6 G; O
]3 y" e' L7 q, V2 Z; K- w7 W- W
http://www.abc.com/pinball.dll?M ... GetImage&Favorite=1
O8 i" X8 J& w! r( L, A& Q8 X- j: b0 o$ ^
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
6 M8 C" }( @$ b' W, C; \% U将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:/ A# Z1 N, S3 Q. m" U
: Z1 S# W7 B- `; ?# }7 E) a
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
4 E" x: E# K* f# ~# ?4 @; R0 b+ |+ r" [/ L, ~/ N3 [% M% W
而parse map需要按照下面的形式定义:
1 q S( @) s8 q
' x; X' H4 l2 P' ^2 w) a//CPinballExtension从CHttpServer派生而来
6 T6 e2 |2 x4 w' O! CBEGIN_PARSE_MAP(CPinballExtension, CHttpServer) N2 t0 A4 m% W6 O
# S0 ~/ C/ H: n7 |- \. m: J
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice$ x# U+ V7 ^8 j& U
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) ( s7 A2 d1 i3 t) m9 [" }
3 c" c9 m3 \4 ?8 [- L//该参数在URL中的名字为Favorite
6 ~( G6 l# g% B+ j. j, n+ S; @1 [ON_PARSE_COMMAND_PARAMS("Favorite")
3 X, w2 l4 }% m# o6 ^6 n2 Q" J5 m2 n% L% }
END_PARSE_MAP(CPinballExtension); p$ P! o( Z7 p: k) A( o
3 w( ], M3 o) r7 b3 h$ F
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:8 f X. Y( Y9 W. c2 D/ o4 P/ c
1 R; H9 }& }$ ^5 oOnPreprocHeaders( p1 s/ T2 A# D( @" E7 L2 S9 R
OnAuthentication
& W2 j* A2 \- E9 |' I/ d* ~; M9 u/ g2 nOnUrlMap
6 k& u; P! Y& Q6 d8 O+ i( [0 L rOnSendRawData
2 ^5 t k0 i& Z5 t9 q; V/ `OnReadRawData' W/ q/ f A9 V3 [+ \# N, p" L, x
OnLog
7 t7 h" Z' n5 |& ?1 u. DOnEndOfNetSession' y" A8 _$ H( `. r; k/ z' L
7 ]0 \* h3 ^9 G2 y% i
MSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
. w2 D1 Q/ K0 K3 F
% \. N( O, f' r3 A& C参考资料:
/ b/ j2 R# d' R5 a/ z3 ^# x
( s1 K/ T8 l& v) ^1、MSDN
+ y( D7 Q$ |/ r* `9 {4 J& @$ e2、《精通CGI编程》,丁一强等,清华大学出版社 |
|