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

IIS的ISAPI接口简介

[复制链接]
发表于 2003-10-13 13:22:17 | 显示全部楼层 |阅读模式
作者:netguy (mailto:netguy@nsfocus.com)
+ D+ l) ^* ~$ r, M# z; q$ ~* n1 r
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。8 J: n$ W9 G4 d. S. g+ }" T4 o) X

- d: `, _# ]+ W7 P7 l# O( U一、ISAPI接口和CGI接口的不同。. W+ v; c* O' v$ _% X

" B# v, p2 H. F. ^9 S- NISAPI程序和CGI程序完成类似的功能,但是实现方法不同。( \: |( x$ M$ n5 u- n. Q8 U
8 ?1 P4 B# E3 g) O
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。
$ x" c5 }, m6 A2 P6 h/ Q& N; }% ]* z" ]  }; L* ~
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。  A7 C# H! z. a% _1 t$ [' C

4 X% F# ~' u/ Y- B5 I; y- ]ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。' Y: Q) q. a. B; z$ E2 d, t. i# H0 X
+ r5 w6 F. j, P/ Q0 {3 \* J/ C7 L
5 G# Y9 j; M3 v7 @7 ?8 [8 p0 `
二、ISA
6 D4 ^6 k! M6 [$ ]8 x( |0 a
# K- V* e1 i: iISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):  |% p3 K  U3 n) r$ Y+ P
http://www.abc.com/Scripts/function.dll?
( ]% S. B# d. X+ r; t0 U" w! ]. _! s+ Q( @1 N- @: R( q  Q) o
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。, ]6 q/ R2 X" \. {
+ c/ n# K' \5 m  T# x& f+ o8 @
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
: h3 ^" e4 ^5 d8 h; I3 J$ a+ x+ w$ a$ N& ~/ _: r
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
4 n  [  {/ G" C  s$ {3 M: Atypedef struct _HSE_VERSION_INFO% @8 Q3 o& L6 x" ^% L/ E
{8 B6 C: g7 A1 W' y' K5 X
DWORD dwExtensionVersion; //版本号
. r5 c5 `: g1 BCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
: r$ O9 K0 s! z: @} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
; }& a; |" o* @$ F  c+ ^$ l5 T/ l) v0 X7 S5 ^
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:- L( F2 v; @% ^' N
5 h6 r2 D) T+ D- Q) x
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );% S& M# y' Q0 y
, ], y2 d, f# o6 c2 T+ L2 {
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):
2 a0 ^: ]5 d5 c" h+ t" [& f7 O, p+ V# Q% w( j# V8 s! ^6 k
typedef struct _EXTENSION_CONTROL_BLOCK
. T1 c) x$ \0 d3 D{
) p. @( n/ U( W1 O9 }6 S. n  o4 r7 `DWORD cbSize; //IN,本结构的大小,只读
$ b5 _1 N; `2 `DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
1 s+ w6 B* l5 U& e+ P8 R; w- mHCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值" o$ @& b  G$ k3 d+ {: v* P6 ^
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态
( \1 I4 S) q1 |0 ^$ BCHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
& J# `& p: m1 \6 a3 r9 |LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD3 F8 U" n/ p2 w8 P' k
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING& @1 |, {% |; t  F0 y4 s; ~7 A  B
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
; Y9 x% U7 w% ?7 [' v1 e2 H' b2 kLPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED2 l- A1 u/ K+ }! h1 ~
DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH3 Q* w6 X  j. y) Y6 n  J4 w) X$ [
DWORD cbAvailable; //IN,缓冲区中的可用字节数& m4 l$ a, `, f, x- _- s" f
LPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据* i" p0 c; h, ~2 @
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE
  I" i+ Q' O7 s7 L9 A* ~& @. \" n) o# z% u( p  V
//回调函数,用于返回服务器的连接信息或特定的服务器详细情况- E4 A  I: Z6 d) Z  n: @
BOOL ( WINAPI * GetServerVariable )
) d7 V' O# S  O' z6 f1 @0 |( HCONN hConn,' b+ @. W7 s( z9 N) n  I4 F1 y% h
LPSTR lpszVariableName,6 i& J3 E1 Y) W9 L
LPVOID lpvBuffer,# V% A, v3 l# f
LPDWORD lpdwSize );
- _' `% e: f% R' k. q0 z- D+ z( r+ j) u, O  _6 _
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据
$ ?3 g8 A" X- r, k( f' F* _( HCONN ConnID,
1 y/ \2 @9 r. s. y5 ?LPVOID Buffer,
7 `' L- ~' l' n" ?: t% n1 zLPDWORD lpdwBytes,2 W9 _2 E' u% a4 O" O
DWORD dwReserved );# c: e) d- B7 T8 ]* ]8 z
6 W! d# a1 Z" {, ^. K3 o
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据
' p0 G! @4 T% e( HCONN ConnID,
/ v+ `% L* O4 {- e& I+ Z6 K) |LPVOID lpvBuffer,
1 g# m0 u6 \2 x3 ?( l% G" M) L; @9 \LPDWORD lpdwSize );
! |* K  z0 K  X0 F1 x# ]$ `: J. V' C. C6 j
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能4 \" G% x4 b' l! Z' E4 X  b8 ~4 C' Y2 z
( HCONN hConn,
+ l! n3 u, G9 @" t7 V8 P+ q9 n" NDWORD dwHSERRequest,% _! M3 H1 r+ W3 q0 p
LPVOID lpvBuffer,
$ N" v$ Z+ _2 f6 ~LPDWORD lpdwSize,
  {$ D: X$ A7 \* X1 s2 X0 ULPDWORD lpdwDataType );
$ M; x: b: R" \6 ^9 d" h7 d# z6 l$ c" |2 `& P
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;2 ?9 M& N; V' v
6 b% _# e) I) C+ l" s
在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。, e0 T& C. l- S4 Z* r0 ~

+ y. U# H1 Q+ a4 N三、ISAPI Filter: I" ?% f0 b0 u$ ~8 d) j" _0 i

. S& d. x( k5 [8 vISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。# d2 J% d- T* f& L" ]/ q$ n

! D5 x1 t$ ~: S5 }0 [2 }& _ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
4 r3 c; G, q+ U. s: ]ISAPI Filter都必须引出这两个函数以供服务器调用。
. x6 p$ z3 u/ q' p
5 W. _' m: o; B2 F- _1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
% K1 ~9 j  d7 ]% [% f3 U3 TFilter的文件名并加载它们。
$ p  b- j3 ~+ @1 [3 F: p/ F) r
3 }5 ?+ C7 ?( {HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL, [- q) g2 N7 M; H7 K- v4 y

3 y) l2 \/ F2 F' L4 a2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:5 X$ b0 {  x3 D" y
' r  h0 e+ A4 h" R+ ^2 T8 ~( h
BOOL WINAPI GetFilterVersion(
) Z1 z1 _& t) W. k: @* u8 uDWORD dwServerFilterVersion; //IN,服务器使用的版本规范 # W7 I2 ~7 F, Q* r
DWORD dwFilterVersion; //OUT,过滤器使用的版本规范2 e  y7 b: b2 L2 Z
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
( n2 S* f7 @' z" D  u* o' H, Q8 sDWORD dwFlags //OUT,事件和优先级标志
' p9 ?- M6 m) U( M: `: ]) T);
- ?9 m% e, e1 L6 m2 q+ K* @( j8 o7 [# ]5 [9 U: h+ ^
事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
* p) J7 B1 t' m8 o' o7 V4 j6 Q
1 k2 h) R8 o+ X0 @6 T3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
- c3 B/ G$ g, L, h* v, H6 u% b0 t, R4 Q
typedef struct _HTTP_FILTER_CONTEXT5 F- T0 A! g. i% g5 c
{5 O& }  h$ P& _9 k/ I, U# J

. I" l; o! N" d: Q9 V) KDWORD cbSize; //IN,本参数块的大小
6 P' G7 X% p  `/ d) S# ^DWORD Revision; //IN3 u3 B3 h: G' W) i: A
PVOID ServerContext; //IN,由server使用本参数# o- c1 y1 s# H+ L8 C0 X  m* V
DWORD ulReserved; //IN,由server使用本参数+ ]2 l3 v( i9 x; |
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上
3 R( [& ?/ n8 E9 H/ Z7 IPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文
( o; a2 W5 q& `9 f& {6 o$ Q4 f1 A4 c- Q, e
//回调函数,取得关于服务器和本次连接的信息
/ x6 I. K/ f  g8 `2 RBOOL (WINAPI * GetServerVariable) ( 6 H4 {8 m5 m- s7 H% @
struct _HTTP_FILTER_CONTEXT * pfc,
8 s3 S2 D! x* p6 z& iLPSTR lpszVariableName,& C% P  s& L% Q" }9 [& G* }/ R
LPVOID lpvBuffer,  ~4 n5 h; _0 g3 ]1 c8 x- c
LPDWORD lpdwSize
* Z( S9 h3 c) _" h);
! G4 N. i* `" m  f/ R( q+ Z' d4 h8 L1 S
BOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头' G+ u7 G; D8 K3 _4 f
struct _HTTP_FILTER_CONTEXT * pfc,
( g# A# M! J6 lLPSTR lpszHeaders,8 J& N' X1 I+ {% a
DWORD dwReserved! P- [4 g! l+ d
); + {& y: V; }+ Y: n5 V
( r$ W( k: \  }3 t( o8 e2 `8 N' ^- u
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
% X" ~3 E2 g) d( |, i$ g/ ?struct _HTTP_FILTER_CONTEXT * pfc,
2 k% r2 O$ w: A+ t$ QLPVOID Buffer,  c! N, v% f5 b1 W3 _
LPDWORD lpdwBytes,
! @; @7 P2 d5 }: W4 m! w" IDWORD dwReserved. T& A& T  b. R. v% o3 J8 [- Y1 }5 u
);
0 c# `; W8 t) k& K  N
  _- H: a0 \5 i$ HVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。6 A& F6 ?' ?& }- u- e
struct _HTTP_FILTER_CONTEXT * pfc,
) R) N0 z) `" G1 Y" K8 DDWORD cbSize,( b  j+ |, G0 ~9 D/ h& C9 n3 o. L
DWORD dwReserved
6 T6 K3 b2 G+ k+ L$ M);
3 E9 ~1 s( `2 k/ x: Z3 Z! s
# l. f4 g$ f; i3 y7 oBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能2 g1 j0 X- {. H# H& p% D  B0 r8 `% ~5 ?
struct _HTTP_FILTER_CONTEXT * pfc,
  D$ P$ d5 I' \- Tenum SF_REQ_TYPE sfReq,
