|
|
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。
1 {; z) Y2 G" f$ C1 ?! _7 `5 P* |2 i' h4 r, }+ F
一、ISAPI接口和CGI接口的不同。 & b" ]9 x v+ ?# a* W
2 H2 X8 k# ~# a$ g; ?
ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。 % [( x( i2 Q k9 A
2 W& s/ N3 u3 _1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。 " p) C( q3 e) C9 y+ C4 `
4 K/ m0 f( h( F( ]$ f. D) a( q" ]2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。 " T3 g( {, y# ~ U
. L" u, Z0 [9 P6 u4 P O) D2 u+ o2 c! r
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。
) M- v& z6 ]/ A; ~3 F# v' a2 r9 n3 L$ u: ]) m
! F3 a- P; }. K, F; v6 f- o二、ISA
& i2 { z5 ~' I0 {, h1 I+ h/ q3 E- M3 M+ U9 |) G7 f& z, A# a
ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下): ) U3 L$ J. [' a* e0 |" ]2 w$ w
http://www.abc.com/Scripts/function.dll? / {0 C4 d% R4 j& J
" ] i2 N1 n' P9 u! H/ s$ LISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。 : q" a4 D# }3 D6 @+ H0 Y3 ~* |
% J t/ T4 ]/ U+ L1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下: ) W( T6 f( n7 ~' k' _, `
$ h6 V+ E- W$ x' \ C' m3 JBOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
0 b5 i9 N2 c, H* Y3 \( P+ ctypedef struct _HSE_VERSION_INFO % Q' _$ i r; D0 _. P
{
4 f' T/ E. ~5 h8 f, U; zDWORD dwExtensionVersion; //版本号
; f! l4 S k" d1 W5 k ~3 lCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
* u" Y& o) T$ }3 S P} HSE_VERSION_INFO, *LPHSE_VERSION_INFO; / d! \5 C m5 n; L0 Y6 Q( Q( ?
( i% T9 s9 l+ x, D; E" }- @2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下: - C2 h2 X7 h; d. R% @
+ D: v: [! k2 o/ ODWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB ); 2 n5 x0 T2 E$ W+ T
% W+ J8 P# F* k" t# k- HECB的结构定义如下(IN表示入口参数,OUT表示出口参数): 4 p9 i. [. Q) g) ^" R* ~# R
! L/ b0 |* B$ f( ]
typedef struct _EXTENSION_CONTROL_BLOCK 1 `$ m% Y( j5 p, v3 V
{
0 j& |9 p/ v. k& V" p8 SDWORD cbSize; //IN,本结构的大小,只读 4 r7 T! P; ?; Q3 g
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
- X# d7 _ \$ Z# vHCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值
: b( I7 M& f! m* HDWORD dwHttpStatusCode; //OUT,当前完成的事务状态
1 Y7 B- ?1 T1 V7 kCHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
6 t7 ?' ?3 n" L% f1 BLPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD
3 U5 t4 S0 L) u/ o- LLPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING
+ {' v9 }, V5 A( RLPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
$ `7 G D: Z0 c, sLPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
% y" k8 T7 d# s1 R2 R' NDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH
1 V- {3 P3 n. ^+ u0 KDWORD cbAvailable; //IN,缓冲区中的可用字节数
' o! E) C5 M6 E! W! |" W. y3 ALPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
; J3 `' j8 r1 O2 R" o5 ELPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE 0 C; D1 W% D/ ?1 z( S
' w& _ C7 a/ c- n+ Q//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
, ^3 H) p/ U$ X0 a4 u+ PBOOL ( WINAPI * GetServerVariable ) h$ a3 a' N# s) y7 {
( HCONN hConn, 1 ]9 D, g- B# D9 X, K- i3 w
LPSTR lpszVariableName, 3 m& \* B, ? B/ [
LPVOID lpvBuffer,
7 Y. v2 c' H! a3 o; o7 u- qLPDWORD lpdwSize );
E# n: h4 l( f8 G8 t' X8 {9 E# }1 q& i2 U! J( {4 A1 V& w, g
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据
/ x- G9 E$ M6 J5 n* [# r; ?+ W( HCONN ConnID,
- _8 C3 Y; I6 q' O- b: x6 K/ rLPVOID Buffer, 0 Q& f! X3 }; s% N. U8 i
LPDWORD lpdwBytes, 9 G' b' ^; N; Q4 l; X# G1 C
DWORD dwReserved ); $ n5 ~& D* ~4 H" k5 n/ ~4 m
- A/ f0 [# M. n4 t/ n. P/ t3 {& Z: a
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据 0 R% b3 J* X9 W8 Y/ ^' {4 Y- z
( HCONN ConnID,
' V: ]. d2 U7 l/ ILPVOID lpvBuffer,
4 ~" C) A9 }3 L8 T+ a- MLPDWORD lpdwSize ); 5 p8 M1 f7 n* F" I
4 [% n9 ]7 G" ^BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能 , ^( Z- G; L% _$ ^9 X0 S
( HCONN hConn, 0 K- y, V* j, F' I( N# ?( K2 e
DWORD dwHSERRequest, , i- U7 t$ h O+ e f
LPVOID lpvBuffer,
# ?1 Y: w& A$ A5 I3 P' OLPDWORD lpdwSize,
3 R$ Y9 E- K4 C1 {9 g' j9 z/ K/ qLPDWORD lpdwDataType ); 5 g" G2 [4 o" A; Z- e. ?
" J4 L5 _6 U7 w) m: s} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK; ) i0 U5 p9 q4 ]. T; o
6 U" _3 i: a: a. w! [! ^在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。
! L; u6 g/ w& q. d" h# E- n2 R. O- v: U# n7 c, V! }+ w
三、ISAPI Filter 9 X% g" ]) \0 v1 z' f5 z
4 {. q8 X2 o9 X: @/ p; }ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。 1 p& B7 |6 f# a! a* s4 V& B+ l: Y: e
- ?1 D" {- n- N' `, \% \+ lISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
; n. K8 S4 h Y1 q4 ~' g+ ^ISAPI Filter都必须引出这两个函数以供服务器调用。 - x4 b* r5 I, P. l% E4 m: F9 I& L' h
. U5 I7 c6 ^6 `$ p/ ]6 M; ?
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
7 Z5 e R0 e6 u# ]# d) mFilter的文件名并加载它们。
' k0 V/ m$ ~' v& [8 p- j5 B4 x3 f$ l. H# Q& b! @1 O8 J; n
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
8 B0 Q+ [1 v, H0 J3 @8 p/ U6 W" C' d
+ I1 s+ u+ s& c6 R2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
8 v9 x, k0 i) e0 a7 D4 O6 [% L4 N, o& h9 q+ ^
BOOL WINAPI GetFilterVersion( ! J5 V8 u+ ~2 d
DWORD dwServerFilterVersion; //IN,服务器使用的版本规范
0 E& H) H$ [# V0 pDWORD dwFilterVersion; //OUT,过滤器使用的版本规范 1 o8 w) H; G% W( Y) v
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
0 N) E7 Q' K! o8 ODWORD dwFlags //OUT,事件和优先级标志
' H# e* k2 b8 \; k: S); 5 U: z* W3 {. Q& t$ S* M
5 \, i. R5 K7 L8 r: V
事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。 6 T1 J% M: ], W$ ~' ~0 |
! D; V: l7 S+ _) Q* A+ [3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。 # s# _) N: x- W* W
( o. V s7 x2 z, ?' u8 t: _
typedef struct _HTTP_FILTER_CONTEXT + c: Z: P& g$ h6 U+ R4 b
{ / A" E8 ]6 v( x" B5 x+ B; S# Q h
0 T& b/ f w7 a' f- KDWORD cbSize; //IN,本参数块的大小
4 i, W# b: \* M/ R1 ~DWORD Revision; //IN
6 J* C& `; u! GPVOID ServerContext; //IN,由server使用本参数 / r i m+ V3 Y+ N; s/ Q
DWORD ulReserved; //IN,由server使用本参数
- I" n8 M! N/ U6 W5 l# gBOOL fIsSecurePort; //IN,事件是否发生在安全端口上
4 N6 n9 ]$ ?- ?4 E6 NPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文 ; j/ c( b- U+ r) x
$ R; m/ Q4 j r$ X1 O//回调函数,取得关于服务器和本次连接的信息 7 ~$ E' Y) {7 r+ L1 J
BOOL (WINAPI * GetServerVariable) (
+ p5 S$ @& K6 I4 F4 G, ]; R& ostruct _HTTP_FILTER_CONTEXT * pfc,
) i5 A% S- J- i, ]9 j, Q9 GLPSTR lpszVariableName,
9 s8 ?) s; z! e; P7 s4 G4 E, s3 d2 dLPVOID lpvBuffer, 9 ]) j3 N6 ~5 \, f# Q
LPDWORD lpdwSize
0 F9 g% m5 t$ {0 _) z: J); $ `* {* z- x6 b
3 O+ o! a( l. \5 E, q/ SBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头 - @9 L! s0 [- I4 f* I
struct _HTTP_FILTER_CONTEXT * pfc, # P4 R# z d' G; o$ E- s
LPSTR lpszHeaders, O8 j( I- y3 X/ ~
DWORD dwReserved , g! r; v/ q- ]$ z
);
5 L' j y/ j% g3 Z- C/ m( q5 O* u* F. j1 {$ P6 B, e. a5 w
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
; E" ~" B& \0 d. L% U1 M" pstruct _HTTP_FILTER_CONTEXT * pfc,
& D* }7 h, w% |. M3 u8 h4 `: b4 P9 ULPVOID Buffer, . F4 L u/ j1 E% r7 \% f
LPDWORD lpdwBytes, , R5 Z C M; C! z' D
DWORD dwReserved
4 _, D) m9 u4 h! q% C);
8 U( c4 D: M& ]: C: A% F1 J h1 u- e& g+ p- ^" [
VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
4 v+ | l* K: E8 @struct _HTTP_FILTER_CONTEXT * pfc,
% X" V9 L5 q$ e! e7 IDWORD cbSize,
: x" P* y3 g5 x3 R6 A) nDWORD dwReserved # N' D; h3 u" ?- Z, I% }
); , m" y" O8 L$ t* B* F
2 ?5 \5 E* |2 iBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能
/ }% t" G) |! V7 K1 r) K" X6 gstruct _HTTP_FILTER_CONTEXT * pfc, : h6 N5 ~: V% [% c
enum SF_REQ_TYPE sfReq,
+ I7 W- p2 A" }; T, t; ?2 r- o% SPVOID pData, 9 ^& \6 H- q2 ~$ p
DWORD ul1, 2 O& G ~% k* V0 v7 W
DWORD ul2 * U9 f: \6 }3 I3 a
); 2 o& H3 e1 H9 g% S0 q
. C) q$ x7 N7 k1 G) Z} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;
. z7 L1 {5 K/ P7 z1 F" w! c% D/ K. Q, k: H# u
四、VC++ 6.0中对ISAPI的支持
1 v/ W; M" L p ` S6 K) A% r" Z$ y
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实例,每个
% H8 F1 b5 v6 l( Z5 k( rCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
0 L2 A* X! _) Q. F* y5 S
; ]) B9 f, z% r. ]4 [) M, [ ^9 L' j一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。 8 |* V6 @- W i0 L* b% w
( m2 U/ L8 b. k/ [8 w4 F. u8 J) ^" N+ p. d
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为例,该例中有下面这样一个表单: 2 ~( P% A2 A: I. v. M% u. {
2 k+ C6 k6 V3 q7 `& C1 T6 d<form method=get action="pinball.dll?"> 9 P: B% G8 h& A6 |. z8 ]
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
$ [+ o, v* ]* v<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br> ) d2 h1 H: ?, N3 y4 b
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>
' l, } V r! a6 G<input type="radio" name="Favorite" value="3"> The Addams Family<br>
: ?$ ^* m$ h' J6 A; V$ i% _0 Z<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
, C3 j# \1 `9 p8 b* j4 g<input type="radio" name="Favorite" value="0"> I don't see it here<br>
) T2 h) k% x% {9 D: L7 }<br> $ T' o! U# k. `; O) u5 o5 [
<input type="submit" value="Show Me!">
5 q* f- }; Z" R. N9 m: v( r</form> + {+ _8 k1 {% }8 Z i' [0 l9 n' ?
2 r; D8 i. O" D+ D$ _& q
当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端
2 d& }$ {* C, j+ _4 C b最终将得到如下的URL串: ! h$ _- d* D5 N8 d8 `
' c& O0 }5 n6 V7 ehttp://www.abc.com/pinball.dll?M ... mage&Favorite=1
+ d% W. Q/ I% h- F/ K9 X- j1 A) U) ? a/ F7 [1 K
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数 5 }) |; l' V, x, k7 X
将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite: " K2 p* D; r2 S$ S' d3 O
9 S. L g' b9 I; L& T
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
! {& J8 d7 g' Z- j9 T ?1 K5 Y& l& y7 a! m; a ]' ~
而parse map需要按照下面的形式定义: 7 o' H9 C* r; A# U) N6 F
- o! S$ g6 F8 b- ?& d
//CPinballExtension从CHttpServer派生而来 ) B* z K9 W# d+ v0 b3 z( F
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
( q1 V& M: Y/ C3 R! ]' W3 J
3 t7 c3 d% q5 N S. ^. y//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice
( c/ F7 G: O& S& g! j, r, q9 G! zON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
! E1 e8 H+ U% k1 ?7 Y e \, y* M
& J1 ?& I! Q: G( _6 _# V$ ^ S//该参数在URL中的名字为Favorite
6 g' \, U5 [0 I* V1 d. j# Q# C- ^ON_PARSE_COMMAND_PARAMS("Favorite")
) Y$ u2 Z h4 u* q6 G. w# V& m$ }! b# U) W, K6 N) Z9 o" @
END_PARSE_MAP(CPinballExtension)
/ N e8 v5 R( b. x( C1 x
+ ]0 z7 B/ K5 t* @' M6 p而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件: $ H) @$ i' L. k
a+ u7 H M) x p, f
OnPreprocHeaders ! n" n; h$ p/ r; {' b
OnAuthentication . [3 ]6 ~! E$ ~8 n* ~
OnUrlMap
" v# Z T; m3 K3 ^6 |) }OnSendRawData
8 f9 Y1 }/ }% n7 k- YOnReadRawData
6 O" Q" K; y$ I* y8 qOnLog 7 h/ ^& X5 U1 b. J k
OnEndOfNetSession , E% S* p7 ~1 @% |* c. R' e1 H, P. }
8 `- \$ h4 m: @* I" z" x
MSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。 & j! d2 }: e# M, H+ q
" k$ \" m$ B$ k( u( @; |7 Q' t. E( f& W参考资料: p1 c# x0 v+ E! G8 Q! G
: _! ^. U% k& r
1、MSDN
* F' x6 R+ @* X4 j6 t% E! ^4 o2、《精通CGI编程》,丁一强等,清华大学出版社 |
|