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

IIS的ISAPI接口简介

[复制链接]
发表于 2003-10-13 13:22:17 | 显示全部楼层 |阅读模式
作者:netguy (mailto:netguy@nsfocus.com)   X+ C5 M, G2 m( p3 [" S/ b2 f5 p0 P
& s) Y7 I1 M) K. I: D4 A" ]3 s
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。  j9 B5 x6 L2 I4 A
9 O6 K$ A* X8 N% B+ ~) q
一、ISAPI接口和CGI接口的不同。1 N* l. _# v9 A0 S
* I: H, z0 `" a7 _+ y- ^# r* U
ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。
, o0 d3 @& C5 w* ]. V6 t# P3 L. O3 z, i% S% k: d0 h3 ~
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。
+ j5 d& X3 D* ?' x# h, n/ }) O4 R: C, E
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。, c0 K' F1 S8 m) c9 Y

" ^9 z1 x) C& a- o: V; YISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。- Y: @" _! q$ P: U

# R; T4 w1 h. Z4 {6 A0 [0 R. d/ y' D5 c' p! P
二、ISA" X. z: ~" g4 `$ j+ g8 j8 {

7 M, ?3 s; n, n+ B- mISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):. D" }" L$ r: [2 X0 _$ u
http://www.abc.com/Scripts/function.dll?
% b. V' E/ H: u- j3 V
* \  r% M4 o) U. d: `$ FISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。
# Z( X, X# z, W1 q- v) _% i8 V. l8 _; V) ^8 s. G: g( p8 W- a
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:8 L$ W2 n7 {' f

; e* F  ]0 N$ [2 e; x1 v. KBOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);3 B( C! \1 G. `& a6 |! r
typedef struct _HSE_VERSION_INFO
# f$ f6 y8 p  i/ q{' B; k& }) {9 b$ f* g. D/ R
DWORD dwExtensionVersion; //版本号& u. ^, ]& k2 g/ n$ c
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
' L" S2 q- B, ^# C. M# P} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;5 ]4 @1 p. g+ J2 s* J2 J8 D3 @
6 l( F% o+ ~5 a
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:6 y* D/ K; b# C, J- d

5 D+ s$ X3 q* B, W4 o5 G% d& yDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );0 m. ]0 ?' U8 x2 Y- v% J

3 U* W3 _! z) M+ g7 g  U- WECB的结构定义如下(IN表示入口参数,OUT表示出口参数):/ b1 H- O; U% n) S) R
6 E3 L' d' a# y4 o0 L
typedef struct _EXTENSION_CONTROL_BLOCK
" A0 l. o% m+ T9 Q3 P5 X{
. C1 e  e7 R/ [& \DWORD cbSize; //IN,本结构的大小,只读
" O# ~* Z- L5 A# QDWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
6 P! R" x8 E9 L) F  s1 ]. EHCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值; L) M$ C. s$ n9 t
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态( ]4 L" |. I( w& w- P* T, i: I. C
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
0 O) [+ f2 i0 b* P2 {LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD
9 Z% s8 Z! p1 J1 s6 _LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING* i8 p# v7 w2 \. G
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
/ W6 ~; ~* o- ALPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
3 |# c4 H9 N1 z% p8 ^/ \DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH% K% p. x1 ~) G
DWORD cbAvailable; //IN,缓冲区中的可用字节数; m1 w9 p& V8 W5 L9 U' O3 X
LPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
8 m' p! |4 F4 a7 a0 x2 ~6 pLPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE
/ A. Y- a, u5 X1 s
% N- q( n$ N5 P//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
5 B3 f" I" D; U! k! o  CBOOL ( WINAPI * GetServerVariable )
9 H& a4 ?: O3 k+ i: P: K( HCONN hConn,/ `: L" ~) V2 V0 g
LPSTR lpszVariableName,
2 V& s& X7 X. v8 v8 \LPVOID lpvBuffer,
+ ^0 l3 k$ U- _8 h/ L/ x8 N! \8 ~1 FLPDWORD lpdwSize );" n$ @$ k6 I' r0 A" j2 `# L
+ K  }" a& }3 l
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据8 N! e" F3 r; G: J( x
( HCONN ConnID,1 t9 o+ P' n' w) T. V) U7 W
LPVOID Buffer,
& |. v7 C7 D' w3 `, Q: a/ A5 x8 XLPDWORD lpdwBytes,. u8 a2 N: S: ^! h: N& R# F
DWORD dwReserved );- P$ t; \; }7 O, u, F  |2 ]  }

