|
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。
. q7 S. ^( e7 h( O7 [& X! \: ~1 \; p2 ?8 H3 Y5 z& q _
一、ISAPI接口和CGI接口的不同。 - x# i8 j5 H. B" Y3 V
: Q' f& I7 |! N' t5 |7 B4 A8 hISAPI程序和CGI程序完成类似的功能,但是实现方法不同。 ! E, k! H- t: \, l# f9 X/ e) Z9 T
$ G# s' N3 _1 M7 i' r. @' F. ^
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。 5 H/ J3 r9 q b1 ~
9 C$ y) b2 U+ y! b& Y( C- U: K
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。 " D* U: l4 }" ?! f
; K/ a3 g5 n* n8 `- T- `
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。 + [0 y$ Q6 b$ v: X+ o: r% n* k/ W2 i1 L
( H4 T- K$ e: s) j. r. A+ ]* J/ j
5 q2 u5 u4 U7 C5 c' e二、ISA 9 N6 Z* t% p; J! [. i" D$ Q
& ?! t1 E; Z3 _- G ], n, `
ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下): - L+ E( K5 V9 X7 \2 w* T
http://www.abc.com/Scripts/function.dll?
" E) E7 S2 Q f* Y8 B; @- k" }1 x8 f( X7 i6 b# o
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。 + ?8 |( c9 t4 n
9 q8 T( q" k4 R9 ~( G4 \3 d1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
; y) P4 |9 [% `8 e+ u+ q7 q& e- C% l. q& @) a R/ h
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version); 6 I0 y# c6 o3 T7 c/ R
typedef struct _HSE_VERSION_INFO
1 d+ B8 d# J" c: W- }- n{
# [( D: K+ a; \! z$ J) UDWORD dwExtensionVersion; //版本号 0 y5 |* K- a8 y! \/ @9 d) y" v
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串 ( }; }+ O; {: A) e2 y( z
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO; ! s) e. v9 [3 C. I9 y4 G) k2 d3 Y
$ u3 k4 J/ O X" {) Q6 F2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:
* ^0 \* G q# [3 z- J+ h( U- | I6 k0 G: P5 t. M4 E
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
# e) ?5 b! W& G2 T0 I
7 N$ w' c# I) M) n O' WECB的结构定义如下(IN表示入口参数,OUT表示出口参数):
( @6 P& K- R8 ?3 L1 L2 \/ N1 f
7 [ [* D' `! W% b. f# {7 ctypedef struct _EXTENSION_CONTROL_BLOCK 6 h! S9 s0 q( i1 F
{
) F1 l/ n! `; G$ U7 R% eDWORD cbSize; //IN,本结构的大小,只读 8 E) w- v+ R. T( s5 @+ K7 L, p
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号 ( D0 ~# `" b# d0 i3 j: a3 I
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值
" l/ l6 H* |& q/ H) y3 v# kDWORD dwHttpStatusCode; //OUT,当前完成的事务状态
1 Y7 j3 ~6 P8 }, ?- L# y6 G7 UCHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容 , J/ E( h# `* `$ m7 }% d& D
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD % Y. S. u3 w" U6 z U4 T
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING , [2 [2 M. h7 @8 ]5 H$ a% [& `
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
6 S# H. E* f2 l# I" qLPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
: `9 d' U3 F. S5 C! O4 n, IDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH $ @5 S* \5 L+ |3 x
DWORD cbAvailable; //IN,缓冲区中的可用字节数
! M2 P ?# v: G4 G* jLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据 - d: B5 ^* z$ \3 w# \
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE ' @$ g, e4 q& Q' t
' j4 s6 D0 y6 q7 S" d+ H s B) p//回调函数,用于返回服务器的连接信息或特定的服务器详细情况 ( d0 |; T3 d: \9 U7 ^# g5 O, t
BOOL ( WINAPI * GetServerVariable ) $ ]% D' v4 ?1 {0 ~# A# R
( HCONN hConn,
" ^- ~8 ]" o9 F6 z0 ULPSTR lpszVariableName,
: j! x* p* X& u' C4 D+ l6 C0 `% t3 VLPVOID lpvBuffer,
" @" K% |. J9 A# \# @- P5 jLPDWORD lpdwSize ); - y9 a9 i* Y% T$ K. q0 u+ ^# T' t
) Y; W2 C% O+ n8 k. c& fBOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据
1 N- \+ S3 t4 y; Q' I% C* _( HCONN ConnID,
) }( } c) J; E& wLPVOID Buffer, 4 F0 N+ k; c l" Y* U8 K
LPDWORD lpdwBytes, # M& r8 Z7 p* E
DWORD dwReserved ); - [" Y' D/ o9 }$ j5 K* ?3 E$ F
! I n7 E' J/ m. M8 Q; c$ W } ]BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据
8 U6 e& T9 s& W( HCONN ConnID, " p* o& t$ u* d6 R: W* s q) p, U2 q& R
LPVOID lpvBuffer,
! q3 R+ e. u8 ?2 N6 kLPDWORD lpdwSize );
1 L. u- d! x/ U) B0 n6 f% C# i5 m7 L
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能 ' S ]( l5 s3 q5 x5 q. T
( HCONN hConn, 2 P3 ]2 W5 A0 b6 W
DWORD dwHSERRequest, 9 F( ]5 u1 P* a8 S0 C- i1 c$ ^' p
LPVOID lpvBuffer,
3 ?4 y. f1 {1 K2 N2 H/ L% wLPDWORD lpdwSize,
) p( |( ?( Q0 h' `# L7 @& DLPDWORD lpdwDataType );
7 V, R$ b' l( C4 a' c3 P+ l4 M4 }. f" Y) k7 ]7 g; R7 }5 J/ B3 ?
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK; ' C& `- q' g5 B! o
9 `9 x9 i3 _, b在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。 , ^+ f6 q9 z, |7 h, w
6 Q, {9 K9 R2 o! c u, v
三、ISAPI Filter ! d2 s* F8 R2 w L Q |
1 ?2 R' b- {& z, ~+ RISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
0 ^ Y9 Y0 {4 \
2 c. @: p9 t6 [0 EISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何 - T9 O% k0 p+ Y: `8 t
ISAPI Filter都必须引出这两个函数以供服务器调用。
J# r: @1 M9 V* q/ l) q. s: U; R9 z! o% w
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
. f$ A7 Y, p! PFilter的文件名并加载它们。
9 N$ F/ S U% Y; B* l: D% F# o |" ?7 P$ H6 t+ q8 b0 ?* ]" K
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
5 h, Q+ Y- s; a0 D6 \) h0 \) ] P# w- A k: w3 m+ J$ ]0 r5 w
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
8 ]9 D# h" Z; C! B7 V
" ~) [2 d4 j7 W7 g6 pBOOL WINAPI GetFilterVersion(
0 _) p; Z5 L5 c0 m, V! PDWORD dwServerFilterVersion; //IN,服务器使用的版本规范
9 M1 H. V, C6 [& F2 x' z7 ?* lDWORD dwFilterVersion; //OUT,过滤器使用的版本规范 - [7 R( ?# T$ I$ w' V
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串 . w+ l& d* A+ U% w% R7 j+ t
DWORD dwFlags //OUT,事件和优先级标志
9 S3 ~7 Q( O* ?1 A$ a/ |);
4 O! ?" ` O# n6 r: g8 C9 ~3 p) w, I2 s! D3 l, n
事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。 8 P. ^5 q* O# }
- t2 s% {- {1 _$ F1 B3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。 ! L- @) Y$ v# ~0 ~1 T! V
1 Z7 i* `' ?: p9 otypedef struct _HTTP_FILTER_CONTEXT 9 c/ S4 i6 x/ w% {
{ + o- D* b/ O% m; e# c: W
n) i J8 ]" d: A7 G5 G8 a6 XDWORD cbSize; //IN,本参数块的大小 6 N5 @$ B) U: I: k
DWORD Revision; //IN
: I: U5 H( N: c0 x, W/ CPVOID ServerContext; //IN,由server使用本参数
6 |5 R8 n+ m/ p5 O+ K7 A) D0 oDWORD ulReserved; //IN,由server使用本参数 * V) ?1 n" b$ {9 g
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上
4 }% X1 D+ J9 k/ [# u) ~PVOID pFilterContext; //IN/OUT,与本次请求相关的上下文 + d% z) O! n7 ?6 _: r+ f$ i
6 F& b6 Z$ f8 B M* V* i2 W& O" |//回调函数,取得关于服务器和本次连接的信息 . L- l- b* S. ^5 u; t- h: C& {
BOOL (WINAPI * GetServerVariable) ( $ P. o7 d7 ]8 |0 y* j
struct _HTTP_FILTER_CONTEXT * pfc,
8 ^% J! _+ L; `( H* R& k% x0 zLPSTR lpszVariableName, & Z! g+ G6 |5 a- `1 {
LPVOID lpvBuffer,
( J2 G* `% ?+ pLPDWORD lpdwSize
' n2 Y7 W: U8 D6 X+ Z); 2 @9 k3 T& W k* T1 o
6 }3 W4 Z% m2 [* l! m2 f+ b, c# a5 |
BOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头
z; I9 x- n3 \9 Pstruct _HTTP_FILTER_CONTEXT * pfc, % R. g2 v: c7 H" w, m
LPSTR lpszHeaders,
* N8 d$ m2 u' _: v8 gDWORD dwReserved
$ r; g5 _, Y. a( B* b);
4 K4 p: {0 V. D: L: t# h' j2 i4 r+ i0 C L( D* e
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
- J; x) @% F- [, dstruct _HTTP_FILTER_CONTEXT * pfc, 1 c, }4 ~5 w. I, r
LPVOID Buffer,
; ^* ^5 p4 [+ p8 @LPDWORD lpdwBytes, 5 z$ O: h7 {) P# J* s1 N$ Q. m
DWORD dwReserved
/ @2 h L7 L: ^); 5 o2 A. j ]$ Y5 x
; Z8 Y0 C. k" i4 J3 U5 T8 {' u$ c+ fVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。 6 f |( [1 ?3 N; Q
struct _HTTP_FILTER_CONTEXT * pfc,
) D% J# Z' L6 C) s& B( QDWORD cbSize,
( t- L" v( {' z, ?/ ]6 h8 T ODWORD dwReserved 1 }* _/ i0 n7 b$ [, i' k9 k$ h
);
( z$ Q9 s( p& E3 s& Y
! L4 G, ~( ^, @: _1 HBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能
( K$ [' ^, o* M6 m) G4 N \- H! C! vstruct _HTTP_FILTER_CONTEXT * pfc, 0 A- V9 |* k: ^
enum SF_REQ_TYPE sfReq,
8 p; `; }$ j% n: X( w* B" H) YPVOID pData,
; A& R9 p: m. T. d# KDWORD ul1, 7 V$ H! O" C k
DWORD ul2
, F0 q" F+ A' c( z$ F. ?( u8 e); + k9 |) b9 `% k' H- O7 g
1 s5 [# p! x; _& E$ }4 @; z} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT; / a7 n Q& P. }/ ]
7 }1 y( d2 K( g4 A; Q
四、VC++ 6.0中对ISAPI的支持 7 V0 J$ F9 U9 S& o" }
4 x$ ? {- d& n9 n; i) EVC++ 6.0中定义了5个相关的类以简化ISAPI的编程工作:CHttpServer、CHttpServerContext、CHttpFilter、CHttpFilterContext、CHtmlStream,这5个类都没有父类。其中CHttpServer和CHttpServerContext主要用来编写ISA,CHttpFilter和CHttpFilterContext则用来编写ISAPI Filter,而CHtmlStream则用来操作内存中的HTML文件,为其它的4个类提供服务。CHttpServer在每个ISA中只能有一个实例,一个CHttpServer可以对应多个CHttpServerContext实例,每个 & ?4 Y0 x7 ^! {6 f( f
CHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。 & l* F& b$ i! ?' ~
: _1 I* J d0 g9 m$ Y$ x7 }: O
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
, `# ^6 b" h; h
% c3 r: `% p* `4 D6 n7 m1 W kParse 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为例,该例中有下面这样一个表单:
" Y; v( e n# G$ C2 b8 W1 L6 d$ q. ~" \8 ]- V8 k3 }& T
<form method=get action="pinball.dll?"> - ]' X2 ]* e) n' h0 \3 _* e8 t
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
! [" S. S) G, T<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br> % m% O4 K2 o/ X1 G, t
<input type="radio" name="Favorite" value="2"> Twilight Zone<br> 4 |+ @% L F9 _5 ]" i0 u# z7 c9 ]
<input type="radio" name="Favorite" value="3"> The Addams Family<br>
2 N1 O2 x/ Q+ i- g<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
4 G: G5 x5 s2 h# f! q9 {7 k<input type="radio" name="Favorite" value="0"> I don't see it here<br>
- y" ?! t2 [: E<br> & y+ u9 F% W! o0 t% z
<input type="submit" value="Show Me!">
2 T. {+ r" }. }5 \) D5 g</form> ; i4 B. T# X2 g3 @: p9 O4 @
/ i, v, y/ O( q- U- s* H当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端
4 } }$ k9 _4 }最终将得到如下的URL串: ) u# a, _: w1 C% |
1 A$ ^7 f N( w$ `9 O I0 n
http://www.abc.com/pinball.dll?M ... mage&Favorite=1
, P, S X2 `: V* V3 h% j" H2 y+ p
/ \. e# Y4 f/ K# B, m: G h在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
- K! f, ~, T" i5 |5 _0 M将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite: + C% w) |" ~$ h: }. y% G3 c
/ q, C' \. v* x4 J: d
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice); + N6 u; x5 d7 ~" p2 I& C# r* E1 D
, g( t: z4 I- h" P7 m/ U6 Y/ j$ M3 P
而parse map需要按照下面的形式定义: % v+ T. @7 p( m( A7 B" ]1 x
+ i, ~/ n# O, V" E% G. Y5 w//CPinballExtension从CHttpServer派生而来
3 |$ Z5 G V/ P( Z2 nBEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
( N8 \0 i* r% C8 J& r$ o- Z; `% C# m& l9 n/ R
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice
6 X: ^0 |2 b, K# HON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) 4 T0 i) X h- g' X& L# t, l7 r
g1 i, W; L, o7 [$ l
//该参数在URL中的名字为Favorite 9 k* _6 F D9 d7 e) L' B" X- \9 r
ON_PARSE_COMMAND_PARAMS("Favorite")
* c8 v) m6 _5 V# Q7 M! q5 e4 ]7 e4 @' I }" p: }/ a9 p
END_PARSE_MAP(CPinballExtension)
" N; C4 @! A/ Y) p& \7 n W0 z9 w. A
1 K$ f- O" C2 _: Z而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件: # W' Y% I0 `* p' N/ ^: [
3 ]& P2 b2 G A7 E/ \0 R# w
OnPreprocHeaders
$ e1 K( R( `6 t2 }$ |OnAuthentication $ B% m3 ?+ p6 I: K4 u/ Y
OnUrlMap
# Z3 F C3 n! J0 R% S4 \' ^. DOnSendRawData
! |( N) r0 `( J" Q" u+ VOnReadRawData / a' E3 O5 H& ?- h( o
OnLog
( Z* Q3 U. k2 POnEndOfNetSession
: @! j( B& i* C7 H( R* `" G/ I
0 w$ Z4 e7 H9 Q, g( ?/ KMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
8 z% Z6 Z, I$ r( m# K! c1 G( B: K2 n+ a+ V6 D* \1 Q, Z% s
参考资料:
* W. P# W; D( [8 F' @+ S
4 z, F4 V3 C, g$ s9 h1、MSDN
( n7 u! N4 {8 `9 S8 ?2、《精通CGI编程》,丁一强等,清华大学出版社 |
|