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

IIS的ISAPI接口简介

[复制链接]
发表于 2003-10-13 13:22:17 | 显示全部楼层 |阅读模式
作者:netguy (mailto:netguy@nsfocus.com) % D9 S. A8 o4 t7 x1 R. X/ x
( X" }: K( G3 n% S- I; q
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。6 V0 O- S; C) E

4 o7 k1 z+ y  P  c( }2 m2 j一、ISAPI接口和CGI接口的不同。
& ~& j# k3 M; F% I7 G5 J/ D! N
4 {& C3 w+ ~- Z! z' vISAPI程序和CGI程序完成类似的功能,但是实现方法不同。" ?. _* G! [$ q, M, q9 P, r

5 F! M5 w! @3 n- g' c3 ?3 z. u1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。" c* A4 |3 y- R$ P7 [3 Z+ L/ k

. o2 p+ M, |/ S: l1 o2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。9 X0 @( ~* W! F, e# p6 Z8 o3 S) N
9 h: K# D  R9 f- G
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。
+ {  |  `2 o* g/ d7 X  _- n! M& I
! j  l; I8 k, }% x- j. N5 W: M& T3 O
二、ISA
- Z! ~! J; v4 [6 D
1 \. |/ ?( L7 q  FISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):' K" \0 L1 O% \" B+ }5 T# j
http://www.abc.com/Scripts/function.dll?+ [" d0 ?8 f4 u, z9 F  \% o
3 E% Z5 n" b. E  a6 I
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。4 ?+ ^& ~1 |7 X5 S5 S: b8 z
$ ?, P( b3 b& j
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:' A2 Q0 B9 g- V8 ~7 }2 D! q- |8 e

9 J; p+ k9 @; F) e& c+ r5 c/ zBOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);! G# b- J$ ^' x3 _8 ^3 d% U6 F
typedef struct _HSE_VERSION_INFO
, {5 [7 g( S7 D6 ~! O7 t, V{& u$ s) A6 k2 l6 v
DWORD dwExtensionVersion; //版本号, ?. }/ ~( r) C3 P  q
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串4 @1 O% `0 ^: z( t, v
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
7 F, ~3 E1 i' k; G3 Z& w* |& [' X+ U1 \# k/ i( ?$ u& U% z5 n4 \: V
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:
. M7 Y) \4 Z7 w, r' b5 K- k2 ^5 s1 H3 X$ X; c
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
  p, Z6 m9 u3 I% V' Q
9 H9 g) `# m" X  V2 Y8 ?ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):
" O$ a; b  B8 J0 K* _3 d8 L
+ N8 O, i9 @: n5 B! ntypedef struct _EXTENSION_CONTROL_BLOCK
1 W8 L1 B1 r" r( S# H" B{8 o. M6 Q: a6 e: i
DWORD cbSize; //IN,本结构的大小,只读8 k" S9 D+ g7 M# ^
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号  d+ `4 }, P* X
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值8 K: D! X! L4 H( ]
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态) h: l4 T% b; ^$ ]
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容! t3 r  S9 d& D6 W  _
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD9 ~  k+ `3 W8 R- l6 B: |- q
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING) H3 l" C: p6 V( o& H4 x$ {$ @
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
% p/ s5 ?; ~1 @% L; _1 R" fLPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED8 r- f" `) G3 D2 R
DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH, [# d4 Q) [8 Q( c* K% Z5 u% k
DWORD cbAvailable; //IN,缓冲区中的可用字节数
, ?7 U8 G+ p1 Z* y4 C5 F8 o. {LPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
" U  U9 F. Y1 ~- d8 pLPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE
9 N% g  B* _, ?) t0 g0 b; a# F- H2 F6 t, Y8 v. e8 O
//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
; V  v6 X. C. N6 O1 `7 q0 ?" j3 uBOOL ( WINAPI * GetServerVariable )
. H1 e' E: d; N" U2 @( HCONN hConn,
- O$ e  m! ?  h6 {LPSTR lpszVariableName,- P* ^+ D- q  [  a9 k
LPVOID lpvBuffer,
$ O" h& q9 q% ^8 n$ h. o! T3 NLPDWORD lpdwSize );4 C) Z/ L- o+ P! f+ g& v5 M# t: w

- m8 K: _) M, {0 iBOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据
; q3 g  U9 ^2 G9 P" Z. O- Q9 ~( HCONN ConnID,4 e, ~7 x: o  k- n
LPVOID Buffer,# t2 i- ~/ i" x3 b- b( |1 e5 x
LPDWORD lpdwBytes,
/ t$ R+ m3 O' w" t" cDWORD dwReserved );/ c" S0 i' g; l: x4 n6 M  S/ g7 C6 i

( j  X8 {! |$ {BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据( h* n- }; k4 |4 {0 S
( HCONN ConnID,
$ V5 h3 t  s3 l* G! u" aLPVOID lpvBuffer,
1 b" @6 h+ W- FLPDWORD lpdwSize );
" E& O/ N5 e% l% z3 ?$ I6 q& b* C1 ]# m9 R' s
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能
( ~& i# ?* V5 N2 b( HCONN hConn,
4 U( ~: j' Y) ?4 c/ Q9 |( eDWORD dwHSERRequest,$ q+ |6 d1 }) {% e6 ^
LPVOID lpvBuffer,
* G! K5 b6 N1 vLPDWORD lpdwSize,
- O. X0 ^" e# uLPDWORD lpdwDataType );5 ?: f) |* L7 D  o
/ ~  S$ Q( A- ^  I
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;9 k+ U" p" ?* V" b6 s
) e; ?$ k0 }- ^" H0 I$ d  o6 [
在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。3 R$ g# u# n- Y
9 ?0 _" q# X/ T7 c& z& L% `3 P
三、ISAPI Filter- ]- ^; D3 e3 Q' \$ _7 ]