- J! ~  r+ Z: J6 o3 CPVOID pData,. O5 f- A6 @: d6 [- Y3 Y
DWORD ul1,
' U% e( q6 j3 T( E( }) _; ^0 e2 aDWORD ul2( D% F7 g# f+ g
); 9 w! o6 g, `) |* {# L5 U

6 u! I+ L$ X7 P# k  k} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;: r- s; C# T* d

8 Z! [: _3 Q! U( }8 O0 p四、VC++ 6.0中对ISAPI的支持$ b6 ~6 r+ D& S$ L* q

+ {) k; |  i0 s! 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实例,每个
$ |2 _" R6 @/ ~9 w' t8 H( w# @CHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
# s, P  c/ w7 o" r2 k, r! c. I
% c# k+ Z' Y* }, o( V1 P一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。( e) Y( R: Y$ Q: j/ @
& ~; W4 f8 i0 H6 D: E! `
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为例,该例中有下面这样一个表单:$ d+ P, A% K+ e8 z  N/ J

) M  w+ p9 R5 X% S<form method=get action="pinball.dll?">
2 N5 r, L. z6 F$ a, j<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
  m* o/ C$ d- R$ C, L. b* V<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>
! y/ _$ B- Z1 s& a" n<input type="radio" name="Favorite" value="2"> Twilight Zone<br>7 [) L7 f( B6 X) I  X5 Z
<input type="radio" name="Favorite" value="3"> The Addams Family<br>
+ k* M3 v  K, Q& E$ H$ B& ?<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>* |: U* Z1 y: M+ S: k$ B, w
<input type="radio" name="Favorite" value="0"> I don't see it here<br>! b3 o: n) D$ O% Q& L
<br>
5 _$ j/ m6 G- }: `4 G<input type="submit" value="Show Me!">
& Y8 i2 k: i& U) B- K</form>
/ A( l8 W5 d3 n
+ N4 b7 \0 `( n, L: [5 w! {% _( R当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端" v/ I# l( c4 W: Q! l
最终将得到如下的URL串:) k9 [5 f8 ^' a5 z+ S
; a1 c8 U9 A! e/ ?3 W% h
http://www.abc.com/pinball.dll?M ... GetImage&Favorite=1+ G: ~4 ^) U) m' x
* z- v) `  i, K6 [
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
! n) k5 \" O6 A0 p( x+ p将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:2 T7 I0 o" F5 T/ n) i

$ G6 |2 l. @9 Dvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
1 g, M" T0 S! W
( s1 Z  d: Y1 K" c! ?: F& g+ B2 X而parse map需要按照下面的形式定义:
7 c3 }4 X( g: a: V$ }' o: o, f9 F9 N6 x
//CPinballExtension从CHttpServer派生而来* I6 T% L6 K" s  h
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
) D2 C2 I; w- Q+ J: |% ^, i" }9 q0 o3 ~
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice3 b& _. X5 o' Y
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) / K1 m1 L. `# c% u6 _, y( ]! g

# ~2 u8 R( R" a% Q6 ?//该参数在URL中的名字为Favorite
' X* p* w  f7 \/ X0 oON_PARSE_COMMAND_PARAMS("Favorite") - r5 g: i4 f: m3 p; D+ L

: D9 A% l- c9 c, A4 vEND_PARSE_MAP(CPinballExtension)( d; G4 K3 X4 G
" I0 I2 G1 `! h3 |2 ?: q# Z% T, g
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:# D5 |% C. `7 b& C) \6 B& f
+ B/ @- p8 J3 p! g
OnPreprocHeaders
* v* }: l( G2 L' oOnAuthentication5 N; a3 J# u. H0 \, T/ Y
OnUrlMap9 x$ r' T! v- T7 B8 }9 _
OnSendRawData
  y' \6 Z/ |* J% ]OnReadRawData
1 E* |5 }7 F' Q' n4 Y+ O: w, kOnLog
" {) k, t1 j, C1 W; a% s: WOnEndOfNetSession
. X) c6 B0 }+ r7 s: d3 m: p7 `
' p, R6 H) x; z# x  M/ ^/ g0 |MSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
/ ?! A3 g2 }% E: c  L; s
# R' O1 T/ K. v# w, }2 O7 l参考资料:% ]$ x- ~; d& c* @) j1 x& C
- s5 n% j4 R  z' i) G
1、MSDN+ {: v0 g) u" @# }
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

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

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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