找回密码
 注册
搜索
查看: 4422|回复: 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中的实现。 ' h0 K5 k) {; R9 h

* h& x6 j' }9 v) a% d9 J: k1 m# B一、ISAPI接口和CGI接口的不同。 ' |4 ^: q2 Z+ a5 H! L  G' Z
" l" O0 f' @9 R/ f
ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。 * Z; D2 h/ e- [
, k$ x& x( g0 L, I- e; Q3 g$ K7 q
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。 / n" |; \$ ^8 W: M& ]8 z+ B* B

7 u8 R0 I( `$ T3 y, r# v2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
; P! C) y( R: V! A' Y" _. C$ }( K1 I9 I; s' M; q  e: x
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。 + G8 \; C+ u* L1 j
4 y" \! N6 d# x9 }

: C( U; v3 c# w; l/ s二、ISA , M. Q, Y  C9 o! f/ _% R) I  S

5 v4 ^  r& m/ b8 ~+ e2 [. k9 EISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下): & `5 h: i* i) ]# ~. }3 p& r/ B: {
http://www.abc.com/Scripts/function.dll? 7 g: A8 s- O& r: {8 \" t7 W, f' O

  p! ?, P, r$ a9 HISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。 - Q5 r: X4 ?8 p4 X0 O, E

! N& z( K1 J5 _& i) J$ c! {# o* C0 Q1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
) A3 i0 N- m4 W; h
* K" F% ^% r6 w3 @' A' _BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
. x8 S  R8 C0 J' b' d# b  ktypedef struct _HSE_VERSION_INFO & A) G, O4 q" F4 k+ x
{ 5 z0 A/ J6 S% I& e+ ]# D
DWORD dwExtensionVersion; //版本号 , E% D# N% j  p9 e
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串 * ~) |6 T$ ]' M. ]3 g
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO; : `9 P6 L/ l# t9 y! [4 o! S

& o! g, L! o- [6 J- u  h0 j2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:
; P' r, ^  o6 l% R
: _; }" t% W% k4 TDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
) O2 n* p, O' t' y- Y7 G6 p0 Z- l/ [" Y! A4 a7 L2 H: Y( [' W
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):
" D0 x) D; f, }1 ?+ L1 ]) F$ p" X. p1 J# O( o* c
typedef struct _EXTENSION_CONTROL_BLOCK ! Z9 b0 w. o- ]' _
{   E: ?6 t* A! l- x/ \% f8 Y. O
DWORD cbSize; //IN,本结构的大小,只读 " @! V7 n' r6 j) Z# L
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号 9 I/ ^3 A) [2 n0 Y; Z! U: e
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值
- w# y' {) b- M- e% a% wDWORD dwHttpStatusCode; //OUT,当前完成的事务状态
6 d1 z: ~) f8 w7 b; [CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容 " \4 V* F6 {. T- F) j7 g7 q2 @9 d
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD $ O* `2 p3 J) [7 J
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING   Y% F5 B$ X7 }5 s3 I; }
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO 7 d7 Z6 d8 t# A: h
LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
4 D4 @7 c3 r$ \" tDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH 3 o7 O1 `% v3 `! J
DWORD cbAvailable; //IN,缓冲区中的可用字节数
& b( X# h- R8 u8 LLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据 $ r+ m8 Z1 T  P# M$ O) w+ T
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE 4 K+ _. ~+ J) d& ^. ^, i$ j5 J2 @5 f% K

