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

IIS的ISAPI接口简介

[复制链接]
发表于 2003-10-13 13:22:17 | 显示全部楼层 |阅读模式
作者:netguy (mailto:netguy@nsfocus.com) , h$ |! {0 o8 C

. j( l1 z5 ~& GISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。- Z% W/ j6 f! z

% l. }6 n. M! j; f一、ISAPI接口和CGI接口的不同。9 x' Y8 j7 x3 f7 C

" U6 F( H( ^* k" z1 M6 ZISAPI程序和CGI程序完成类似的功能,但是实现方法不同。
# _+ i- O7 Q4 h* H! U# N8 V4 _( o' W$ j
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。( P. h; F7 F; T' c
' T8 C: G( P: A
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
* e% M3 c% a7 K3 x
6 z0 A- `0 K* a; H; N2 V; ]4 a+ |ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。
6 u7 ]7 x0 s7 B. m$ V1 c2 R
5 m2 |' D# V8 o) y8 }% _; C& R  K- q5 N. S
二、ISA
" G& m; Q' K2 u' s7 ~) z% [  T4 `3 a
ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):9 j0 E+ j0 g# m% \
http://www.abc.com/Scripts/function.dll?: n  X7 n  x" o( Q9 |5 W! ?$ ?8 c
) V1 ~, J6 v  p
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。: s; a) }% C& a, ^6 S! f: B
% q' G! i+ `4 Q+ j" {8 }
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:* v  F7 R/ V' u. g
7 h' {8 O+ x. O# F  Q5 |7 c
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
" C! Z& c% f2 X1 E( R' utypedef struct _HSE_VERSION_INFO
- u% f, {- Q" ^$ [{; z0 [3 e" r# P7 m" F1 F7 V+ b( ]
DWORD dwExtensionVersion; //版本号
8 i# k8 ]7 I. Y; I" \+ \3 D7 tCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串2 n. }7 E$ z+ O+ Y, h5 E
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;. \. Z" e, x* T) W* F0 M( L2 j5 D" j; J

& G6 S% M6 C+ W. d2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:
+ Q8 \# [; f5 C" W: F6 k5 J: |/ P2 x0 {6 K' z/ C- C
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
& |2 n5 h  E4 F! g* y  |; c' I7 [8 G7 l( G
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):' f1 h9 q% Q% n) k2 N# V- T
1 S. i6 X) H0 l- ~+ L
typedef struct _EXTENSION_CONTROL_BLOCK : U: C+ |2 [& e9 A) G% h( o5 M2 \
{
3 G- I& Q, ^1 C" P' oDWORD cbSize; //IN,本结构的大小,只读
5 d: d6 ~, E8 k/ o! i% j6 U. _DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
9 M1 t! s4 p& L7 O' J  THCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值* ?! G3 u. F( B) I; c8 N. k
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态; ?9 }+ ]$ A2 R( `6 o
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容$ m9 \. p6 D  A' I3 R
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD
8 S, @4 ]5 z* h! c, u7 j* \1 wLPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING
+ n* Q( v- v. D, _, c* h9 V, S. FLPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
* d- u7 Y9 G. Y& a  O6 G+ B+ VLPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
+ m0 J- ]* h, i- C# RDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH# F) v: }  x8 U: h2 J6 K
DWORD cbAvailable; //IN,缓冲区中的可用字节数
+ M) R2 Q. d+ _+ G. c- z( DLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
/ B: S2 h+ H2 |6 j& PLPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE7 x# L$ `# y0 r7 @7 X

5 O: r0 D" I/ R: m; |% p: M  ^//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
# E) N9 h0 J4 a" ]0 q- _' UBOOL ( WINAPI * GetServerVariable )
- X- P2 j0 W. \( HCONN hConn,
; e! X, i5 b' YLPSTR lpszVariableName,
$ U3 V0 U$ X( M" |LPVOID lpvBuffer,, z' q% j; \& i( N3 ?* V, z8 b4 \
LPDWORD lpdwSize );5 d  t3 t; E  ~% p2 [

4 O% e+ ]+ z7 O# {* E( q8 sBOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据+ N9 X( b0 x6 d
( HCONN ConnID,
  b; O7 W, |' X/ |3 t- C% zLPVOID Buffer,
, g$ B9 N6 y/ m9 Y6 zLPDWORD lpdwBytes,
) ~5 z. D7 w3 B1 N" n4 f' q# UDWORD dwReserved );
$ `: Q5 x; ?' `* s$ _. a) L$ {" [: Q* P. R
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据' T2 U7 }) t' P6 W
( HCONN ConnID,
2 t7 E( [7 g2 P1 S) uLPVOID lpvBuffer,  Y) T" v# K' V0 g% W- |/ {
LPDWORD lpdwSize );( M5 t+ v6 [1 v) h8 ~

' Z( b4 F* T# i6 q; G2 o* E: N; |8 P% XBOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能
& c0 O8 P2 M; i( HCONN hConn,, w& N+ l2 `2 _) n* m: R
DWORD dwHSERRequest," g- I% y) H; ~1 N! d8 [1 V
LPVOID lpvBuffer,& t& ^6 N# E/ o3 M
LPDWORD lpdwSize,
' u( O6 \) T0 TLPDWORD lpdwDataType );' ^1 N' n2 a6 Z$ n( u9 }8 X8 ~
$ l3 ^' I5 D6 ?* _( t$ c, F8 h
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;6 I0 C4 L# |+ ]) }3 r0 {% Z

  @, D" z( t  |! }在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。' a8 j% K+ O! ^" y) I
  a! x) j( D' l5 U
三、ISAPI Filter9 B2 z3 w% B3 Q+ l% x7 c
7 K1 X: X0 P: B; Y5 A8 I; ~1 M! m
ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
" S4 J8 M# s. Z/ a/ M# {. c  w( E. `# K; E
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何) C% O! S! w: o7 a
ISAPI Filter都必须引出这两个函数以供服务器调用。
( Y- c; g* x4 d, ^1 \. g8 z
) z6 O# t% O% W: B" C" ]1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
7 a9 p; f2 C9 tFilter的文件名并加载它们。
) `: m" d; I5 A: }. U; ^5 U1 u. w# d9 i
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
& m' E+ i! T! {" y4 Q
% s6 G% `% r. E- i$ Y2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:0 B0 K' O* [1 e9 u- X' M; C9 Y. `
* O* {% X" x1 E+ r2 |7 [! h
BOOL WINAPI GetFilterVersion( 5 p9 w; {& Q& O( k
DWORD dwServerFilterVersion; //IN,服务器使用的版本规范 * [2 c& m% |: ?! k, M; r6 q1 L2 ^% \
DWORD dwFilterVersion; //OUT,过滤器使用的版本规范
' u  v: {4 l8 [, J3 UCHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
! {. Q- W) ?5 @DWORD dwFlags //OUT,事件和优先级标志
5 F. }7 m1 f* t$ T8 w5 ]);- Q; F1 I( J! ^" V7 `# L

1 V2 f" G4 u. P* `2 C事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。( S7 E9 I) ~6 w9 V2 v

; x$ _7 J; H! p, o( Y7 f3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
4 H) L2 `) `" m7 q: {$ ]. M: H% Z9 n
typedef struct _HTTP_FILTER_CONTEXT
0 L5 M2 _* G9 z: S4 K2 H' E{" G: ?- ?& m9 d; j
$ X& N* H# r7 ?: B9 x$ @$ _  O& u" O
DWORD cbSize; //IN,本参数块的大小
3 e4 S* g* T4 KDWORD Revision; //IN
' r$ O- G5 ]' z6 W+ _7 ?PVOID ServerContext; //IN,由server使用本参数* Y# p6 \3 I" |$ c4 S) l' m
DWORD ulReserved; //IN,由server使用本参数2 e" j* V1 }# H- Z8 m, m$ V) F
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上5 r! c$ w3 n, w% ?/ c! n
PVOID pFilterContext; //IN/OUT,与本次请求相关的上下文# N4 w" ?' z8 B* q9 }/ o; V+ z

8 k) F# i7 y: r9 e//回调函数,取得关于服务器和本次连接的信息
6 p+ ~& I. A! i; ~! @$ r; m' xBOOL (WINAPI * GetServerVariable) ( 7 B2 ^  P+ r. W2 m, N/ F1 \0 f) H
struct _HTTP_FILTER_CONTEXT * pfc," `' x% O) e9 m6 d( x
LPSTR lpszVariableName,4 M% B9 B- W& `9 ~5 G
LPVOID lpvBuffer,
9 B9 M+ q# k; _! u+ |# O% A; NLPDWORD lpdwSize
; |& X) X, X; U# M);   d9 X% O5 j8 |3 @1 v; Z

* K$ _$ `6 ^( r' ?+ N8 p, eBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头% S2 J& V3 j& S/ _
struct _HTTP_FILTER_CONTEXT * pfc,
! |2 O' g/ ]7 g; d' `# _LPSTR lpszHeaders,% f7 |( e' o/ }
DWORD dwReserved4 t) |! Y& d6 v7 ?  z4 e
);
% O* O; e. [( _- P# t* R( |6 q6 o
3 t5 A- j9 i* X2 Z7 s5 C+ M7 z" PBOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端/ Z1 e0 t( W4 v# I1 }) p2 g8 T
struct _HTTP_FILTER_CONTEXT * pfc,0 L3 g0 }! e6 |( @  K1 t" h# E
LPVOID Buffer,
9 P" T5 ]: y3 F; a" O) L* G8 ?' ULPDWORD lpdwBytes,0 x3 ]3 u) p! l+ c
DWORD dwReserved$ @# Y0 J" c0 X' L& a2 {
); ( D& ~6 J# b  ]. `

! A9 {( s$ e! a# J/ Q1 F, ^VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。- q: v8 y6 o, n: [4 U
struct _HTTP_FILTER_CONTEXT * pfc,
4 w4 j, i" O5 |: T: vDWORD cbSize,
7 S' M, {: K. H1 aDWORD dwReserved/ k# x! ~$ V# S0 A. u0 U+ t
); , r$ h* _5 X8 ^0 y: y

' A" P7 z, x5 Z4 {/ [9 M8 k, ABOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能
/ m2 T- b5 c; Gstruct _HTTP_FILTER_CONTEXT * pfc,
+ I( z! C0 _) S! Venum SF_REQ_TYPE sfReq,
' g5 E  m7 H! w) {5 RPVOID pData,& m7 ]3 V4 e- I" {7 G5 q& y/ C7 x
DWORD ul1,
$ ?4 m& S2 Z& G" {/ I1 ]$ vDWORD ul2' {2 {& Q% v1 d+ l0 w# {0 U
);
- C! x- e; p$ }$ K( E3 Y4 A
4 z: R2 M9 n# X} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;! v0 l9 `# P6 ]4 S1 Y

1 R" w; `4 N4 O5 h四、VC++ 6.0中对ISAPI的支持/ S4 h$ X+ N( H  }9 c! f

! R# x3 M1 K5 J5 q9 ^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实例,每个
0 S! }3 P5 [" }% E+ OCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
( b2 L& z/ D4 X1 _3 a" B
' b$ I, S/ R4 l- v一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。2 L7 @, n# G+ A) a" G$ i% X- f4 g

; U( T- C6 W& P6 @: j1 P% ^' Y2 Q  kParse 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为例,该例中有下面这样一个表单:
- X, C+ W: N  L5 Y; m
& ^+ `- a1 [9 k+ |( j<form method=get action="pinball.dll?">( p: @, G; C' B# _3 z% I; f
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">: w- W; h8 B8 a. W6 _5 ?
<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>
, e# _0 y" H2 f5 g<input type="radio" name="Favorite" value="2"> Twilight Zone<br>
7 x5 L- E7 }# X$ d1 h<input type="radio" name="Favorite" value="3"> The Addams Family<br>
. ]  `3 O. U9 ?<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
8 q9 p. u1 F0 f5 U9 C& M7 z<input type="radio" name="Favorite" value="0"> I don't see it here<br>
' }) A# L3 {" }5 \7 |, I* d<br>
; E3 ?9 N( H4 O6 J) f3 J3 D<input type="submit" value="Show Me!">
3 Z5 `: |' b$ c" A. G# V: V</form>
: {  G2 R0 m8 c+ v8 d. b: ?1 m2 M% @  u- L; ]' S
当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端; o  k# D* L+ ]: \* G7 V
最终将得到如下的URL串:! n5 o; r7 X& F; u

. G5 U. I: s! ^/ m7 G6 I; p1 vhttp://www.abc.com/pinball.dll?M ... GetImage&Favorite=1
; C5 P/ Z8 e4 x9 h$ ]% _+ @. y, Z8 K' T' o: l- f. [* u& e* B
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
+ J1 d1 V6 x$ o2 k. Y0 _将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:
, Q+ C7 E5 a; x
6 U5 C. |. O& `8 |; S7 u! B  a  yvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);* R. F; Z( A6 @- E
9 o( K$ _" L9 _. M
而parse map需要按照下面的形式定义:
% N. L0 V  }' e; u' j& o8 y! v- P- m/ b2 j4 g9 u  t/ X
//CPinballExtension从CHttpServer派生而来7 I0 }$ B+ A6 N" ]6 F+ V: I) Z% _
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer) ) S5 T" |+ B- h/ X% Y& E! F# t, p9 f

  Q. a& P9 M  V# j; A//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice& a4 m) w8 e) ]5 t5 l- z- V
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) ; y7 G' t$ J6 ~. {) h1 L

; l' w0 D. A8 E2 G//该参数在URL中的名字为Favorite" q0 O8 f( U4 G: B2 j
ON_PARSE_COMMAND_PARAMS("Favorite")
# o2 `3 W$ w) q9 F! M* j
( ?8 {# B( a. t1 h0 m5 {END_PARSE_MAP(CPinballExtension)6 E7 u. p- H0 f% C/ G

/ I8 |% K) t* e' i0 n, P而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:: K8 b- z0 N: J5 X, f5 n$ V) D9 ?

1 u  o1 S& @/ Q" x- Q5 fOnPreprocHeaders
' L; d# f4 \! H- Y& N. B. HOnAuthentication: R: ]+ H  r" _2 M' p/ @3 k- K5 g' t
OnUrlMap
" a/ W* i8 s) ]0 \# F, q0 t" bOnSendRawData- @( N; Y- U3 d
OnReadRawData
' y) m1 G  R$ v+ o- YOnLog
! z0 H7 n" A7 QOnEndOfNetSession3 U: R3 ~( W4 b" D

  Q5 [/ W5 i, O5 m# l2 QMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。+ F7 r4 U- {2 b/ z7 z6 b6 j) S' V
" s. z- F/ u3 G9 Q; o& f, J
参考资料:! k4 x; a( ?5 W0 X1 O) t8 ]
$ X# y! C# H$ [5 P
1、MSDN/ v' @8 ~: D# d
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-5-2 10:21 , Processed in 0.019421 second(s), 14 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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