找回密码
 注册
搜索
查看: 4267|回复: 0

IIS的ISAPI接口简介

[复制链接]
发表于 2003-10-13 13:22:17 | 显示全部楼层 |阅读模式
作者:netguy (mailto:netguy@nsfocus.com) + q- v. z# n4 q1 c9 E
3 h4 V3 a/ a8 s( e( Q% R
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。
1 q3 B8 T7 o, z) y& S/ J
; c7 }" j3 \% f/ m7 l# h一、ISAPI接口和CGI接口的不同。
& M; C. m5 N* X# I! \( Q3 G0 Y
' Z2 f0 u- I% A5 x3 E5 |ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。, p. a6 x0 a! h9 k, l) ?

1 d% q/ ?0 p8 U; e1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。6 o3 v$ \3 v1 v' C6 I0 \

% K( K& }# ~- }3 X- E2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
1 D: U& R) c5 ?  h3 h( d# O
: I' |3 l* E4 M  N' HISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。! W9 O. @# n* B' i7 K! R6 n( ]5 j

8 k! {: j" [  S3 [. j# H
7 \  A0 p2 h# `% f5 O2 l/ f3 Y二、ISA2 _- t. |0 |, l  l) D- d

% A- v# s( m* q) m' W3 o! k% P! CISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):5 w; j8 j/ V" E0 n8 Q/ `5 I
http://www.abc.com/Scripts/function.dll?1 n+ c" T0 d% F# g

, O) }7 h8 c4 t* g! B7 V+ nISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。
% ?* T+ e  R7 E) U& D/ ~
: j+ W. M. r* C# }- ?' T1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
5 h7 J# l# R: }
+ B/ W5 t- J+ `# {# T" C) {BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
$ _4 E- |+ R, k1 j+ F, V$ ptypedef struct _HSE_VERSION_INFO4 Z3 c3 @" W1 y4 i
{
2 u$ v4 Q5 i1 j- P/ A: J; nDWORD dwExtensionVersion; //版本号
# S- b$ G9 z' Z( B. ^/ q9 P: s! ACHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
* g5 U* {* V" k+ R3 B} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
2 ?4 k6 f" U) O4 w4 Y3 e7 G, E% X. p, z- n3 t
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:0 \' P$ r! @/ ^; I' G

) m5 G* d1 ]' @3 L) xDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
5 \0 u" Z7 \6 A6 F+ k2 `3 t7 l
. N. W' ^. h& b( X  ^& aECB的结构定义如下(IN表示入口参数,OUT表示出口参数):
2 E5 H8 g6 d$ _) M( H" g. b6 S; n, u) ]; N) j
typedef struct _EXTENSION_CONTROL_BLOCK
7 {5 ]: s. \; {, T( @{* r3 m1 `4 I% n. x
DWORD cbSize; //IN,本结构的大小,只读
0 p% X6 g# v. l/ k" f! xDWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号6 G' r+ P5 `% q2 i3 W
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值5 n" N& x2 L; n; A
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态' L5 |" O1 n. Y3 P9 M- i& \
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
6 V) G# h5 ?6 pLPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD6 t' [8 d3 i* y( Y! u7 `
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING3 g4 Z' c& \6 h; x( L: @
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO* z: i, W! D6 k& f" g0 q
LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED$ w. }* ^# ^" x  \0 D- `! |8 e
DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH+ |7 Z/ g# x- x+ G0 B6 x9 M# S; _
DWORD cbAvailable; //IN,缓冲区中的可用字节数
; i. I" m5 p2 e6 O- P" Z7 O  iLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据; d  ^+ ^! v% p# N7 d! x
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE( ~, a4 ?- Z8 x5 @& Z0 _0 Z

# O' }' F0 A' h//回调函数,用于返回服务器的连接信息或特定的服务器详细情况7 i7 P1 U) P; {4 a; R/ |% P
BOOL ( WINAPI * GetServerVariable )
  {" B' A. W9 Y" t# X; }9 L( HCONN hConn,
* _# V7 o; C8 c. s: f- z4 mLPSTR lpszVariableName,2 u3 Z7 N) V  X6 D8 N* \- P' S
LPVOID lpvBuffer,
4 z+ P9 v5 c- x( DLPDWORD lpdwSize );$ n; [$ [) K- `0 I
/ [7 c1 Q/ k/ Y. }# n4 O- ?
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据$ `6 g) u% u7 C$ i/ U
( HCONN ConnID,' U3 o  ?2 N& E( F; D' Q
LPVOID Buffer,
! j+ \7 f/ Q" i0 N) V" q" X& sLPDWORD lpdwBytes,
% i  C* {& h$ `) m/ U4 pDWORD dwReserved );
3 ]- y' ]1 L9 K: }, H5 q6 J- N/ k  v4 B
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据/ B# n) ^: Q. D
( HCONN ConnID,
, @& O9 \, M3 I# X  J- J& j# l( yLPVOID lpvBuffer,- q8 B8 @" I* `) T4 c2 S4 |- }! v
LPDWORD lpdwSize );
. L* P' V% _8 S! A! J8 X4 s; ?
0 D  @8 U, R; P( ~+ GBOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能9 h: @* g: F  y8 D6 m
( HCONN hConn,) ]" [+ r# C2 v5 t0 w/ Q
DWORD dwHSERRequest,' F! {7 v0 t0 x! Z+ f8 @& c
LPVOID lpvBuffer,
* {' n8 T& H: e  O5 ~) nLPDWORD lpdwSize,
7 b' t' i* O* ?% k( _LPDWORD lpdwDataType );" ^# l, Q+ y5 l# Z! m
, X+ |. Q5 g0 ^  ~
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
; d* b, ]; m$ e0 x7 m: z7 J
4 X6 A  V, @2 y- y  j在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。2 H" l4 h; K( P) `) t# _

4 d$ w8 ]9 J4 }  s: N: d三、ISAPI Filter
3 t* U& R8 ~, C8 H- S
, W6 a" ^1 C+ xISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。5 g2 d/ u( m" d
8 e( D7 D, W( N' T) U( v1 m
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
# U! ~, I9 M& W& v$ w+ J! wISAPI Filter都必须引出这两个函数以供服务器调用。  q  `1 q3 \: K+ `' {

* J  g8 A8 Z: u2 z  \8 q1 K1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得5 e) [, f2 z  `, T$ |' ]8 c
Filter的文件名并加载它们。9 F, o3 r, ]$ }* Q3 E. ~
3 B$ O2 j. v, I2 \5 Q% N
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
5 f# q7 X0 S/ D
0 M$ s& N6 b! j, @' D2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:" D& _7 c2 T7 m% S' I
8 M7 `- [! E& B2 {. [$ R1 g
BOOL WINAPI GetFilterVersion(
7 C  `; C) c8 }& s( I5 ZDWORD dwServerFilterVersion; //IN,服务器使用的版本规范
% K5 _% e+ l; ?8 }DWORD dwFilterVersion; //OUT,过滤器使用的版本规范
% ^6 G" |% a7 E, ~+ \* k; }+ m( [CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
7 |' K7 v, x( \. _* K1 C! p: _5 uDWORD dwFlags //OUT,事件和优先级标志
+ x# ~1 U  R9 ~( r. |! ?);* k1 i" }* I, X3 }3 r/ `

/ P) |( F9 X0 I$ q事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
4 l5 A2 l% Q/ B7 W; l) X3 }/ B# A% `8 R
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
6 g. s( s  I* R1 G  b4 x
4 ^2 `+ b( q9 v, @/ q) n% o3 W5 ktypedef struct _HTTP_FILTER_CONTEXT8 N0 @- q* X- n
{
/ c- l8 F9 e* o2 \% L
2 |# E* m: |" QDWORD cbSize; //IN,本参数块的大小: r) T% O8 n; x- }) {% ^
DWORD Revision; //IN7 \, J7 K2 C4 c( a, W" W/ u% f: m
PVOID ServerContext; //IN,由server使用本参数6 \7 P% H6 A2 s
DWORD ulReserved; //IN,由server使用本参数! l* {& W# |+ D: `
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上4 A, T' j. d/ p1 k7 V" u% a
PVOID pFilterContext; //IN/OUT,与本次请求相关的上下文$ R: V6 ]2 I, F: K$ M4 x& b
" F+ b: y4 h' K
//回调函数,取得关于服务器和本次连接的信息
) c3 m% `  o* M+ X$ g9 o# mBOOL (WINAPI * GetServerVariable) (
+ d; }0 ~% d) rstruct _HTTP_FILTER_CONTEXT * pfc,
2 S7 b4 d6 X% s) s" qLPSTR lpszVariableName,
9 {& J1 l5 s+ O" \1 ^9 l9 fLPVOID lpvBuffer,
1 U! n$ h/ [2 R# }" iLPDWORD lpdwSize8 e- F2 g6 \' I- l5 b' Q% C) t
); + w/ H7 h3 k3 a0 V( p2 }7 F

$ Y! e( g) X! q  k5 C0 U$ \5 eBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头
! X. U+ P0 P3 s% J# Bstruct _HTTP_FILTER_CONTEXT * pfc,
& k8 v0 z0 v* e+ T9 i: mLPSTR lpszHeaders,8 j/ Q3 ^3 B+ t0 W* `" `$ w0 o
DWORD dwReserved
- W! b( N' {) [3 }* G/ O- s); . Z8 ]5 q' |% ~# |( G
! Y1 l. \0 |" o! O2 v5 z
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
# R  P9 l1 J% K$ _4 ostruct _HTTP_FILTER_CONTEXT * pfc,
; H$ \8 J; q$ p8 JLPVOID Buffer,4 z+ N9 ]: d% D8 |  H
LPDWORD lpdwBytes,
3 k1 b- u( I* L! r# QDWORD dwReserved9 w7 w" z  s; g6 b5 d4 B1 `
);   Y& m7 E* K/ I1 a$ z( [( h
" ]3 T1 l  N% s
VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。' q  j- c8 U" k$ o$ {+ M( V
struct _HTTP_FILTER_CONTEXT * pfc,
3 Z5 ]) T9 h2 \% i8 m# j1 E9 {DWORD cbSize,
) k) w% y9 A. g& y- vDWORD dwReserved2 L# c1 ?6 W9 {& r3 K
); ! [8 w+ j2 {( w3 X" ?
- R9 t' y5 ~; e0 `% v
BOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能
# h, z! ]$ w- k  S$ r; P, Pstruct _HTTP_FILTER_CONTEXT * pfc,* t/ s& u: M' W7 _6 T" I
enum SF_REQ_TYPE sfReq,
. s% h3 {5 O4 J5 w1 kPVOID pData,; m' x% H2 C0 U% C5 T
DWORD ul1,
/ d6 R8 [" C, J0 {# O; kDWORD ul2; n; Y/ {2 W! m; ]6 f
); 6 f" ^$ B, \% \2 [/ ^* k# \
9 l+ @1 w- ^9 U" c3 t+ y- }
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;! W: [0 v) K$ n( y6 O
& T5 k; Y" N# y3 \, N5 c, _1 k
四、VC++ 6.0中对ISAPI的支持8 _, Y; U" g# C9 C' ~% h( S$ P

  w) O0 `6 G$ m7 kVC++ 6.0中定义了5个相关的类以简化ISAPI的编程工作:CHttpServer、CHttpServerContext、CHttpFilter、CHttpFilterContext、CHtmlStream,这5个类都没有父类。其中CHttpServer和CHttpServerContext主要用来编写ISA,CHttpFilter和CHttpFilterContext则用来编写ISAPI Filter,而CHtmlStream则用来操作内存中的HTML文件,为其它的4个类提供服务。CHttpServer在每个ISA中只能有一个实例,一个CHttpServer可以对应多个CHttpServerContext实例,每个
( h; l: V5 y( {+ L" mCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
- }! E# B. l" x+ z7 g+ y3 D
' K9 E& s6 d, U% A1 [9 W1 Y% [一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
# ?- E8 b+ J# y
% \! r6 c! K8 w2 g/ G+ DParse 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为例,该例中有下面这样一个表单:: G$ G  f" @3 S" d

* [0 }* W" I( \) J( V) M  R<form method=get action="pinball.dll?">5 R. b) L- z  ?+ ^% F" @+ L
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
' E6 |" Y, L/ w6 u! `- k<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>6 A, g4 e; p3 ~0 N
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>
; R* s' B/ `5 g<input type="radio" name="Favorite" value="3"> The Addams Family<br>
; Y3 T$ `! ?- o4 l" z% f8 [( G' b9 d<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
; v' O  s9 A5 X# [$ z! V0 B% |7 J2 N<input type="radio" name="Favorite" value="0"> I don't see it here<br>. {; E- F  X' z: Y& p3 q
<br>& [5 ~* c; Q6 f/ Y
<input type="submit" value="Show Me!">
0 b: @& I9 P" |6 S/ a" m8 t3 B+ h3 t</form>
6 O. S" I8 q) Q1 v6 Q: C7 e5 l- R/ e, J& \7 V- m! \
当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端2 {) y6 l5 s1 ]; V
最终将得到如下的URL串:" p. A  }& k" U2 }

; A3 `: T; j5 O( d( ihttp://www.abc.com/pinball.dll?M ... GetImage&Favorite=1
) p! L0 c3 W" J+ N3 E9 G1 H* ~
3 g3 n) t0 y& l* t4 z在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数$ M3 g& G" U) V7 F2 y
将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:" [( ~$ h0 d* z: h7 s$ j
3 o2 Q% }8 T3 W" C
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);6 L2 S( d  r) i, P4 T
4 |# I) M: t7 q1 U" j7 C% A
而parse map需要按照下面的形式定义:
5 O+ H" H; L2 D$ R" Y; U
% y* B; i- n* ?5 i: p" M' h//CPinballExtension从CHttpServer派生而来1 V( g5 T0 j5 p% c
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer) ( z$ }3 n) u1 A$ T) C+ V- Y+ z
9 ]: X0 `- h6 V6 C0 v: V
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice
! o" z6 ?! B4 s: qON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
: v& F: V. P6 i2 D# X
1 o" D5 w, k' h' J! [4 P//该参数在URL中的名字为Favorite
; e3 W6 c' m- ]" w. O; uON_PARSE_COMMAND_PARAMS("Favorite") % g; b  }* e# l9 [* K6 J
+ s/ r+ c+ p  w6 v2 o4 S; U, U5 `
END_PARSE_MAP(CPinballExtension)
. V5 B0 B* V3 z2 ]0 ~
0 i, G1 m. W8 i" a7 n  f而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:
# q# B* Q0 @6 I0 m8 R8 U8 M* @2 y! K# f, J$ p; w
OnPreprocHeaders# o% R" l- h. `# }
OnAuthentication
9 `+ [* F" P7 c2 N1 j3 ^( O. b" FOnUrlMap) k  O1 p- L8 D# @7 [2 O% _
OnSendRawData
! T* Q- R( R' [) z# C( @OnReadRawData
! n) A7 s- ~2 G: EOnLog
# n- @# @' Q; U5 T5 yOnEndOfNetSession
6 m' k3 C/ y7 d) n3 @
' v5 y9 m+ B+ W- P$ M4 bMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。4 t0 S6 C  f0 p! S1 r

6 x% R) y% l$ Z  f参考资料:
2 X: x& I; N7 }' X# F2 C8 S7 J) _' m) O! F
1、MSDN  ^4 `# f* \6 a* @2 N+ _4 x* R
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|宁德市腾云网络科技有限公司 ( 闽ICP备2022007940号-5|闽公网安备 35092202000206号 )

GMT+8, 2025-11-15 04:50 , Processed in 0.019492 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表