, j' J: v: A* r3 ^& MISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
9 y1 j- d) t* z9 ]  ]% F% D
) R6 w* o; P: J8 |2 `4 JISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
$ m+ p! j: A# s4 P+ ^2 KISAPI Filter都必须引出这两个函数以供服务器调用。- [' q8 G) f- Q4 ~& X! c

& {% I- D6 w" ~* b% Y) W' r1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
7 h& _2 y* t1 e3 ?  u1 O/ AFilter的文件名并加载它们。
- w' F4 Q, z6 f4 k% c
$ p2 Q1 k- {% M3 Q0 Z2 CHKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
; }! D, Z6 _0 G% n3 i, L$ m0 v: e- U- d: @
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
7 {  q; Y" \/ w6 L' n. P' @0 w. G! s" P% q; }
BOOL WINAPI GetFilterVersion( , w! U) Y1 N% n, W
DWORD dwServerFilterVersion; //IN,服务器使用的版本规范
- M, L6 E/ b+ {0 \9 @/ qDWORD dwFilterVersion; //OUT,过滤器使用的版本规范
4 e1 h% W. a8 ~! ^0 xCHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
+ f. M& o' a/ h- w6 e0 X2 Y) V- X8 }DWORD dwFlags //OUT,事件和优先级标志
, N  F; ]: B8 G9 v2 o);! Z7 h/ O3 M; x3 b+ ~

: E6 H- t  z5 q1 ?" w事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。! D  {% c3 h7 q# O) ?- J
# F# m9 _) Q. e
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。, Q7 a$ }6 p- A$ G2 J7 a) B1 I
) b2 i( f5 c& Q. d7 C$ A3 H
typedef struct _HTTP_FILTER_CONTEXT
9 E' Y7 i2 M; B$ Y" }5 y& S{
: f4 j" f2 p  h7 `, V1 y1 a  L$ |1 y7 B! X9 a3 u5 N/ I
DWORD cbSize; //IN,本参数块的大小
& F. h8 ~* v7 {0 ADWORD Revision; //IN
9 y& Q/ g( n" e2 J; g. _2 j- u/ jPVOID ServerContext; //IN,由server使用本参数( Y" C1 p& \1 f& @3 V3 j
DWORD ulReserved; //IN,由server使用本参数
2 q; C4 u- [* I/ ?# _  H/ J+ KBOOL fIsSecurePort; //IN,事件是否发生在安全端口上+ ^, ~! X" h+ c8 V  @
PVOID pFilterContext; //IN/OUT,与本次请求相关的上下文$ G; C0 t8 E, x

6 |% T7 L  G: @% T# v1 x//回调函数,取得关于服务器和本次连接的信息& w' p7 y) h, o
BOOL (WINAPI * GetServerVariable) (
$ X, g3 _3 Z$ Cstruct _HTTP_FILTER_CONTEXT * pfc,0 ~/ t  i- i) U% R! k: d3 V3 C7 X
LPSTR lpszVariableName,
6 l: _: j1 T5 p' m( [& ]LPVOID lpvBuffer,. |: @$ {7 i1 z1 i, ^
LPDWORD lpdwSize/ G# f* x3 v$ \* j
); . F9 u/ a4 C1 E1 [$ o* N% d

% k! q: E; J6 a) KBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头
( O; i/ V- X: wstruct _HTTP_FILTER_CONTEXT * pfc,7 E0 E9 }4 d6 M9 b7 V' [
LPSTR lpszHeaders,- S/ j+ ~  r" |1 m4 d8 @0 U
DWORD dwReserved
9 r7 ]6 ~* B' \: C6 F); 1 p5 B, E0 S! F3 X+ u8 K0 U" M

9 i6 j% R+ }2 t6 T4 h) |% WBOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
# U' e  R( O* }6 C9 ?, C" O' x9 ]struct _HTTP_FILTER_CONTEXT * pfc,
3 M; [- t- g; D& I0 K! tLPVOID Buffer,! W% t# x5 f7 l; K! k4 W3 q
LPDWORD lpdwBytes,
$ T7 q0 k& e6 m2 t! |8 ]: {DWORD dwReserved
  D+ X1 D2 M3 g  d% g  ~3 f); & |7 V2 M# {+ ]0 Q6 U/ z