& N1 W8 z7 v, {% q- P  z% pBOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据+ n0 |$ h1 a1 y+ F
( HCONN ConnID,
% @6 o. G6 E/ s" ?3 U& I/ e2 U3 cLPVOID lpvBuffer,
7 C; j, ?$ k9 NLPDWORD lpdwSize );/ t0 G* d8 q6 b8 a- U. p
7 G! U0 j3 B, i. T/ y
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能+ w$ E) V* w1 [4 k
( HCONN hConn,
+ u& j5 ]5 M4 R. c9 {8 \4 JDWORD dwHSERRequest,7 A) X( n0 |" ?. C2 k; y6 y4 t3 ]0 t
LPVOID lpvBuffer,
" b$ _# r  A0 g  `LPDWORD lpdwSize,
4 u8 t: Z1 ~$ r4 M6 L4 L' f4 TLPDWORD lpdwDataType );
) R3 f' B5 w3 `$ P  x/ W
4 D  s5 g+ A1 b4 z; ^3 @4 ?# l: p} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
! b- {$ v6 \1 G+ X( K% D5 {. P- e7 W$ y; J. v$ @9 n
在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。
+ _6 {; j, D" Q8 j0 u$ ~
( b- Y$ A( K' C2 \- P三、ISAPI Filter5 p: r" w8 ~5 ^+ i; s6 \

2 o9 \# C; `! ]; c3 WISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
! b0 `0 s- l9 d- z% T  A" D. t+ A* f, P- x3 i8 `
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
; Z( [' V2 c9 j) e) e( cISAPI Filter都必须引出这两个函数以供服务器调用。, z4 g4 b/ `" m0 \- M! ]

7 @4 k+ T% i* R1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
/ H+ }8 n; I" ~7 gFilter的文件名并加载它们。
+ z! I6 l/ L+ |( Z: X0 Q  U$ m# M; X0 w2 F" [0 O5 F
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
; C. ?- ~2 [, @" Z" w" R# R2 F0 u/ P# O0 e% U! @
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:$ O; A  n8 j# ~' L& u1 A0 Q0 t0 {  k4 B

3 N4 \3 k% y. P. v/ j' u4 W0 R3 EBOOL WINAPI GetFilterVersion(
5 u: g0 {! Z8 bDWORD dwServerFilterVersion; //IN,服务器使用的版本规范
7 a+ n1 A) p4 U% ~2 ^DWORD dwFilterVersion; //OUT,过滤器使用的版本规范
* I9 B4 l& O. Z. I% ?: I5 q! TCHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串5 b8 G  s, `6 Z" e# Y8 e
DWORD dwFlags //OUT,事件和优先级标志
, J. |1 y8 h$ q8 X);3 l/ f; r2 [: _' ^# {# e
/ w/ H. q# |( z* S- k  \
事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
. U% g- L. k) I! w. e$ T% K3 K, {* }' I0 W+ i% |6 |, m
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
& M# B! X6 A3 t6 H! I- R
! z  Q7 t* {  P1 Htypedef struct _HTTP_FILTER_CONTEXT* w. K3 R  m  `+ s1 H/ |% ]
{, S/ W  F* b4 p2 {$ v

! ~( @; b/ r& v& g" F1 @DWORD cbSize; //IN,本参数块的大小0 F1 E7 i+ i$ l$ ^! W' i. {! D
DWORD Revision; //IN
0 k5 v8 o  N1 `3 ~' W7 o6 |; [PVOID ServerContext; //IN,由server使用本参数! H5 _: _9 O/ `; k
DWORD ulReserved; //IN,由server使用本参数
& e. i  T6 q# Q. ?" A4 cBOOL fIsSecurePort; //IN,事件是否发生在安全端口上
2 p& f! ~+ Z7 Y7 G: ePVOID pFilterContext; //IN/OUT,与本次请求相关的上下文
. g% C4 T* P; e2 ~4 H# g! l3 o3 o" I# s) {, A
//回调函数,取得关于服务器和本次连接的信息
/ P4 u/ K/ }  hBOOL (WINAPI * GetServerVariable) (
/ Q# d/ e( U9 d$ G# l3 b9 astruct _HTTP_FILTER_CONTEXT * pfc,$ }2 _+ |( p2 y. F, U6 F' X! q2 |  n
LPSTR lpszVariableName,
6 _9 e+ Q% @6 f4 R8 Y) ]LPVOID lpvBuffer,
! f# N( J' L) K* I. ^7 y) B4 u0 |LPDWORD lpdwSize" W! Z! x4 e$ U7 g% U2 a- I) e
); / x& m9 d5 B8 j1 T& Q: ^. ?
0 d1 L( [; D- ~9 Y/ d# \* h/ z
BOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头; S9 g+ N6 q' U6 j1 `4 E/ R$ L6 `) C
struct _HTTP_FILTER_CONTEXT * pfc,5 N7 F4 ~& N. \# p
LPSTR lpszHeaders,
3 w" f, _" m: R, v) H, `DWORD dwReserved+ P  ?  a0 j- v' n( W1 f
);
& m8 Z, M1 L  \8 [4 s. A, V9 d
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端' b4 H% J4 j: L, h4 C1 s2 d0 U
struct _HTTP_FILTER_CONTEXT * pfc,
' D& c" f( C7 X+ l! Q* X7 T" v5 ZLPVOID Buffer,& |+ b* R- E* J0 i
LPDWORD lpdwBytes,% c% X# w7 ]9 i% a, C
DWORD dwReserved6 l2 Q" |/ g4 U- a* w/ l
);
2 P9 \7 _3 P+ i# [2 v# W5 m. O6 ~9 i, y" s4 _  Y# `1 N6 r% l+ ]
VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。5 G6 r+ Y$ l* F$ p6 J, |" U
struct _HTTP_FILTER_CONTEXT * pfc,6 p. ~) \% Y/ F9 l' F" u
DWORD cbSize,* O* a  r' g# N
DWORD dwReserved3 V0 M! t5 M7 A1 z; [1 D7 y5 N
);
) L$ A; a7 Y0 K' P9 v- a* w! Y1 N+ S8 d3 {1 B" W+ ^
BOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能4 n4 l7 k7 D0 I, o  [2 c3 P1 ]. i
struct _HTTP_FILTER_CONTEXT * pfc,) n3 R& X) z2 h# T: B5 s
enum SF_REQ_TYPE sfReq,
) X1 m) R0 n2 `7 j" UPVOID pData,6 k2 d8 Z9 I- w; s. u, k6 p; u8 t
DWORD ul1,
* i5 d& j5 b4 ADWORD ul2
6 ?) O+ j: H' ^( e);
! R! o# f/ j" X( V' C3 {
7 J- W1 U; b6 s4 i* ^} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;" k$ X, s1 e; g: V
8 `8 W& R/ Y" f
四、VC++ 6.0中对ISAPI的支持" m' w/ [: N( P7 u$ ]2 [0 Z; j0 F
. v/ c1 W% a% y# _0 T' t
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实例,每个
+ S; w1 @$ H5 B% w8 J3 GCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。/ I, Z$ q3 p, X0 k
/ i* }) z* M  B" l& V+ c3 q
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
% v; W  A' \: ^, U& `7 X3 y6 L3 }  x+ m. p& J0 N8 ]
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为例,该例中有下面这样一个表单:1 `0 R, P! `: w! W+ I
0 b' m) j: r) i  m% I! g2 P" c- X3 v
<form method=get action="pinball.dll?">2 l- I, m% P. g9 |5 r+ Y1 G! k& z
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
  N0 B9 e' L1 c' |<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>1 A) y0 L* Y4 w& E" h7 I
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>% L) X9 v+ P" G
<input type="radio" name="Favorite" value="3"> The Addams Family<br>$ D/ c8 T) J0 ]! s  Q( m+ Q& {
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>5 f: O; P! h$ n5 G' {: [
<input type="radio" name="Favorite" value="0"> I don't see it here<br>5 w2 t# F: @$ B- T# f2 U! @
<br>
6 g" D8 T4 l3 c) H9 y6 P<input type="submit" value="Show Me!">
  G; c+ i. m, {  K" q2 F5 I" W</form>0 Y7 k3 _  l# l( Y

# o+ g# }) {. j6 @* E$ G* c当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端7 E- O1 g4 l0 C/ h2 a9 ?4 Y
最终将得到如下的URL串:
$ B2 B2 `, e' K7 n
# n1 \: t9 T7 x: t6 |1 Xhttp://www.abc.com/pinball.dll?M ... GetImage&Favorite=1! C7 t0 W9 e" t# L
+ d5 A" }" b6 W8 L* ?8 ?' Q$ J$ ?
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数' S+ s. M9 K2 q! t. |$ \6 A+ Y
将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:
0 Z8 f* t; w7 c/ s
. |0 Q, R" J8 a5 T% V% g/ Lvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);$ R6 ~/ ^: {' H% b3 K; S" \  @
4 j6 Q/ j$ j3 Q) g4 ]0 C
而parse map需要按照下面的形式定义:7 b+ N  N# }$ m2 d- L- w
. @: K: z  M5 W7 N" U/ Y, K3 s
//CPinballExtension从CHttpServer派生而来
& L' d; j2 C2 ]* K- ZBEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
  N: B0 I* {8 l4 s: \' t# l
, H' i; b' f, `& F5 ^) f6 e) G//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice3 c1 u& l3 \" U; d; |& x0 j5 t- P
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
* q% b- i# ?2 i" m( }$ Q" c) f7 a3 j+ k6 S" f1 c7 Q1 i/ p' x
//该参数在URL中的名字为Favorite5 d. A0 B5 W: M7 g; z) c& h% F
ON_PARSE_COMMAND_PARAMS("Favorite") 0 q* ^  c8 ~* H
1 L3 y  }3 q8 _! {8 h. C8 P1 F
END_PARSE_MAP(CPinballExtension): x; c& x: v" ^3 D3 F0 K! K; O& X; g
1 p( p" [4 D! o
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:
% Q# s* n+ X, A) w0 I7 b. e
; z1 |+ O& D0 L4 a: K, x; VOnPreprocHeaders  }! Z" U9 y+ ~
OnAuthentication# F! |# s5 f' b- U
OnUrlMap- f3 O8 l' B1 ]( ~) Q6 i
OnSendRawData
1 l& d: l! J0 l: P% l4 e! KOnReadRawData
& A/ T/ _5 W# R0 ~OnLog
. l) ]" [2 _: `* ]1 t- COnEndOfNetSession
, D9 F; N3 D9 P; N+ t) P2 b' o2 a& V. }
MSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
' c& z% x1 z3 F$ s7 ]8 O
/ D6 u, s/ t8 n1 I4 I参考资料:
1 H3 @4 \1 i7 Y0 t; l' C( F& a3 p  O& |! g. _3 B$ E" W/ L
1、MSDN, K0 j; v1 t/ G4 A( _" }+ ^" g
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-9-30 13:01 , Processed in 0.038196 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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