|
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。 / j, W* Y7 _7 O% A. a. R0 T
, H" i: e+ z$ K% U# a2 \一、ISAPI接口和CGI接口的不同。 ) w7 ^ g% g8 @
4 y6 _ t* P5 F! w. n
ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。 " L4 l% c% E" j+ a
& ~) S/ D: U. B- h; j/ V1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。 0 W e; X9 `( s a# Q- m, ^
8 u# Z# g9 e y2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。 . x A, ]# H: Y- j; f; e
, C$ v( } i+ o |: X- B! `. E+ q
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。
$ X( g& }, S* Y4 D4 r0 k" A& V# L4 t: U7 j
; \, W8 ?5 s7 O5 g. n
二、ISA
/ z) ^1 y1 l8 x9 ]) c
5 ^' Y' N/ b2 sISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):
. l3 N: A' Y& Y) L7 P$ K, lhttp://www.abc.com/Scripts/function.dll?
9 A% p7 E4 T2 D3 m& I7 u' L( X6 q* s: _- F2 O$ f% H' r
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。 4 Z9 g$ j$ w0 X. D6 U0 A0 O
: a% X) q9 f, X8 @
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下: : p2 }' x6 _; ]7 \; k. p
% A' ~1 g+ d8 K& u2 p% t
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
2 |8 g0 V8 C# L0 A0 [# g% utypedef struct _HSE_VERSION_INFO
7 c/ c- G: X6 f+ ~. R |6 v6 l) y; M* W{
/ r( O" t( T- X) U# FDWORD dwExtensionVersion; //版本号 . X) F( l$ m9 w. c0 c ?
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串 ) V1 y" e- K2 Z9 T V6 N% S" s$ C% G
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO; 9 h1 R0 n& ^& V$ `/ ?
) u7 P( d- V1 {8 V& H7 T2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:
% ?' U; n( J! ^( p, k9 Y0 U
7 Y g. v* U4 z& b q1 rDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB ); % ^' L: X2 Z2 h6 o; E2 Q* k7 q
8 a& l6 z) X& }% a0 S! e! I
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数): 6 a; K: i+ c8 b# ^2 ~# t/ y
5 r x* A4 R$ T4 T4 H' ^- O+ ktypedef struct _EXTENSION_CONTROL_BLOCK
2 N1 T2 h1 r" y! x* U8 b. W{
3 y0 p% f; s( x2 g% h" {0 W1 B2 L& E5 QDWORD cbSize; //IN,本结构的大小,只读 + L( @' v0 M3 n! K0 K) U4 ?. y& T
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号 . E1 Z0 `5 |/ [4 g
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值 0 W# |: i% Y0 q4 h/ U
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态 & u$ T( a! n2 h0 ]' c
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
6 k; j$ ~6 P3 ~* M2 C8 L( X4 lLPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD
& s ^: p9 R+ f0 m& @+ h* q0 {' I7 `LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING l7 F: m$ {, {5 Q9 I a/ b
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO . K' q" M9 Q& N6 z' a' Z
LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
, Z, \: D9 \3 o. O* Q4 oDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH 8 \6 w4 f9 H t; b# l {0 E
DWORD cbAvailable; //IN,缓冲区中的可用字节数
: V {: T2 x. c' L1 P5 ?3 U' rLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
0 E3 F, b& N2 h( |LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE
( K( D4 R" v7 Y# r* X; h% }5 G) h
1 r& `, T1 {3 u- b( z! d% W//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
2 y9 {8 L/ o& s5 f/ u3 _& e DBOOL ( WINAPI * GetServerVariable ) , M5 n; A0 B5 {4 K
( HCONN hConn, 0 o% P$ U" I5 h
LPSTR lpszVariableName, 5 g5 K/ D# z6 M" `- S1 Z
LPVOID lpvBuffer, & G Q# L. V5 Y
LPDWORD lpdwSize );
E3 t, ?. f9 w& v0 m. d* n n; |2 ~( s! Q6 Y) {
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据 9 d+ L, c3 E$ w$ J/ O3 G
( HCONN ConnID,
( W7 {. S4 v9 ]; `( J iLPVOID Buffer, 9 ?: S: v% q; O( `" _
LPDWORD lpdwBytes,
, A" M1 R( n3 b1 e. K' R9 XDWORD dwReserved ); , q% U9 l! K) R( I
, Z# _8 `" U$ y9 H- ~BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据 6 E/ U. t2 I/ A* @7 w: X" E" M
( HCONN ConnID,
9 `2 I J3 ^9 o" z" }+ q4 zLPVOID lpvBuffer,
; m1 P; C% n! hLPDWORD lpdwSize ); 8 D: a- m L& s w+ ]% ~
- o2 p) V/ w) b3 J9 [# ?9 B4 qBOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能
2 d& f) c) f, i( HCONN hConn,
- K6 S+ h e @; DDWORD dwHSERRequest,
) Y# F6 h! y7 z2 S9 e5 {2 vLPVOID lpvBuffer,
; U/ r) @" K4 t6 L/ oLPDWORD lpdwSize, l+ ~( X7 c" Z: J: F- V8 l
LPDWORD lpdwDataType );
# c: q5 Z9 `2 N) |- A. P* o& x3 I
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK; " R8 s' B/ ^ M! @6 b: D* }
0 r% J e# \2 N/ t6 [在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。
5 b% Q- x2 `1 V" l- I5 O1 t" z7 y
* x& V0 @+ h1 O三、ISAPI Filter / u5 i; s6 ]1 t; P6 S
: A$ j( V& z) n# L
ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
$ \8 p$ n$ f* J. F( w/ a. Y# W) O5 J3 ?. S+ A9 g
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何 : c' p6 K6 ?) r0 R; \' e
ISAPI Filter都必须引出这两个函数以供服务器调用。
6 d) V5 T; P( C) f) l# H2 a0 @2 G- W5 V2 V: R
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
2 V! Q7 s2 {; T6 J0 {0 k2 BFilter的文件名并加载它们。
: y# c' D0 X1 U+ _# D; Z5 i
7 ?* S+ u$ L) z' D- L4 eHKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
; @+ l- J$ h, d9 Q, n R K
4 D2 i( Y! i4 T$ _2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
2 A4 T, M# h- g6 p `+ f3 Z2 t! d& @9 w1 M: z$ `( c
BOOL WINAPI GetFilterVersion(
% X7 Z6 \1 \" _' V h- y2 BDWORD dwServerFilterVersion; //IN,服务器使用的版本规范 % M' v7 H$ \/ \
DWORD dwFilterVersion; //OUT,过滤器使用的版本规范
& F, Z2 P7 E6 M6 Z2 s6 w- U4 ZCHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
6 C- N0 z: N5 pDWORD dwFlags //OUT,事件和优先级标志 z! W# t& z) K+ O
);
" A4 E# D& Z; M$ q+ |; ]2 w
2 [( W. T4 e* {3 c: |事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。 ( w. S1 T4 Q" n
* L i' w/ k$ X" ~7 z3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。 & p* ?) k5 s' y# }2 j: }- S
1 }) q" B( w7 w' Q2 ptypedef struct _HTTP_FILTER_CONTEXT
2 T# T, U) [9 S/ D% A2 I{ # @( M1 @/ N5 @0 M0 }
! V( }9 T7 v) R+ j& j; b
DWORD cbSize; //IN,本参数块的大小
0 M" e1 g9 K- C: {6 N2 rDWORD Revision; //IN 8 U {" C; C; |# ~. ]$ Y% |( j
PVOID ServerContext; //IN,由server使用本参数
( b: r8 A, a6 d9 k6 HDWORD ulReserved; //IN,由server使用本参数 , t( `4 [) f$ g3 g
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上
3 `# D- f3 b% L& d; tPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文
& z4 I( c5 x$ i3 ?
9 A5 k3 K7 j! a//回调函数,取得关于服务器和本次连接的信息 ! }- q s* a' O3 L% j( O
BOOL (WINAPI * GetServerVariable) ( ; M' y4 [ `3 L2 {5 |1 g# k3 ]) }
struct _HTTP_FILTER_CONTEXT * pfc, 1 v0 L/ j: C _* ]& R3 A6 i1 x/ |, p
LPSTR lpszVariableName,
" `7 o! D) P/ c8 wLPVOID lpvBuffer, ; U- r6 c4 x# N& `# u; I; _+ `
LPDWORD lpdwSize / y L) _4 G1 s& Z& V( S
);
1 g* B6 R+ k1 ]' p# S2 m, d& B
! a3 N. d1 x+ w/ _* A w9 EBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头
; Z t4 o2 k: V* X {struct _HTTP_FILTER_CONTEXT * pfc,
2 y5 V' n T; B6 d" ]0 KLPSTR lpszHeaders, ( {7 |$ I0 x4 q6 I
DWORD dwReserved / z8 U- s+ @; t' a, r* `
);
1 @. U8 i1 v. B+ A, j
" ~$ C; z2 W& s8 B' i+ v9 YBOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
. W3 o3 A8 m5 b6 `3 r: ^struct _HTTP_FILTER_CONTEXT * pfc,
4 J" F. `, Z0 x* ?. \. T6 v4 BLPVOID Buffer, 9 \0 F, H" p1 N) B: S- q7 Y
LPDWORD lpdwBytes, / q) O7 f1 q+ N! R4 R$ o- d
DWORD dwReserved
' g/ f. ?/ \: g1 @2 q6 K4 ]);
/ [* T w2 o' d) t
% h. B4 [! z, W! A! b) L0 UVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
0 r% j+ E: n: V+ l6 X9 t- Rstruct _HTTP_FILTER_CONTEXT * pfc,
7 r4 l; C2 W% F* hDWORD cbSize,
. h" x! x. X+ ^& @$ S: X) H7 fDWORD dwReserved
% @* t9 M: x) D& E+ [& A);
* d2 @8 N5 n8 S! @5 f; {
! w& j& x- o$ V+ ~: P9 J" mBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能 * ]8 `7 x( s- f. C- K
struct _HTTP_FILTER_CONTEXT * pfc, 5 `& X0 \! N* R+ X- @. A* m
enum SF_REQ_TYPE sfReq,
; E' N: m# b" q& H/ G4 J0 gPVOID pData,
0 _% Q& p2 M6 Y' r3 H3 t- d7 U+ lDWORD ul1, + [( o5 y* U, g- j
DWORD ul2
) c' P, ~9 u4 ~0 c! W* g/ y);
$ ~, C2 {1 \" Z( i- t& i8 @+ v) v6 H$ Y4 K8 I# c
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT; $ V8 ?/ g6 w; K9 c A1 x3 M
& M$ M5 z' ^( O+ E2 E8 ?# Q# Q四、VC++ 6.0中对ISAPI的支持 # y9 G5 |$ n& k- W
+ J' t6 k$ }4 u$ z1 r( W5 T
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实例,每个
/ F$ v6 o9 a0 Z4 z6 uCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。 ' e7 X9 K( Z* }
2 \3 x& @% _. e% ~" E' l一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
; v5 y) Y; p: M% p w5 a% A4 `& {4 K
+ i9 S- _7 C+ B5 T d! VParse 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为例,该例中有下面这样一个表单:
% A6 o3 m+ z } t7 V( K3 {( @1 S- C" W! `
<form method=get action="pinball.dll?"> 5 n9 O# T& {. y# j6 N: F% l! b
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage"> % Z, @3 [1 q* r& M4 I' y* f7 z
<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br> ; H, G2 }* e( Y* a7 l( W- T
<input type="radio" name="Favorite" value="2"> Twilight Zone<br> 7 E9 m4 m& z( @9 L' e+ N! z6 Q
<input type="radio" name="Favorite" value="3"> The Addams Family<br>
6 D7 b( A1 W2 {. u8 Y+ i* u! B<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
! [) k* Z5 F/ f0 F5 ?! e! x<input type="radio" name="Favorite" value="0"> I don't see it here<br> & o5 N* P2 {) S5 b2 j/ V
<br>
[+ u: `0 r4 j' x* i ^0 I<input type="submit" value="Show Me!"> 8 M6 B& A- v [ `# L1 O
</form>
4 {) `2 ?+ F$ d" D* M4 J8 H6 a2 c1 X( I: \" ?* j
当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端
0 c0 K s R5 m0 q* [最终将得到如下的URL串: * Y& j3 U6 z2 p
4 X# I( I1 U" |- r) Z3 V6 ^3 w' U8 r- V
http://www.abc.com/pinball.dll?M ... mage&Favorite=1 : Q1 Z" W8 ~$ ~) X
y( e. R j6 D2 I u" T3 t& p5 c
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数 $ i P; y$ n( P! G, M! r. y2 c4 i
将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite: " t5 n0 S2 P7 O |( q( r
5 H: A9 N8 t& H/ z; L( [8 j: S3 O% K
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice); 8 u( E# k7 ?+ ]" S4 m7 ~
2 D+ Z% P p3 f2 r, w) r) d8 c
而parse map需要按照下面的形式定义:
) R( h9 E+ k0 m' P5 \* J
# r M6 G; U6 a j//CPinballExtension从CHttpServer派生而来 , @% P1 ~7 A; i+ h
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
% A# Z0 r. L+ V2 X
8 t% D* c4 h( r* p2 N p/ }+ `//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice . d' v/ v8 t+ z, c
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) & p7 g' i5 S8 O3 p9 M
# c8 M4 [1 N4 ]7 S7 r: X
//该参数在URL中的名字为Favorite $ Y U+ ^* }/ q/ D0 D4 r) I
ON_PARSE_COMMAND_PARAMS("Favorite") , ~. L6 p1 p- e5 {
$ O2 E- y; G; wEND_PARSE_MAP(CPinballExtension)
3 b9 n& n5 s# I& z( c
; t/ W% N6 e5 g+ ` Z而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:
0 y' b4 j) C* J6 B7 p- _" g$ T9 j" N+ j
OnPreprocHeaders & u' d) |& Q1 E8 E+ i# @
OnAuthentication
& @5 u j, a5 |! Z' g" IOnUrlMap
- c7 e0 X: L' @% j: A: J3 v, UOnSendRawData ( Z; N R5 V/ ]' D
OnReadRawData 1 }( b; ~0 b9 l% ?4 _
OnLog
" _. C. b- `) s' O% U6 SOnEndOfNetSession ( V& @5 i; h- g J) w+ |
- y; g, h$ f3 e+ ~
MSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
% u$ B5 C& ^4 K: t( K/ {
" G! w w+ A( K$ u+ @( W) q1 g参考资料:
. w- J! n1 i6 j, a0 L' M* S# ], \* L! P8 {4 I
1、MSDN
/ D( |: [ c/ m+ }, [3 ~5 }2、《精通CGI编程》,丁一强等,清华大学出版社 |
|