9 o- J: k: _' G4 Z8 |$ Z4 h& x0 r. H7 y//回调函数,用于返回服务器的连接信息或特定的服务器详细情况 6 D; _0 }% N3 e( B7 A
BOOL ( WINAPI * GetServerVariable )
8 }5 X+ U) v0 P0 i* `( HCONN hConn,
5 O; j' E  n" z! sLPSTR lpszVariableName, : O$ C: T% I5 M- U
LPVOID lpvBuffer, # J  y8 v+ |# N" b1 x
LPDWORD lpdwSize ); ! Q* h2 t3 _( |" e

8 m, O7 }1 [: k3 {, q8 \$ |( c" EBOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据
1 W  Q  V1 A9 F% H; v( HCONN ConnID,
8 v7 [0 K0 x/ \/ K7 [LPVOID Buffer,
1 S$ h* K, P0 h9 z. M, A' ~LPDWORD lpdwBytes,
1 w( S" K4 T7 L! o8 T  _DWORD dwReserved );
- V  V- W8 X% H' R2 [0 s! B- u! P2 {. [, h; e0 M# v' ~. Q
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据
4 M5 @/ j* E  n$ S7 J, X( HCONN ConnID,
" ^6 O5 `( {9 q6 x7 k9 ^! sLPVOID lpvBuffer, : [" }; D0 P7 e7 r; _) [; x: Y9 c
LPDWORD lpdwSize ); / s# b7 _# S" ^" i, X2 r) u

& Y2 j! I1 P! S9 Q# U  }$ v% n! T- xBOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能 * N. R5 Q$ f8 Q
( HCONN hConn,
( t8 J6 |: h5 S3 |! A# VDWORD dwHSERRequest,
% }5 N8 t- g3 k+ ~7 lLPVOID lpvBuffer,
2 l! P$ t( k; Q2 U2 z( Y# S. K" m4 {LPDWORD lpdwSize,
) \# F% a9 l5 VLPDWORD lpdwDataType );
8 e3 ~4 a+ X1 E6 w
& n. d8 d3 S  L} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
! C$ g0 I9 ^3 e# Y4 M1 e' v# I9 K8 _7 k7 T: |" Q
在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。 : B% l1 p$ g0 t0 S& N7 ~
2 v& L, u/ n5 i; d; b
三、ISAPI Filter
( o( ]" f0 w1 `+ X" E1 D4 x: D: r3 H- c7 R
ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。 & c/ v! h0 {+ E: M, Z. f

2 f! [" o% i$ m0 F4 y' T0 ZISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
( |+ C7 d) z1 Z1 p* A4 CISAPI Filter都必须引出这两个函数以供服务器调用。
) b& w. F; j6 e  ]8 U) L8 r, R4 B3 y' e( W
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得 ( F5 L! C' |' G- ^; k5 j. r
Filter的文件名并加载它们。
- }5 s5 ~8 }( U# F
% u& S9 l( H/ D. [( R; rHKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL / N7 T1 m# v$ k2 `4 \, M4 \
( a: x( r" F' I
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下: * B: g4 K; ^6 E( T6 I( R
$ d2 n9 B+ W, Q. m( C  O
BOOL WINAPI GetFilterVersion(
9 e8 C0 g- g2 N5 T5 U% rDWORD dwServerFilterVersion; //IN,服务器使用的版本规范 0 c1 r& i5 l; X; ~4 \& ~) e/ v
DWORD dwFilterVersion; //OUT,过滤器使用的版本规范
5 C' o+ P$ Q, G  I/ R% s  QCHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
$ @, {8 W6 h# Y- ^, r8 Q; qDWORD dwFlags //OUT,事件和优先级标志
9 R* g8 a$ @8 U4 b% D7 B+ t); 5 ?% s; z4 q; M+ @5 P3 ]& f7 u$ @3 d

- A+ ~2 D* G# e6 j事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
& A3 u9 e. s4 U7 k; J
/ `0 ?6 U( t5 ~$ h3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。 ; H8 P# H3 {; w8 L. F; }

% i4 m% W: r  M; B( x# _: Ltypedef struct _HTTP_FILTER_CONTEXT ! j9 v1 R+ O* X# M& a3 U; L3 E/ R
{ 1 \( X; J8 ?9 W& r( ]+ {" B- p* l

# R* U; L) }6 ~) i5 NDWORD cbSize; //IN,本参数块的大小
  @  n0 S, L( b& |( y' VDWORD Revision; //IN + A2 B; P4 L* f7 R
PVOID ServerContext; //IN,由server使用本参数
3 b: Z* r# e. q% v0 Y( }- VDWORD ulReserved; //IN,由server使用本参数 4 |5 S7 g8 K6 N+ s8 l5 b( C# h
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上 ; m/ c  e; y3 b. G2 q% |) Q# L
PVOID pFilterContext; //IN/OUT,与本次请求相关的上下文 : V$ u. i, W- i: f- ?
$ c/ ^/ t0 D+ E
//回调函数,取得关于服务器和本次连接的信息
% z3 h+ \7 b, z0 K2 E  dBOOL (WINAPI * GetServerVariable) (
( M2 d' ~7 R8 K. r/ Sstruct _HTTP_FILTER_CONTEXT * pfc, 7 y4 U) Y/ }/ k% @
LPSTR lpszVariableName,
) w: l: @; D1 tLPVOID lpvBuffer, ) ]) u# W" z7 ^! }
LPDWORD lpdwSize
5 ]( k9 Q  d6 E) R5 O);
' E& \6 F: y+ ?2 I3 ?4 v8 p6 w! A% G5 P8 }
BOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头 : N6 A8 [# W5 d. U/ d3 G+ ^3 `  q( f) {
struct _HTTP_FILTER_CONTEXT * pfc, / L; R6 a$ d5 h' z% j7 x! P3 e
LPSTR lpszHeaders, " `8 g' {3 t: l* D. k5 g$ ]3 G
DWORD dwReserved 6 q6 e- z1 [! p% V% {
);
+ T" E9 k; t: y8 {6 O4 B/ y8 @; s! N$ w( x
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端 + q( E7 Z! C! l9 `/ I: x* U4 o$ [( m
struct _HTTP_FILTER_CONTEXT * pfc,
! r& d; p! \' U( ?/ {LPVOID Buffer, 2 `3 h2 I* w. ~. D# C5 Y: z
LPDWORD lpdwBytes, & l9 I: i( I3 D9 _. U; e
DWORD dwReserved & b# f. a+ g4 g/ K
);
$ \  v& c3 X1 N1 H% O% n
/ n6 R7 ~! S$ c7 j8 l& nVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
: ?) ?  }" F( Q/ Ustruct _HTTP_FILTER_CONTEXT * pfc,
) U. K- Y( B4 s; wDWORD cbSize, ; T, }. }" e$ ]  x4 M& F
DWORD dwReserved
( B& F' ]; q7 s' Q* v3 p);
# S4 ^- P9 u0 a, o
7 w- @* i- Z! V( oBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能 3 B: V8 H) N; C& j7 J
struct _HTTP_FILTER_CONTEXT * pfc,
, B- `% g1 e6 P8 D- d1 T. O7 kenum SF_REQ_TYPE sfReq,   S2 Z; k* o+ `' b, D" K+ `
PVOID pData,
, ^, C8 }8 }2 K4 F3 v- I& v: d! _7 xDWORD ul1,
3 y& c$ T2 }, U. \- Z& v) {DWORD ul2
5 V! a4 A, K: D$ ~' }# ~7 z+ Z); . g3 q9 M# ]  t: M3 I
2 ?. T. K4 ]9 t/ S
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT; 5 h) z# y+ }9 v0 |: o5 K2 g7 d

, @* v+ R7 r; T) F- {( p# O, S  s) k四、VC++ 6.0中对ISAPI的支持 1 p( k# |) ~1 O7 w

: X  k% U6 s5 h* @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实例,每个
! ]' c+ N( `* m4 \- Y$ lCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
* M9 ?( v1 x6 Z- m/ R+ E5 x
: d4 M1 {5 W8 w8 ~! L; O/ L" _' W一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。 - ?) d* P. R+ D' ^! X
" S! t' R: d0 x: @
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为例,该例中有下面这样一个表单:
/ I5 x$ i! J6 J5 c
7 @) I. u, Y3 X% D, R" y<form method=get action="pinball.dll?"> : Z) W: g- d( R! B  K( v+ G
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage"> 4 z( a" N# b1 c0 @# A5 L7 K! P
<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>
& i. J" ]) E8 e; ~<input type="radio" name="Favorite" value="2"> Twilight Zone<br> 0 d6 g2 i+ ]' J! [$ P. d# ]
<input type="radio" name="Favorite" value="3"> The Addams Family<br> ) m* H' B2 C# W+ ^
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
7 B; L3 I* X1 H, u! g* ?<input type="radio" name="Favorite" value="0"> I don't see it here<br>
2 H8 Q4 F1 c) B% C$ v5 F<br>
8 R- i+ G. S$ k1 e7 ?' H/ \; i<input type="submit" value="Show Me!">
9 D, T/ b* k  W: g( y/ U# |, I3 M</form>
! Q- I* j& r/ Q. J6 x' w
! _( O. Y) L% k1 O当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端
" D1 ?7 B$ w1 K/ J最终将得到如下的URL串:
, Q- ?! \3 ~6 m
7 u& Y+ k/ e( lhttp://www.abc.com/pinball.dll?M ... mage&Favorite=1
. [: C- T# o1 i9 N& }7 U, a7 L9 V; N! x$ B4 ]
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
. e: c& E$ x( G3 k5 i将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:
# M* ^2 E# M4 k* n% S# b+ c( ?7 M0 p! W2 K
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
  o2 s# `8 M7 A0 i4 T
' X( A- k$ J& Q; p; G而parse map需要按照下面的形式定义: 8 E; Z8 q, {6 l! J

2 S+ I% g: [/ a" V6 @! N: B% E//CPinballExtension从CHttpServer派生而来 . t6 D$ E1 `; n5 t* p
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
& X' ~8 b* a! G/ G0 R  Q
; S) _4 O; C1 G1 b1 H1 x- [/ D. N//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice
3 r6 c4 v$ b  fON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) " U. ~, L1 K- a
3 G% L+ h- O. [2 N/ ]6 U
//该参数在URL中的名字为Favorite
# F" |. }. U) ION_PARSE_COMMAND_PARAMS("Favorite")
2 B$ o  ?' P/ c: K8 {/ V* m8 v- O* b5 ^4 M
END_PARSE_MAP(CPinballExtension)
- H) t- n, u, v# p& V8 ]* ~0 `! E( M4 k3 F0 S( H
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:
! _6 o6 f3 R2 t! [
2 F) C5 E& |( g3 H3 F. `5 R4 ZOnPreprocHeaders % |% z0 t4 S' t/ A; S
OnAuthentication 9 r, n3 h2 K7 ^% P& s; ?+ z& ?
OnUrlMap
  o8 K3 Y7 t5 `6 {6 i! _OnSendRawData
; ], S, I0 o- ^( S0 z+ COnReadRawData
1 j- Q1 h  B. v- p1 Y  YOnLog
6 @/ g2 @5 H" d% Y$ B1 _OnEndOfNetSession $ q5 ]$ ^3 |. _1 `- M
9 {$ I8 w7 n3 s  J  t" Q- ~
MSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。 " S! I3 \5 [0 |8 Q. @
# q6 k$ |& b& m$ a0 n* ^6 l0 Y
参考资料:
; g5 S+ a) d$ j% ?$ N9 ]- u! x( _* [% C2 W) Q2 C" c4 h
1、MSDN
  u" V3 p+ o) [- P0 ?2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 12:17 , Processed in 0.017618 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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