- r+ F% m. i5 L* ?5 |3 H9 A
VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。; G3 w$ a$ l& d9 Q
struct _HTTP_FILTER_CONTEXT * pfc,
4 G- s( C+ B$ `DWORD cbSize,
$ \( x5 E+ X, m& i! nDWORD dwReserved9 P$ S) z. I; W) p. m/ J
);
0 d+ j4 t0 {) V% ]! V- M- G' \
, E2 X1 C% m# [! W. JBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能+ l% y' x8 s* [5 O: \$ y
struct _HTTP_FILTER_CONTEXT * pfc,
. J, f- o. I3 E: \enum SF_REQ_TYPE sfReq,
6 t+ q1 ?* c. cPVOID pData,
4 j% s* j( m$ ?; P; X' PDWORD ul1,
9 A8 @- ]  q% K" z/ }! Q5 n9 V1 K' WDWORD ul2) E8 k0 i0 L: ]5 _
);
/ d2 \9 u* P8 H$ D9 C' R- @) W/ r8 o/ w4 M7 ~2 j
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;
; o: T0 a/ Q/ ~
9 Q$ _1 w% y' F, ~- T/ N1 N8 j  s四、VC++ 6.0中对ISAPI的支持9 ~+ N" V1 X7 a, V
# J. D0 \4 ]7 d# A1 \$ n
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实例,每个
% u! o0 z( O5 {6 ]CHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
# d( Q# \" p- ^+ H& R+ n7 T) G$ S( U1 v, o
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
9 h* M, `& K% R7 |
% ~3 F  U  R% ]6 K% D0 o; T2 IParse 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为例,该例中有下面这样一个表单:
$ E5 r3 ?/ {; ^# J, n
4 U. `- ]$ O, O) K5 p<form method=get action="pinball.dll?">
4 u8 v3 @# B7 j<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
/ ^. p! N2 R; F$ f9 L) t<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>
% ]3 ?& Z  k( C; [  \<input type="radio" name="Favorite" value="2"> Twilight Zone<br>4 ]6 E5 T: Q6 L
<input type="radio" name="Favorite" value="3"> The Addams Family<br>6 J; `4 l; c, ]/ V
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
4 X( U2 w( c5 L7 d<input type="radio" name="Favorite" value="0"> I don't see it here<br>8 r1 b/ y/ q! K- x. |8 c; D4 d
<br>
- ?+ \0 j7 W9 v" `<input type="submit" value="Show Me!">
0 v6 `3 M5 d) B% T' u</form>
: @' L) `7 J4 r3 L9 L  [/ O% F  p  D9 Z1 R# u. Z% B" o
当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端
6 c- u" p- N8 e: l$ s3 u. }% @- y最终将得到如下的URL串:
& q8 [- L; |) x5 v2 l' U0 f: e, O, `" \2 |1 h+ N. e
http://www.abc.com/pinball.dll?M ... GetImage&Favorite=1
) |* H, h, n. a+ j; d2 z1 h; I4 }% t- n" [
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
0 u; x& I" b: b0 |- y5 y0 |将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:
; v7 f. k* t6 Y5 q* a
; l5 w- ?* |. @( u( j# Kvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
; p3 [6 L8 n% d8 J0 ^; H
( f6 K0 R  S( I2 j+ {( k1 j! X而parse map需要按照下面的形式定义:, U- p# n4 u+ Y& C. Y& W. X
# t! ?: Y0 b7 ]; G/ d6 [
//CPinballExtension从CHttpServer派生而来$ w+ g' w8 c: j3 V
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
# ^& H! C& R5 W$ J
3 B5 j2 U9 ^: G//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice
, m0 y2 ]# P3 _' Z/ QON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) 9 o( x# r( f5 t5 a; {; X/ B- h
2 h9 ]* T) E) r; F! P" B/ X" ?
//该参数在URL中的名字为Favorite
6 Z- p8 e5 G- q' F7 FON_PARSE_COMMAND_PARAMS("Favorite")
$ ]* i0 s" ]4 @; Q  ]4 r) }/ N2 ~5 q6 Q, Q
END_PARSE_MAP(CPinballExtension)4 u. Y8 X) c6 I' s' a; g1 e$ G
5 \! o$ l: Q2 L- |$ ^# E
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:( k  Y( L" v8 |9 t0 v, ?8 A2 F: k

( E  u" l3 |2 HOnPreprocHeaders/ u  L0 ]8 n% C, ]2 j
OnAuthentication
* }! Q% x2 K; L+ r$ JOnUrlMap
) Y# c0 ]) R: D: _: E  eOnSendRawData
, i5 R( c0 }" V' u/ NOnReadRawData+ \% @! U$ o  A' w2 W3 Z+ k# [
OnLog, o! V) e+ I* e0 E0 R1 }$ D& R
OnEndOfNetSession8 g2 u/ E, |" d; f& E
+ S; E" ]# O: v+ z
MSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
7 d: a3 h- T4 t5 g+ ^  Q/ o( b5 L
参考资料:$ j9 j4 |- o: N- P* ?$ C% b. h$ x0 t

# [: O7 R/ j0 u4 m: A1、MSDN
# F5 g$ U. l+ c* h( A  |2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-8-9 04:26 , Processed in 0.034310 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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