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

[转载]IIS的ISAPI接口简介

[复制链接]
发表于 2004-6-9 04:43:55 | 显示全部楼层 |阅读模式
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。 ( K# \5 ~* u5 {- I2 i

7 ~' S9 y( e; m, X5 B+ h7 F8 k# f4 w& [一、ISAPI接口和CGI接口的不同。
, B; x& H( o1 n2 [- i1 O6 M. t" ^( Z9 N
ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。 ; W8 m6 P( M; u
& C. Z) k3 Y* F1 t/ R6 e
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。
3 y  J+ K9 X5 x3 y3 ]; D( {8 V! c% v8 X& t
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
2 B5 W; W( U! P3 E2 `! o3 }$ d8 G) f
" v% J0 H" @3 ~+ d0 F5 nISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。 : L  O6 R2 Y9 Z6 D4 q
; j+ Y5 ?2 S6 ^9 i! }

2 L" p$ G$ I6 p! g0 q二、ISA " m$ Z1 X& \: ^5 j% ]$ H. G0 c
! `+ a7 m' D; V3 M+ M5 L( X
ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):
9 J: _, J. o, i. N( o* V! r" Jhttp://www.abc.com/Scripts/function.dll? % J8 F2 H( e$ a% `! Q; d+ o. Z

5 v, T) H! b9 @6 F1 A  @ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。 0 {1 U& ?7 X) l% a  K
8 e$ C; {! `" P* m9 s/ A0 n
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
. t5 G+ U& ?( [1 g7 P: r8 F3 J! l9 y1 g  d3 K4 k; G
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
8 r5 D+ |$ {+ `' j. stypedef struct _HSE_VERSION_INFO
8 S1 ]5 ~# b# ^{ 6 C' @1 c; c. w* `8 p9 k6 m
DWORD dwExtensionVersion; //版本号   Z& n7 p0 P9 b  G9 ^4 X, w
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
% c2 F5 D2 X2 C+ i' \" G+ P} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
, P6 @6 ^2 J( J2 ]2 n5 a3 V1 Y
' \* o9 a" r" `- |  P2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下: " q$ F9 M0 w6 l" A5 p5 @& S

, ?/ G, p7 O* |* F0 N& ADWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB ); / c( ~4 D8 \* E5 g! R- o5 T
3 k' [$ C/ l/ V; a& ]% H% z# W& e
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数): ( i* f3 z! y( @  g

3 Q  r/ Q2 S! [6 }2 Y! r7 ^typedef struct _EXTENSION_CONTROL_BLOCK
: l: ?* N/ E" t' }$ h{
- c6 ?$ o( T0 n, rDWORD cbSize; //IN,本结构的大小,只读 ! t9 |+ C, k5 {9 Y+ Z5 K- M( E1 Q
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
: i& m3 T& x( K" \$ h  UHCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值   [+ P. o2 x! H
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态   x, ]% U! v: X2 i0 z
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
' ?3 w/ |+ s+ a) bLPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD ( p3 C- [$ {4 R8 j  \" F
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING
  i4 P  ^* R% E/ ELPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO + ~+ m! ^2 N7 w
LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED $ k0 x1 P' G* i" j) y+ T
DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH 5 K  Z" V. H6 |1 E  w- a
DWORD cbAvailable; //IN,缓冲区中的可用字节数
3 D4 a. M0 Z# MLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据 7 \3 }' a# q% V& H1 _# U
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE 7 M3 u9 M: C  [- h( A/ q
1 @! x( h; O# a. r& p/ R
//回调函数,用于返回服务器的连接信息或特定的服务器详细情况 3 B* h1 X# W+ o. e7 h' [
BOOL ( WINAPI * GetServerVariable )
, P9 m$ ^( ?: A6 L( W( HCONN hConn,   g5 o. M7 H0 F- P: v+ V+ Y# Y% c
LPSTR lpszVariableName,
% I3 ~. \; w+ ~  nLPVOID lpvBuffer, + p/ `1 W0 D0 }" r; g( [
LPDWORD lpdwSize );
: m. e  r" t6 U, p2 {& }, U1 I, m" N) r8 m4 Z! ~( i  X8 z
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据 2 Y* o3 k( U) |3 z. S
( HCONN ConnID, $ [3 ]& S$ l$ Z. J
LPVOID Buffer,
2 }) `, t, H7 T! b( xLPDWORD lpdwBytes,
4 q8 ?' @$ Q4 s9 X/ J3 c5 UDWORD dwReserved );
4 u" K+ M4 M2 ]% W5 }( J$ @3 M+ Z! V, t! p
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据 8 F( ?. Q: a: B$ i& A
( HCONN ConnID,
/ l4 ~, a. T; W0 Z5 t9 l$ GLPVOID lpvBuffer, & z& t" M9 K5 \5 t& k! r5 C
LPDWORD lpdwSize ); * U  @3 f7 ^" {8 k) N
8 h4 X+ g+ s( V1 l6 @1 ^
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能 2 b* b9 d+ f& Q8 f1 `% i1 ]
( HCONN hConn,
* l: L  }: o7 `, j# GDWORD dwHSERRequest, / Y. ?9 B6 V; f# I/ T( Y
LPVOID lpvBuffer, / @7 W  V% U* j( s6 G8 x
LPDWORD lpdwSize,
: l7 H# Y; l* f1 q; Q  R7 R; {/ R$ LLPDWORD lpdwDataType );
6 C( k/ B! n' P! d/ u$ `4 O. Z5 B3 b0 a. U1 _% L! c- {
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK; 1 B0 H' U2 N( C7 |7 s, M, T- W
2 c7 u: I! O* P8 f0 I
在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。 1 M2 V* j7 `( z* O1 T1 j& [* L( P

  q7 f: y2 o) ]+ X& S* }% \% j! K三、ISAPI Filter
: R2 [6 `; h( s+ ^1 Q
! S7 J. D3 n: u: {  yISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
" y. E0 K/ k" ]  S8 Z. N: K8 C6 y1 b4 K* O+ d
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何 & z7 C/ z; l/ C
ISAPI Filter都必须引出这两个函数以供服务器调用。 % `5 r: f- k: l

- x3 \: p- `' n1 X9 T0 }4 N) P& J1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得 9 ]' c7 R# ^: g' S! d& W5 {) i
Filter的文件名并加载它们。
& A8 v4 h8 `! q& F
' `7 h0 `) T9 _HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
7 ~6 j5 ]$ b- L; }. p8 z8 e7 n3 f) r% ], t% w4 ?
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
- k$ q4 I! f5 `' S
6 A! {& M+ C# F4 iBOOL WINAPI GetFilterVersion( : Y' [& ]! u+ B( [; \
DWORD dwServerFilterVersion; //IN,服务器使用的版本规范
: p4 @2 c/ [# c$ yDWORD dwFilterVersion; //OUT,过滤器使用的版本规范
1 b( ~& Q# S& ]! S0 T5 CCHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
0 a2 P: T' V5 W, l/ x, \0 b8 \2 iDWORD dwFlags //OUT,事件和优先级标志
6 i( L' V+ j/ H- r); & v) H! r1 j. U/ L! g
# P5 S0 J, |, S8 \, [1 H
事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
- Q" I& g$ Y- C3 f- i3 v. u1 T1 w8 y7 u* E9 C( [
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
2 [9 X+ J4 M' D( z  ^. J; z
! e) K( t3 J: |typedef struct _HTTP_FILTER_CONTEXT
8 i# f* x( u1 |+ I{
4 E9 \7 }4 \4 F! W; {9 h( W3 f
' `4 `! w- m. S$ Z9 S) [DWORD cbSize; //IN,本参数块的大小 : J7 b: ~- Q2 J" z7 J
DWORD Revision; //IN 2 V8 F! W! }" t3 p) b& ~6 f. Q; S  Y( L
PVOID ServerContext; //IN,由server使用本参数 # F7 J- I/ @" X
DWORD ulReserved; //IN,由server使用本参数 0 m: z5 B/ a9 P) v7 V
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上
$ @+ ~8 i6 C0 m: X0 d' SPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文 ! W, Y$ O* m' z9 r. O3 E! O) ^
5 ^: {8 Q0 J0 `
//回调函数,取得关于服务器和本次连接的信息
, v. M/ m7 v! _# d9 [. N3 f$ _BOOL (WINAPI * GetServerVariable) (
* b3 m; H% D3 u: j3 ]6 ^  A; fstruct _HTTP_FILTER_CONTEXT * pfc,
' Z5 |) |! E/ X! R2 aLPSTR lpszVariableName, 5 A7 \0 F) B% d/ L& Z
LPVOID lpvBuffer,
, e3 }" _0 M3 wLPDWORD lpdwSize 5 B; t4 m5 i$ a" b
);
/ K$ T  F, s; E9 r, W4 G
2 A2 b- n2 @# \* I% J: x3 QBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头 - v" M+ H4 m- F9 H. G
struct _HTTP_FILTER_CONTEXT * pfc,
; D$ h# {5 ^% s' ~$ [: ULPSTR lpszHeaders,
: P" R5 k3 M3 \7 U4 r5 fDWORD dwReserved
$ O' r7 i( @7 v$ f);
" y0 M) }( }& c, |% [9 q8 ?
% P+ Y" w; l( v- V1 ]# n' L# {- UBOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
( d, U% x, P+ Hstruct _HTTP_FILTER_CONTEXT * pfc,
8 P7 y, a5 i' J$ m0 XLPVOID Buffer,
* g- m; d8 P* }* pLPDWORD lpdwBytes,
; X+ U5 j0 B( D8 v1 RDWORD dwReserved + d+ I2 ~% p& ?& K, F; e- f3 Y
);
% \' o. d9 L9 b2 V; L2 M! s
5 p+ n" g0 F( d6 MVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
% ]. Y0 w1 \$ C* K* q: ]3 Ostruct _HTTP_FILTER_CONTEXT * pfc, ; ^4 c5 A5 ^! |! d- a
DWORD cbSize,
; m* b" r$ B; D& y4 r! uDWORD dwReserved
- Y- {6 T; Y  Z( t; W);
, {& {2 G/ \; Y% C; P7 Y
& n% g' G& v5 s! W2 ~2 r9 }. QBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能
  ^  i# f! D( |0 G& d, S0 }! @struct _HTTP_FILTER_CONTEXT * pfc, 9 v* w# P9 R! s! f: h
enum SF_REQ_TYPE sfReq, " r# Q1 ~7 ]  U4 R
PVOID pData,
2 ^) L9 Y# E6 h" _  r0 D8 |DWORD ul1, * \- ~( e6 X! i0 p# u6 L
DWORD ul2
5 K8 y/ M# z0 b+ ~& q' w);
" K( ^8 j" R' {
- s) n) `9 d- R' w& [$ d} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;
4 ^0 G  o  w& E
9 n3 l# J+ C. U四、VC++ 6.0中对ISAPI的支持 / V; z0 y: A5 ^" d" N
' o" B; L9 ?* Y4 V6 G8 e: \/ H$ Y1 z
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实例,每个 , }8 w7 q$ U2 F( v
CHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
" M# Z, m; E5 a3 B, Z7 r! O. I7 H+ g6 s" H4 ]/ l' H
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。 - j+ x0 x( t! j: Z8 ~* j

& U/ O2 w8 k7 R/ G, `2 JParse 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为例,该例中有下面这样一个表单:
& ^+ |- u8 Q4 M2 H9 y5 j3 g) d) E$ J1 r7 B
<form method=get action="pinball.dll?"> , H: y0 v3 x' L( H
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage"> & L2 T3 l9 H3 ~/ R" Q% h8 j
<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br> ) `" S! A  x* a( m. ^, s& w: H* o
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>
4 R! p1 w; B% T4 g<input type="radio" name="Favorite" value="3"> The Addams Family<br> 1 ?3 K5 o1 r+ G& t3 i) V
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
' P+ ~* \6 ~) y( `3 V+ W1 [<input type="radio" name="Favorite" value="0"> I don't see it here<br>
! n; A. W( E! N! U' b# H<br>
" d% [& Z  s7 N9 u% n( p0 I<input type="submit" value="Show Me!"> ( z6 m, U1 g9 |8 M  q& z" b6 Y9 O
</form> % |  j4 t* l, d" u1 H

7 h) F5 v4 g( o1 w/ P" l! e当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端 * [) Q: h# ^2 y/ t  S4 P. f) E  j
最终将得到如下的URL串: 4 q) b! f& W4 s. H, X  w. j. R
0 f2 d; I! a3 l& R4 _, t  m
http://www.abc.com/pinball.dll?M ... mage&Favorite=1
6 Y+ G) F; r& K4 a
* L# v0 T) s. _/ s/ t  b4 x& I1 J$ y" f. `在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
5 u7 }) @8 F% V$ d将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:
1 {2 S/ N/ {: j' l; ?7 w, D7 o3 m' x; \
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
! }7 {/ c5 M: e' p5 U  F- [5 T
9 A, s# u" R- c0 b+ w7 v9 D0 M而parse map需要按照下面的形式定义:
# t  Y0 p! ?! w& M3 Z" H$ K& ^& f. Q+ H
//CPinballExtension从CHttpServer派生而来
1 z) ^: ]! ?) u) iBEGIN_PARSE_MAP(CPinballExtension, CHttpServer) * L$ d3 d* c$ m/ T. c( E  h
3 ~! Z8 L8 g$ R, K0 E/ s" G
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice : {7 _9 _. R* v, \* \2 G
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
8 Q3 {% i$ I+ }$ K
% x" z: ~' Q- G/ i8 F- K" ]//该参数在URL中的名字为Favorite * J$ D0 a, F2 d% w) Z' O
ON_PARSE_COMMAND_PARAMS("Favorite")
7 V! R  s0 T( t% p
7 e) j6 `, B- [& t/ u+ d! R* AEND_PARSE_MAP(CPinballExtension) 2 e- l" I) r9 [/ k

* d  C; ?+ s" |$ _3 M. R7 n- j3 w而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件: ' I; z0 l3 U% k3 f8 e

' E( i7 T- g6 V5 r; {+ TOnPreprocHeaders , _9 H! C& w1 _5 ~2 a6 F- K* {: f
OnAuthentication 1 }/ y$ U4 x5 [) k& a+ U( s
OnUrlMap   p9 N6 l. F, u- S2 g
OnSendRawData
2 s+ Q2 \2 T4 u  bOnReadRawData
* S: V: Y4 X0 T- k, u/ W% YOnLog " v# A* W/ s# c; E/ ]' h- }7 N! o
OnEndOfNetSession
& W8 d5 R) e+ m
9 _* X" }0 B( n7 Z+ k$ ^/ SMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。 " w4 }( x7 c& v! ^+ ]6 I

& L  |) [, h2 B$ b/ p! Q参考资料: 4 Y6 x0 Y$ q4 g! U, y2 A4 F
* X. x- F- m' `" r4 p6 \
1、MSDN ! s7 ?1 D7 [: ?0 w( a+ ?
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 08:08 , Processed in 0.017746 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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