找回密码
 注册
查看: 4535|回复: 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中的实现。 0 d5 E: ?4 t: m3 {
$ |0 B/ K$ P. ?; Z
一、ISAPI接口和CGI接口的不同。
6 Z  ]% j/ U! e. L- W" P# Y
0 e+ ~& K# \, T0 iISAPI程序和CGI程序完成类似的功能,但是实现方法不同。 # K' d+ M) J: M# T6 S6 a( Y' d* j
, x0 @* G9 ?; v; F" f
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。
% ~$ ]% G) n3 S/ [0 U+ N( v$ F. E5 b. D  A) q; K& c  ?
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
7 y" {$ q! M4 i% L5 y- M) U, h/ e
# \9 l" a6 H, j, V7 {ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。 . l6 \! ^! `. A( \

# L5 m# v- V+ U8 P0 s3 y( V% {2 q, B1 Q# s% T2 D  L4 t! l1 N
二、ISA , u3 q& f# l# f# j3 ~
9 d: p8 a0 t* G! K# d* G' V  L; j
ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):
8 U9 D( ]% T* {  v. D1 v" y/ Zhttp://www.abc.com/Scripts/function.dll?
: R+ ^6 Y2 m6 o- y
( @+ k+ e7 r- k2 c* I' t4 a' v1 lISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。
  Y9 w$ [  ]1 _; M( _6 ^. m( l% j" p& t" F4 H7 R5 G
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下: # o+ F' j/ n- t. w. e+ n* a
. M) r2 z$ w6 n: j5 q1 v; T
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version); , f0 i( v% |5 ^5 j4 u& Y# ?. Q
typedef struct _HSE_VERSION_INFO
  q/ ?. T0 q# U1 i: {{ * S9 G2 H) u% V( `, j! N4 u
DWORD dwExtensionVersion; //版本号
+ U  n: e3 e  B! E+ I( v) k1 f; YCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串 4 G4 q) _1 c5 y9 u  K7 C3 |
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO; * q( `+ J% W8 U1 J, j1 T+ R& x$ d; W
7 g& p; @7 \4 |; i  i
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:
5 m* L$ H/ b4 L4 {7 l( e
6 a; s: ?( j9 Z& {! y9 h2 R! pDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB ); " ]) j0 L% i7 n2 Q

7 k  f  p+ D# W* ^3 b2 W5 UECB的结构定义如下(IN表示入口参数,OUT表示出口参数): * q. w, o# s( ~, T" s9 _' i
! P2 @$ r) E" b, m: a; I& w$ j
typedef struct _EXTENSION_CONTROL_BLOCK
- T: o) P9 g' a! g! a{
7 V1 J% [$ C4 x, z1 K0 kDWORD cbSize; //IN,本结构的大小,只读 : [0 h: I3 ?4 Q4 \! T) q
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
$ e, o, H* T' Z- g) }HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值
. d  [( ^' e1 w+ o$ `8 t1 s" i5 iDWORD dwHttpStatusCode; //OUT,当前完成的事务状态 ' `& T6 ^* \! \$ _; C, C- O
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
- F) ^1 m- j/ w' x% G+ ALPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD ! o8 @3 P5 o/ O0 I2 G' ~! [  D
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING
/ N5 C* y+ h6 `* `8 ~' pLPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO 4 Q' T* C+ n$ b7 U5 N% N
LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED ! G/ z5 ]% k  ^- Z6 F+ w# ]
DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH
/ ?# O9 ^* G3 e) x, ?' fDWORD cbAvailable; //IN,缓冲区中的可用字节数 + v& x  z% T+ k1 I5 e' F
LPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
+ W; P7 N- L! C' ~( ?# J' TLPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE
. q/ r# w6 R+ o: f1 }; d+ B. K: B9 b$ g, l# S
//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
9 ~* S8 O. I- i( EBOOL ( WINAPI * GetServerVariable )
8 ]: P' B$ L1 ~: I; t* [; O( HCONN hConn, " E+ n3 T6 ]8 j1 F" R( J
LPSTR lpszVariableName, ' B+ V) I8 w2 F  k% b( k
LPVOID lpvBuffer, , f9 s  b% K/ u! f# [* ]8 Y
LPDWORD lpdwSize ); / {# Q4 J- A8 P/ p" Z2 R, `0 A

! C" V& U) Z* W9 @7 @; hBOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据 , ?8 j9 n" d1 b; w
( HCONN ConnID, $ J$ c$ u! h$ j2 y
LPVOID Buffer, # t" S. l0 b8 p) T9 ^
LPDWORD lpdwBytes, # x" r) z2 p' e+ b4 S
DWORD dwReserved );
4 o% G) Y) A) }/ }1 t( }) T' k- r( {, N% ^8 U2 X0 f
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据 , w' M4 g7 T* e$ @7 O: a  i; V
( HCONN ConnID,
; ~9 B" n4 b" E. U' |0 P8 O" lLPVOID lpvBuffer, ; w  {: K  l7 B1 C' `( p  a
LPDWORD lpdwSize );
4 m6 u8 s; r9 G0 s+ I$ s8 Q3 W7 I& g. `% w
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能 6 F6 d, @6 F, t7 R4 V. G' N
( HCONN hConn, 8 T! g& @4 I5 ^
DWORD dwHSERRequest,
, e3 x' _8 S, }7 V1 jLPVOID lpvBuffer,
- h5 W  G8 S: I0 L& y& w. yLPDWORD lpdwSize,
1 m* w( c" h3 J8 i$ e0 qLPDWORD lpdwDataType ); 7 r4 Q. ?: F2 p6 _. ~

6 L$ [4 Z) h/ p# {+ Y% x} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK; - K2 E) c. L0 a& X+ x' m
1 T) G, ^, z$ I( i8 @/ N( c$ f
在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。 + e% a, Z- u& }7 {, V5 C

1 J9 L( X" N5 ^! K三、ISAPI Filter
" a" ^! l; ^8 g8 o7 Z. {
7 \4 A+ r. w- w  m7 P  U# z" _ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。 9 t0 k* X1 i& I

/ N- D3 |9 f" lISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
; i) i3 S# O' N' x& C: GISAPI Filter都必须引出这两个函数以供服务器调用。 2 G! @5 e3 ]- v
6 ?6 S6 D5 c) @
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
& u. g' B# w$ R% B$ h9 ?2 I4 c. DFilter的文件名并加载它们。
! u* P% a# C9 o' L/ Z, ]; |8 a6 _' W* z
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL ) s- y* p- ]2 w9 r
0 r1 ]  u( E. c) \6 }) _
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下: # P" U% v4 G" D1 W
- P. o. Y; o: L& K  _$ e" ^4 ^
BOOL WINAPI GetFilterVersion(
; g0 Q% l% }. HDWORD dwServerFilterVersion; //IN,服务器使用的版本规范
) g/ i" z% N- a3 {$ ?DWORD dwFilterVersion; //OUT,过滤器使用的版本规范 * [& d# u! d2 Y5 b* }- @  Q; T
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串 ; M% C5 a3 H% k& Z( {0 l2 [/ U2 }
DWORD dwFlags //OUT,事件和优先级标志 0 m! `, ~, W2 }; e/ n( ~! Z
); * i' |; K- O& K+ \+ ^2 b  F; o- |
$ ]" r9 s6 m/ _) C- N/ ^+ O0 E
事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。 * I7 a7 w8 Y  l
' m6 p  ?  n1 A0 h8 \' z4 ?+ n2 t; ?
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。 $ W+ H  A3 V% `* X( F( w* k2 P  q5 S
% G$ H. k' X: Q. G- `+ k
typedef struct _HTTP_FILTER_CONTEXT
  D8 ^# W7 Y& U. [% d) h) s{ ! Z1 D! L9 V' C' d' s* b; H/ v
