|
|
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。 $ f3 j/ g; s! G y7 t0 F2 m
' P1 H. P( @! L# m) a+ g一、ISAPI接口和CGI接口的不同。
! z& s, o5 C' I3 h: t( \4 {4 \+ U
7 i$ e( H5 F# |) A0 ^+ ~ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。 * l7 O. y7 Q" l% a3 }# P
8 g- A$ B# U( z1 i6 K- O$ x% u, [2 Z& ?
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。
0 |4 m) }) u" }0 r) f# j2 `# v [: x5 L$ P1 n* ?3 z0 p1 Y
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。 / x1 e2 Z2 s) E9 c) y& l! d, ?
* Z! P! q9 e5 m: d* S4 F& K: b
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。 & `7 x1 T! O, i8 V0 d1 H
/ o$ k1 G# Z% h* @
1 z, ^4 o( G) s1 d2 o v! `: @二、ISA
' t- G: M' x. P& H z; O6 Q! H7 [) d# g
ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):
2 ?) N9 Q O B9 g/ s9 T, v/ xhttp://www.abc.com/Scripts/function.dll?
2 N" ~, W8 O! N9 ~. f. w
! _4 P8 r3 h+ d B, G3 k6 ^; T8 tISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。 8 ?" E0 F$ M4 g8 J
9 S: N* f5 n9 `2 c7 h* N- O; M2 `
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
- Z6 l* E" x3 r0 S
, O6 c; C+ _' d' j! E# x& J* [BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version); 1 h5 ?" t- y8 w# K
typedef struct _HSE_VERSION_INFO , X, e( N |; j+ V7 }
{
5 F b! H( x; l/ gDWORD dwExtensionVersion; //版本号
/ S5 M! A+ q# a9 z+ mCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
# ~3 }! s8 ~0 Z1 ^' \} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
; |* r+ h- H! z' v3 |" X6 w' R6 `5 k% @3 @
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:
( k" ~( a9 n) b Y. m
" f6 L0 v. }# K4 d! KDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
0 A% v: c7 ]! ^/ K' K
* J( y* n: D3 \8 ?: W, m: _& x+ M) ZECB的结构定义如下(IN表示入口参数,OUT表示出口参数): " b2 Q8 m$ ~" w' F' f* d3 Z+ Y
7 U# [: y# t0 m! f# k
typedef struct _EXTENSION_CONTROL_BLOCK
+ R$ D3 }+ h1 `{
# f9 g# z& y( G z* T% D S, _DWORD cbSize; //IN,本结构的大小,只读
, c6 u$ K- T5 P0 ?DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号 " C, g0 _! F- J( K' Y. `
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值 ' [9 @: G* D; g* u1 l" }
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态
& ~' ~3 o# Q& x& Z$ O t o5 CCHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容 . S8 r$ D; b- s8 V4 p
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD
3 [% [3 Y# f! Z: D+ ?3 {3 tLPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING 8 n; H6 i" I- M3 B# o
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO " _9 q! V# d5 z( ?$ p7 k* K
LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
, p$ R, J d% D% u6 r: Y$ [* mDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH
/ x5 a) H1 ^0 {) @6 ]/ O' Z* t+ tDWORD cbAvailable; //IN,缓冲区中的可用字节数 3 Q- O: S* ?, R; ]- l7 F
LPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
& h( F: b' l, S0 `6 z+ oLPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE
( l; [9 A" b t: E& Y
1 k! m* D. b1 o2 ?0 \//回调函数,用于返回服务器的连接信息或特定的服务器详细情况 Y z8 h) B: ]5 O" u
BOOL ( WINAPI * GetServerVariable ) 8 L! c9 O6 s. b+ p
( HCONN hConn, 5 s; A* W3 R% v2 e" G0 X
LPSTR lpszVariableName, ( j: u S. T, ?, o+ p, C9 B
LPVOID lpvBuffer,
7 G6 }0 z P. u" ^# _LPDWORD lpdwSize );
" s$ F( H( c# k: y
+ W3 v3 n/ p N- MBOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据
, N0 x7 k, |/ T! z9 w. V& H# s( HCONN ConnID,
4 C5 D) g# r! N$ y& f0 ILPVOID Buffer,
0 V- Q/ s: I3 D) P1 }$ JLPDWORD lpdwBytes,
7 u& j9 m2 n1 Z' ?& F" {! _DWORD dwReserved ); * h! @( H. h, M- C2 Y* k; R
1 m+ P( L7 B* T2 J e# N& K2 C0 n+ G$ [BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据
! x% i% p1 _9 o0 E' X1 Q+ c( HCONN ConnID, * Z( m( \; X# w$ f. e
LPVOID lpvBuffer, % f( D" O4 q! ^; O! o6 B4 P+ e" e3 n
LPDWORD lpdwSize ); ) j7 V. D9 F* _3 T$ ~
$ E0 P; D$ R( E# ?" D8 z
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能 ; U. O. C4 y+ z, F6 E7 y7 |
( HCONN hConn, 7 a# n9 u3 R8 {; G
DWORD dwHSERRequest,
8 j4 h4 [& t5 ?6 R( yLPVOID lpvBuffer,
, @4 n& R4 J" d' ?0 vLPDWORD lpdwSize,
0 n2 f! N! {" k5 c( f$ [, rLPDWORD lpdwDataType ); $ f# i" S/ O7 A# Q, u* j0 L
, v7 P( E3 G, i+ Z+ V, v} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
( N5 I i4 R8 B( d+ V+ S$ s8 [9 B( r& O
在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。 ) ]! _2 N' J0 A ~, M
! E# ~' l5 X3 c3 T+ v三、ISAPI Filter . R4 ]$ S0 E7 E- | J8 c
7 ] Y) _, z' F2 P5 b$ jISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。 ) P, l; v! z* G
- b+ b" d6 h9 k, tISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
+ \* W8 q- l& d+ K2 sISAPI Filter都必须引出这两个函数以供服务器调用。
, W# d3 U7 V7 O* Z8 s
" K2 g0 ]& c: H( M# g( z1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得 ; K9 {" R1 a6 l2 H0 C5 d/ x
Filter的文件名并加载它们。 . t8 y# B0 f& U. y9 v9 i
8 j6 `3 y5 p- V9 B0 `
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL . c1 _% ^' r0 l) w$ B9 B
( {5 I" q6 b! C
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
, M9 V& F% ?/ m; j
; p' ]% V. D; z- D C# A0 u3 Z! \% BBOOL WINAPI GetFilterVersion( ) [7 n! D2 W$ n6 _6 j
DWORD dwServerFilterVersion; //IN,服务器使用的版本规范
1 p# M7 V( n- w/ a9 KDWORD dwFilterVersion; //OUT,过滤器使用的版本规范 g2 L$ g9 ~& ?( |4 \
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串 4 g4 v- r- S) a$ k, H$ ?
DWORD dwFlags //OUT,事件和优先级标志 / ]' E% N# ^9 D
); : r. g/ h( V! H* B) d7 i& N
2 `% a& e0 @; q. d3 r事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。 6 [: d2 _6 J: X1 X2 V! w
; S0 [$ y) T2 Q, d. n3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。 9 k' N9 C) x4 w( k+ l# J& L
" X3 }2 o: A) N ?) U# }
typedef struct _HTTP_FILTER_CONTEXT 2 I* ]5 B8 {. H2 m6 N
{ % _/ d6 A, ^0 \9 ?3 a4 q; h
* a* D7 i8 s4 x
DWORD cbSize; //IN,本参数块的大小
) h% |% N7 K- G. V, j5 MDWORD Revision; //IN 3 O1 g& b6 q! V+ \* p
PVOID ServerContext; //IN,由server使用本参数 ! M3 G3 X( w+ h' s
DWORD ulReserved; //IN,由server使用本参数 ' u9 J. _2 S; ]7 X' L& C7 ^0 Q. H
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上
/ e+ i& [0 K+ z& G& tPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文
3 O% ]% k) c& n' d9 G) I7 y1 q: f9 `6 U9 t
//回调函数,取得关于服务器和本次连接的信息
5 T v& s1 g. l2 I5 d9 S# b( lBOOL (WINAPI * GetServerVariable) (
: n5 f6 w0 a6 w8 F* h) \3 N$ H$ Dstruct _HTTP_FILTER_CONTEXT * pfc,
. o) [$ D! i- M& ~1 A4 bLPSTR lpszVariableName,
/ V. ]: q/ U% m8 C. aLPVOID lpvBuffer, ! _0 q6 T# x$ H+ K% d, E
LPDWORD lpdwSize
( O ~ y4 S1 p- M. ^); m- f. K- |$ H' x$ J) _2 z
& K; p9 ~& @+ j4 I' fBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头
; n) D' r& y I p+ x+ e5 Astruct _HTTP_FILTER_CONTEXT * pfc,
0 x. X' l" q; l7 {; I! PLPSTR lpszHeaders, , _( X: o5 `+ ^6 y
DWORD dwReserved % I, V1 n5 n" J2 z
);
! E1 g- Y0 y; d0 q4 q1 K& P8 j, X7 L: K. _+ Z6 p+ F
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端 ' M+ u8 M$ c9 I- H, o
struct _HTTP_FILTER_CONTEXT * pfc, 0 V5 O( Q% n# v3 V$ g4 |
LPVOID Buffer, 5 q s6 g" D' ]3 w2 o. j4 g1 X9 N4 u
LPDWORD lpdwBytes, ) Q6 }- t- `* U1 H- C
DWORD dwReserved
6 {/ a0 l; D9 V9 J3 R, c); . O9 ^8 w9 v) K, E2 a
) x: ]! S/ F S( u9 d1 s0 W; bVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。 5 `8 ^) w7 s3 u) k4 Y- s5 ?
struct _HTTP_FILTER_CONTEXT * pfc,
# n- ?3 _' y# X$ K T8 w! T. JDWORD cbSize, - k: g- y/ F$ ?4 A5 S
DWORD dwReserved
( }- I1 }; r' C5 |);
( M8 j! H( i8 I$ ? T; p: s
2 z9 e8 D: C# oBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能 1 V. W3 f" H3 u6 N, O$ a
struct _HTTP_FILTER_CONTEXT * pfc,
" w3 X+ b+ \ }; o4 g* o7 p2 ~enum SF_REQ_TYPE sfReq,
; X' a) o, U: `, [PVOID pData, 9 W3 x6 ?* }3 w. F& n3 {
DWORD ul1, 4 y! u8 R; K6 b% l
DWORD ul2 - w) R9 C6 q' v7 M+ }, ~
); 2 e; P) G$ z* P5 i/ ~
) B8 Q+ ^- g6 u6 V6 G; B5 V' G} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT; $ T9 ]6 L4 u3 \! Y' w" W: D
3 U4 t; }$ m7 m! j' P: w+ s7 P四、VC++ 6.0中对ISAPI的支持
9 a: E! U1 N0 S* |, o! K- d2 [: p" C H" V7 F/ }; K
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实例,每个 . b/ m" h6 U1 @& @+ B: w: Z3 W4 G
CHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
6 X2 [! n3 ]& Z: s. C* c4 S1 i7 ]0 Q& T+ F& r8 {5 J# V! }
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
^* ~( W+ {8 a1 m P" o
6 u9 @( ]+ W& b; V2 w8 V- e1 gParse 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为例,该例中有下面这样一个表单: , x7 o& X* ~/ ?" n2 ?; O3 \
# X( C, ~% f/ M' S: j. b% D1 {! G8 K
<form method=get action="pinball.dll?">
. X* W4 b2 u3 b7 K% a( f3 Q w<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
) Z; ^7 n( _1 f' J: B: u<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br> . q- o. c, p4 ?/ N
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>
+ G# } j7 X; U8 R<input type="radio" name="Favorite" value="3"> The Addams Family<br>
: ]4 Y; w+ z9 J: ~7 d( S( _0 H' C<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
_1 m s$ F6 x; r3 Y<input type="radio" name="Favorite" value="0"> I don't see it here<br> i* |& l# b; p9 [$ V
<br>
9 m; a1 B* N9 t# _7 g) ^<input type="submit" value="Show Me!">
" l% T* S" d1 z# g! d</form>
N- j* C, i( J' G9 w- i+ a- v- O- W2 ~9 f) Z
当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端
8 O7 r" p @2 v& k( M最终将得到如下的URL串:
+ x! y: {* z9 p) Z: W9 G- I8 _8 n T
http://www.abc.com/pinball.dll?M ... mage&Favorite=1 - ~7 q: z1 K( T8 Z8 U& O- ]" d* m: a
+ `2 z H" V, A3 I! [* \! Z1 d在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
, P' V3 L: X- \6 x Z+ F8 [! F$ j6 N将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite: ) S5 n# ?8 |7 S u* x/ x
$ H- W8 F2 }: a
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
9 i1 V( R" U7 ?9 O
r/ o# P1 t6 l) e9 Y" b0 V" C/ V而parse map需要按照下面的形式定义: : k+ ?' @9 c) P, Q5 d1 U* I+ w/ u5 Z
) E, @% h8 w* Q9 y//CPinballExtension从CHttpServer派生而来
8 ?% f% s1 G0 ABEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
0 E1 b$ d1 c/ P
3 C3 b$ H- T: \& J//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice 6 V; G7 W" M9 p% j3 \1 ^
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) 2 q4 D# h: T2 l4 n
/ u; U( q1 {3 E. Q$ p' U
//该参数在URL中的名字为Favorite
6 z$ ]; D' h- m! Q, uON_PARSE_COMMAND_PARAMS("Favorite")
% ~ a' D. R1 A# n5 T) R9 ?+ S8 q" r: |$ r3 l) Y Z& j. n Z
END_PARSE_MAP(CPinballExtension) % i. J; }7 x6 X3 j. S
3 Y' w' a( a U" N1 x( h+ i- `- q( C
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:
1 ~1 f5 c2 p; g0 ~8 c. S$ }( R
8 h: t* t& g* |; X; fOnPreprocHeaders
; k. e( r/ }; b; L" f8 COnAuthentication
% i% \5 h4 @' OOnUrlMap ) n% |" Y0 O1 W! p/ X2 |3 j4 {
OnSendRawData t5 a% x8 ]- g6 k# q
OnReadRawData . k$ }$ \% L; t' K9 G1 A& {
OnLog $ |' e# b+ U& P4 l3 e/ i; C
OnEndOfNetSession
! p# S q* J: l8 a3 T
3 c% v! o; s, eMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
( H+ E- \2 M2 a6 a2 z* P# q
( r% e! {- d* i参考资料: 4 @. g" ~" [& A# C- r9 f
8 q* l/ n% s% I. l* s1、MSDN 3 {- T f. _, C) m: X* v7 K ?* a. u
2、《精通CGI编程》,丁一强等,清华大学出版社 |
|