|
|
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。
5 p T7 J5 Q& e* s$ N
]6 t0 @- S- d4 ^! C& Y一、ISAPI接口和CGI接口的不同。 6 K3 _! d$ v1 |0 |
8 w& y% q. g2 f2 `" J" C( tISAPI程序和CGI程序完成类似的功能,但是实现方法不同。
% @/ }4 c7 i2 \. t3 E* m0 E K
! Q. f1 {; @2 Q& L5 I0 P1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。 % }) ^/ Y5 U8 X/ \9 O# i) l
9 ?4 W& v A/ C, q2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。 : N8 _7 j* E' ` ~( J0 w/ i& ]7 G
, y+ P: S. O# u- j; v2 _7 z
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。
* Z3 O a7 b# {
* W7 h2 q h- d2 `3 U! p
9 e0 K a3 U E R( g; R二、ISA + m' r/ m4 S& T
, O4 w( W" c4 S8 J9 z" C) r
ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):
$ D' U$ I' q$ ~. x! |( g$ rhttp://www.abc.com/Scripts/function.dll?
8 K* j) h7 \0 ~2 U, g; E4 N7 U4 \5 p, }
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。 % A& W. M- X! w- Z, L! @- ~
6 _* _7 O& s7 E+ q7 @0 U( G
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下: , X' \2 ? X0 O9 b5 V" {: x
: r! i' l% a5 M+ \. A$ cBOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version); 6 q0 R6 j5 ]0 a0 a
typedef struct _HSE_VERSION_INFO ! i3 F: F+ b- o/ Q
{ , s: H, x: V! c) F
DWORD dwExtensionVersion; //版本号 $ `/ \7 }8 F! o$ b- s
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
, O3 d2 l/ M1 ^9 j* p. i} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
5 Y y+ o# z; g4 E. ]) q
2 ~4 l% Z' r: J0 ]) F2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下: " V9 ^* Y6 B! ^$ `, p, {( S3 y/ ^5 D
' o. I4 t7 i5 _# PDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
# m' I) j0 _& q* U+ \. G- d" O) t% ^) K4 }: q
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数): o6 W! D$ s+ @0 G! u" n' [$ o& ?; P
/ q4 A; G1 K5 D6 F* j' v' Itypedef struct _EXTENSION_CONTROL_BLOCK
7 D+ a$ D) o6 K) Z, v{
) l6 r7 [7 V/ O+ W2 K( yDWORD cbSize; //IN,本结构的大小,只读
7 K8 p, d# G1 b% |- {, z( g6 ODWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
# l4 q0 n7 u3 {- l" UHCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值 8 v3 X: D6 {6 ~8 N( W3 r
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态
+ D9 C1 b( Z5 G/ M& ECHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
& k" {$ k/ r$ \+ P# o$ h5 E- D, oLPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD
, P3 n# q( X8 n# N- ]LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING 5 Y# S4 D+ Q5 ~9 o, L$ l+ c: n/ h$ V
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO ( U% k4 T2 j, ~9 F. B! O
LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
6 p+ _& V) S# {3 t1 EDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH 4 j. d& U, e4 R4 J% l' K' i# v
DWORD cbAvailable; //IN,缓冲区中的可用字节数 # t3 I( @$ w) u) y, c: s% t2 S/ ]
LPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
% b0 ^8 z. {# _ [) mLPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE , V) z, w' m0 w1 m% {
$ d; h5 o Q8 e* P
//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
4 \7 e9 ~+ x! o; P0 \BOOL ( WINAPI * GetServerVariable )
* ^: z% w7 e3 I% f1 k( HCONN hConn, 7 N. K/ s, p1 Q7 k% D* O3 O# x
LPSTR lpszVariableName, ! b& w B0 f, V
LPVOID lpvBuffer,
4 x. A* u5 b/ l1 aLPDWORD lpdwSize ); + n1 W, k, f: R2 |, u" d$ T
( w, {- i: ~! ?4 m$ f* t
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据
9 ?! f; v( N6 l* m# B/ V( HCONN ConnID,
$ k# P0 m9 `2 r9 j2 G/ r0 V; GLPVOID Buffer, $ s0 w3 o- X# I# O7 A! |' ?( h4 r# f( }
LPDWORD lpdwBytes,
: e: R! s: F) Z' J6 B, FDWORD dwReserved ); 7 N" O" e- }1 F* G" B
& k: ~: q7 f! y' H3 N) C% g
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据
* O; B; U# x7 a' ?1 W! ` N1 n( HCONN ConnID,
, B7 ]9 o' B4 m8 S& \3 Q" eLPVOID lpvBuffer, & ?/ x! C0 b1 k
LPDWORD lpdwSize ); 9 O- K( R& M9 o5 J& g! H2 Q
* I* B" s! f) {# L5 v# `BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能
4 [+ b$ B: s% U; {; t( HCONN hConn,
1 a _; N6 N; Z4 l2 k: BDWORD dwHSERRequest, 7 d5 k) ]. K/ }: h9 W5 H
LPVOID lpvBuffer, , Y9 l( \7 h) B. q, i" P
LPDWORD lpdwSize, # c* I5 Y6 G, E! H& V2 [9 ]
LPDWORD lpdwDataType );
, P; y: {5 S% s( P+ r3 V8 _5 J/ B5 q, L
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
5 r/ j" \7 X' n/ V; L& \# i" g
. q5 C+ R7 `" L% T" k/ m* L在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。
! N) F6 N" f( ^4 ^9 u1 ^, k
' S% b+ h, Y) B" L: T* ^6 Q- o三、ISAPI Filter 1 H- n( }; G+ q {8 l9 U$ H) l
! {" p. z- G8 I1 ?9 J* I! Z
ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
0 U9 Z5 B; X5 y- p! x6 V: {4 }. _9 T' w+ U" v" a; j9 |
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
: y7 V* u! M4 h8 \# V/ J3 s# PISAPI Filter都必须引出这两个函数以供服务器调用。 5 j- ^2 i# [ k
- u9 P g- \( l2 v& k0 O8 Q: E
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
( ?7 v% H6 V; kFilter的文件名并加载它们。 % P1 L$ r$ ?8 h: R
. t$ ^. N O. y ]2 Y& ?HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
; M& o& p; D+ T+ U; u) F% x( D: D; l; K: M/ ?6 x) b( t5 c
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
r [. G7 q- c( p; c/ g- W) q# V. F# {6 e) B i7 @" U
BOOL WINAPI GetFilterVersion(
4 C4 w$ ?, `. ~9 G4 w2 B% YDWORD dwServerFilterVersion; //IN,服务器使用的版本规范
8 v0 I1 l* n* ?+ W4 EDWORD dwFilterVersion; //OUT,过滤器使用的版本规范 7 j$ u$ i9 F" @! M5 e: Z9 b0 |: F
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串 # F2 G# \1 l4 S+ I& ~
DWORD dwFlags //OUT,事件和优先级标志
0 d+ m. U' L- R1 P( u$ o1 w); ' V4 q: l1 m' w; p+ N( J
# d( F4 u: {: U" P u! z% A( r事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。 % @. D* \; z. [. }
4 P1 m# o3 K4 f; p7 \2 D/ t3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
: c& X; f3 V1 l' G
& ~2 f) E( m' ]* u( b" b2 N! @typedef struct _HTTP_FILTER_CONTEXT
3 {2 ]7 h6 U9 R s: m/ Y( |{
7 j6 H& R; V# r8 S
; V8 M! ^9 p5 xDWORD cbSize; //IN,本参数块的大小 " p9 a5 A9 l$ Y: t( h
DWORD Revision; //IN * ~$ X+ C v( A( j3 }* w, m" b
PVOID ServerContext; //IN,由server使用本参数
. h( V4 {1 D3 L$ Y' bDWORD ulReserved; //IN,由server使用本参数 ; [- D9 V' F+ |8 s2 Y% d3 K
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上 ( Z& O5 V7 O! h
PVOID pFilterContext; //IN/OUT,与本次请求相关的上下文 % [% f b. N4 u, M9 k- o
& o4 S5 t0 l$ h; J
//回调函数,取得关于服务器和本次连接的信息 ( m2 A9 L9 A" Z5 V- l# H* K9 t
BOOL (WINAPI * GetServerVariable) (
( l: q$ {# }" Z2 }struct _HTTP_FILTER_CONTEXT * pfc,
9 V" t! f) S) A5 N/ a. ZLPSTR lpszVariableName, + k2 F* w h8 {0 x1 y$ B. Z
LPVOID lpvBuffer, ; y, v9 |+ H( O2 \+ z
LPDWORD lpdwSize
; _* g _+ E3 }( [/ x% ^); * ?' M U l. {& Z% c! `
( n2 D* k. N4 P% q" Z& J* @
BOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头 ; J3 f% } ?" h+ r: }" V4 O/ K% a, x
struct _HTTP_FILTER_CONTEXT * pfc,
4 i" z1 [, v( |2 @: ULPSTR lpszHeaders, 0 r* R( r. [; W9 C1 Y- q
DWORD dwReserved
4 O/ w% T" l$ N( J% Q; r7 t5 e/ Z( S3 {); 8 ]9 b- n' s& b; U8 J D
9 w0 N- W7 _) oBOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端 4 C/ [7 ?5 G( i9 c S
struct _HTTP_FILTER_CONTEXT * pfc, % W8 A1 m3 f6 D8 b( ^* q+ q
LPVOID Buffer,
$ e' T) u+ e7 a& l) v# _LPDWORD lpdwBytes,
5 }5 _8 [2 ~2 O. ~: aDWORD dwReserved
$ S. a- ~- d3 D3 S/ `. F);
" h6 g+ J9 P) D" P- K$ G* `+ i# ]( ?9 f
+ ?4 h9 o: c+ I/ b1 AVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。 ( ?' z$ D: E h" E% d! s" [# G
struct _HTTP_FILTER_CONTEXT * pfc, ; B3 X4 I7 @; q1 Z8 K8 Z$ o
DWORD cbSize,
% P' d0 L' @0 F' Q- k! l) ~* VDWORD dwReserved 8 i) G4 y, [8 Q: @, j: a
); 2 N3 X" I! ^. L' Q( ?) X
{; q' M8 J/ [# s6 W; Z6 l# ~BOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能
5 R: E" }( S; F/ ustruct _HTTP_FILTER_CONTEXT * pfc, % L. P! D3 F6 x' [6 l& S
enum SF_REQ_TYPE sfReq, : T: T0 S* k& k5 w, U- `5 U! G* J. B
PVOID pData,
' d8 q% A7 O! z/ n( J) SDWORD ul1, 6 T2 P5 u, F3 K
DWORD ul2 ! x" @" X/ h, d" o) b: v7 K5 ?
);
$ F) Z" k1 N l r/ n8 b
8 h [; s- j' N} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT; . A% D* g6 Q- Q \! T7 P$ A
- P" U- W; v! r( v, ^/ {# F% g" r四、VC++ 6.0中对ISAPI的支持 % d! E' Z1 d" p) ]8 x/ B! z6 l
. ?8 w3 {9 k: @/ v
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实例,每个
( t: N" M% e. q% `+ H$ K* gCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。 ) N: I+ M' Y6 ~0 k
; U; q0 |! I' i, u7 M- r6 c8 f L" `" `
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。 $ z+ M0 L6 T& a9 Y, _" v2 j
0 w1 ~# }& I0 l7 l6 `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为例,该例中有下面这样一个表单: & W% r8 L7 l0 o- G% Z& k$ h4 d p
6 R9 ~; e# O" _<form method=get action="pinball.dll?">
4 j- ^1 ?3 K) w5 v0 ^( X) m<input type="hidden" name="MfcISAPICommand" VALUE="GetImage"> - u* E9 U/ r f2 Q
<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br> & ?4 i5 k/ q' ]: |9 G
<input type="radio" name="Favorite" value="2"> Twilight Zone<br> - f3 `* B/ ]- p' J; _
<input type="radio" name="Favorite" value="3"> The Addams Family<br>
0 q* g& U( S& p+ ]. Z1 W G& c( q<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
& b% `$ j, K8 m G- m+ y8 O9 W% F<input type="radio" name="Favorite" value="0"> I don't see it here<br> ' Z4 I- H% D: Y4 a/ z8 M% d
<br> , A, U. R8 Y7 s
<input type="submit" value="Show Me!">
! Z0 P3 \' N& e- K. s</form> 1 a1 Z8 H1 D6 E Y
; C) @0 C, R9 A" T6 _4 K当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端 & S% X5 q! `# E, C# u8 U0 s2 @2 x
最终将得到如下的URL串: & Q" _2 L' y6 O9 u
) H! R$ S) [3 w" G. R7 [1 B/ \
http://www.abc.com/pinball.dll?M ... mage&Favorite=1 : n) \$ w9 R* [4 Y* T7 m3 a
! {, D0 E2 w# }5 M5 [& A2 E
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数 # V# X4 n V( W0 H5 Y
将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite: 3 \6 J' x3 m; v! v
) z8 Y, o& _; G
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
; ^- N) n4 p9 M3 T( q# G
1 T) l3 j8 W: c: N0 _而parse map需要按照下面的形式定义: , }- ]" W1 s4 G3 j. o. I
! ^# m, L5 ]1 j, ?( Z# |/ j7 e; F
//CPinballExtension从CHttpServer派生而来
3 Q( d/ P+ U; Y& ~" l/ z/ cBEGIN_PARSE_MAP(CPinballExtension, CHttpServer) 5 X0 @( w5 k6 K2 g) D, I
+ `7 i$ E7 R& b3 R. l0 r
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice 8 _( q5 r. t( U8 X- o
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
; C# D# z n u7 V* S4 d7 T; Y+ ~9 ?! E8 r$ N
//该参数在URL中的名字为Favorite
$ F7 E2 n$ d2 a' E0 d' ~6 O& k+ [ON_PARSE_COMMAND_PARAMS("Favorite") 1 c, M" {0 m; F0 H
# u6 a5 l/ C$ u4 c9 ]' r
END_PARSE_MAP(CPinballExtension)
3 _2 v9 X! [$ Y. }& p% {
+ Z6 w2 f. u1 I. V3 H3 J7 \' i而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件: 4 _; l2 G( `% G @
4 g* V& h! N7 p9 J" P
OnPreprocHeaders
( c6 b; p& S9 V5 q v% }5 t' Z$ AOnAuthentication
6 j! N* B; `' W3 N0 y' c9 F! J4 s* k7 {OnUrlMap ( K$ I$ a* S/ ^: i# T7 |
OnSendRawData 2 j4 {% n; i, m, m0 C S
OnReadRawData
/ L7 S) p4 l# u1 t* _OnLog 6 Q5 I' ~9 B5 d! h0 k9 `9 Q
OnEndOfNetSession
9 P% A9 V9 B5 U2 C6 K; [
, G6 s1 [, o& O% N6 X" UMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
% _' s3 |4 ^; A3 r# j( ]' ]7 U5 N. q; `) j/ Y
参考资料:
5 o/ F% Y( u8 @, ?8 \0 A# s8 ?, V, p/ c* X4 ~% c, H8 x
1、MSDN
) \, E' g' A' A, M5 E1 h2、《精通CGI编程》,丁一强等,清华大学出版社 |
|