- ~; J+ ]' A* d" a& W
DWORD cbSize; //IN,本参数块的大小
/ {5 H: k' w+ b6 P0 S( s/ C2 c$ _DWORD Revision; //IN
3 b+ l- S9 s. Y. A7 gPVOID ServerContext; //IN,由server使用本参数 7 o) _( p, O0 |7 n) u" Y
DWORD ulReserved; //IN,由server使用本参数
0 u1 `% m$ n! S) A. PBOOL fIsSecurePort; //IN,事件是否发生在安全端口上
! P: j. z% M0 T" JPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文
+ r2 h2 ^6 i3 g; }) l1 Q; ~5 {8 {7 J& L6 {
//回调函数,取得关于服务器和本次连接的信息
9 z$ n! a+ |2 i& R, x, b* @BOOL (WINAPI * GetServerVariable) ( / p, X! D# s9 P0 V5 t1 I
struct _HTTP_FILTER_CONTEXT * pfc,
1 |% C* s2 _+ ?7 p7 FLPSTR lpszVariableName, 3 o9 D9 x6 W/ [3 ^1 b. }
LPVOID lpvBuffer,
9 k9 f/ P6 ?9 z5 yLPDWORD lpdwSize 1 _' S" J* c2 I$ M( z3 @# [
);
1 |) r9 P9 P, L* f1 Y% `9 B# |7 l" K
4 _- t: z& S0 x  UBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头 0 d( ?. P7 ~6 O& c: u
struct _HTTP_FILTER_CONTEXT * pfc, ! s, h& h8 z0 G, d) D4 c
LPSTR lpszHeaders, # w% h( J& x& X- ]3 v' l
DWORD dwReserved
# a2 L0 S, y8 d- f' d/ X; |; B% D); 5 b* T$ g% a* ]4 |$ c' Y
* T6 T0 h5 ]2 e( w3 A  Z. }- [# I/ P
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端 / n! N5 V% {. z
struct _HTTP_FILTER_CONTEXT * pfc, $ A/ I0 D- m# Z
LPVOID Buffer,
: j7 b* I6 P- k* P# d" ~2 uLPDWORD lpdwBytes, 6 F: N, m, q3 e9 W3 `1 D: e) i
DWORD dwReserved . Z: c: |$ g& v& u7 r7 F
);
1 z2 g9 [; q7 Q# ]9 e$ ~* R( {! U/ k* q. v# l" q0 R. i4 X
VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
1 V+ ?# b5 C7 Z' r' N6 ystruct _HTTP_FILTER_CONTEXT * pfc, 2 @3 @6 t8 y$ [- T8 i
DWORD cbSize,
$ s0 O) c* \# [* Z1 u- ^DWORD dwReserved
- a' u4 G5 g4 |  h);
) L% L9 a* v4 ?. }0 D) f" [
2 q2 b. m+ M9 e2 \7 yBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能 ) ^1 G  L, n. ]/ i6 G/ ?- d
struct _HTTP_FILTER_CONTEXT * pfc,
- `2 |' z+ N6 V. ~enum SF_REQ_TYPE sfReq, 3 e; z- V/ u/ K& n! L
PVOID pData, 5 g( S6 z  E) v4 E2 B7 c1 ~
DWORD ul1, " j+ W+ i8 Y# |$ L8 S$ Q
DWORD ul2 5 v' Z) W6 g% ^6 \
); 1 K2 Z: l+ _9 }
$ U1 N! A' g8 W2 ]. r. L( [
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;
1 ?$ v3 _7 F( g" h2 r) u
, N6 K2 r# L' v: y0 [四、VC++ 6.0中对ISAPI的支持 ' X- K: u/ v. J  _+ j8 v+ J

! P2 d& R* K4 W6 [% G+ C5 jVC++ 6.0中定义了5个相关的类以简化ISAPI的编程工作:CHttpServer、CHttpServerContext、CHttpFilter、CHttpFilterContext、CHtmlStream,这5个类都没有父类。其中CHttpServer和CHttpServerContext主要用来编写ISA,CHttpFilter和CHttpFilterContext则用来编写ISAPI Filter,而CHtmlStream则用来操作内存中的HTML文件,为其它的4个类提供服务。CHttpServer在每个ISA中只能有一个实例,一个CHttpServer可以对应多个CHttpServerContext实例,每个
# x; p* r) g! F8 ]2 TCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。 ' S$ D  F. i% c6 \9 V' n" s& j
' t' [2 R, Y9 {" D$ R: T
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。 2 {7 F% o5 V  N# `6 x

0 Q" ?- L; M% z; I; 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为例,该例中有下面这样一个表单:   U2 u! R0 C$ K

! _' p2 ]% y8 K<form method=get action="pinball.dll?">
; i# c$ T* \5 Y8 I<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
( {$ D: f+ B6 i2 Z. ?<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br> & d) p+ t" k: j* ]+ H4 E0 E- M# U
<input type="radio" name="Favorite" value="2"> Twilight Zone<br> + t8 b. |( f" [  y, \# u, s/ |
<input type="radio" name="Favorite" value="3"> The Addams Family<br>
' @1 l4 Q: t' ]% l( _- J0 |4 Y2 a& i) ^<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
/ m* m. @: c0 L3 T+ o1 Y3 r<input type="radio" name="Favorite" value="0"> I don't see it here<br> . F" e& N  X& O' d! `5 H
<br> 9 M, S7 |4 n- C" g) \* t5 a
<input type="submit" value="Show Me!">
- N# c8 [8 b0 a</form>
+ S5 c. w2 `+ r' \1 `: a, b
* W, S; A& |9 w3 b8 ?当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端 7 |* [( w" @+ a% v, [
最终将得到如下的URL串: 5 Z+ e- L6 f/ h) E0 J
2 J. ]. K( J! ^$ _* I; j) K
http://www.abc.com/pinball.dll?M ... mage&Favorite=1 ) ^6 g6 J  q- u

9 t# ^9 C, g; @7 p在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
7 w( k5 k. A" e, Z0 K将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite: " A5 s) d. [+ u. \# Z. @" |3 {4 I

4 O) M4 S  |4 U% C+ y" ^5 d6 }2 Vvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
0 C$ \' I. E" p) _
- o7 ?: V' |7 ^6 c* m而parse map需要按照下面的形式定义: # {7 x6 [) l/ T
& T. \$ l1 h) Y! g/ T+ f, S0 V
//CPinballExtension从CHttpServer派生而来 * H' u# ^6 Y2 m5 T% Z8 }
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer) % x) L- o$ f) q! |7 o2 M4 x/ A
; X$ S* U& u2 f! D- F- L
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice
8 w# C  l5 k2 l/ _3 B4 }ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
6 `+ _& ^7 l: T6 s. ^# E# O0 }/ B
//该参数在URL中的名字为Favorite
1 u0 o) [) F7 m9 JON_PARSE_COMMAND_PARAMS("Favorite") / z* @% k" n! ]

0 t- m3 [) H9 N3 `0 hEND_PARSE_MAP(CPinballExtension)
) t; n; a& ~% |# @8 V: F% m' [( |* ^7 r
5 q, B% U0 Q2 h; \而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件: $ f1 g3 \/ P, A1 a6 o" ?% Y; [& T, w
. B: z" D. d8 A) Z" y$ c
OnPreprocHeaders
3 I# R0 i3 g, M& k/ f1 E0 }OnAuthentication
( u% A1 n2 S' w* ~, fOnUrlMap
2 {8 c- N. a) B% W" Y" `OnSendRawData
3 Y# s9 q3 k  _OnReadRawData
( S3 R5 r5 [! k/ G8 ^8 T0 K9 ROnLog 0 F" [  z& w# L; p+ G
OnEndOfNetSession 4 p  r! L! r  G7 H

) v7 X& B0 Y6 W  cMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。 ; o  T7 g, y! A( `; ^) @  a9 a) l

: J2 `: W. r- ]1 [/ a) m9 X) P5 U参考资料:
5 q3 z& l. M. e5 b  K1 d2 ^6 Y& L% g/ n2 \& q) P
1、MSDN 4 @8 `: [% l5 ~3 {! h/ |
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-10-1 22:03 , Processed in 0.016543 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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