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

IIS的ISAPI接口简介

[复制链接]
发表于 2003-10-13 13:22:17 | 显示全部楼层 |阅读模式
作者:netguy (mailto:netguy@nsfocus.com)
8 l  h. `0 O& M) ^  T: z+ c, \! c, ]+ S; `* e" t& ?/ z1 f" @
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。0 t0 @- x; R( I

7 |+ o' A6 u+ L一、ISAPI接口和CGI接口的不同。
: _* i; M3 g0 T3 ]0 V' t% Y
( u+ \- ^7 ^( Y% Y& {7 {! Z( ]ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。
, ^7 W2 ^+ c7 X- c
  z: Y) Z% v" o1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。, ^- ]- Q2 V3 ?/ U' x4 F

/ r5 \% n" \$ w1 ]9 D' T2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
4 a! `- L, d! `, N+ k) j4 M# a  ?) [8 E: J8 z
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。5 T* i' C& \4 b4 x# Z1 \7 o

! {* v' m* ?3 E; a# G7 ]+ [
! O' g! o: _" @( Y/ n9 X# W" M9 \二、ISA
" R% k- ~, G( J% @, C" i
$ e+ I0 F0 M1 l, J2 pISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):' q& |- v- L- W$ R
http://www.abc.com/Scripts/function.dll?" @3 ], q# l: V" C2 S7 N
* B4 k5 V; P5 `% Z4 X% D; y
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。
9 i9 I' M6 c% g6 m( ?. j3 d7 w9 {3 g
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
; f  B+ ?( `0 \; K# X' x1 _0 q
( i4 p3 J4 c$ S2 z0 }BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
( Y9 X$ D1 ]6 s/ K# Stypedef struct _HSE_VERSION_INFO* {5 r) T: D0 Z+ V
{' }8 [" @6 |8 h+ E4 {: }  C
DWORD dwExtensionVersion; //版本号
: }8 |4 Z2 B/ S* j( wCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
5 F4 N2 z: m: X6 M- M1 B9 a} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
8 c7 H; N, Q5 N0 g! c
. E3 U; V4 N! S1 o( B9 G2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:
3 q0 e3 F0 X, B( I' S6 ?" ]$ A" S. _+ Q0 {. E2 m- w' U
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );# d7 I% D: V7 L$ N+ F
2 G5 E/ A" t. f6 n
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):
+ f  M1 _% w" x( d7 }2 E5 k
( L  T/ }' U' e! |0 jtypedef struct _EXTENSION_CONTROL_BLOCK ! _7 G8 F: }& g
{
* w4 ~# M0 _" r0 D7 w1 V& B5 [DWORD cbSize; //IN,本结构的大小,只读7 g- t2 {7 t. u$ d' A! L* I' K: U
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号8 `4 z' g7 f9 \' D* Z# o
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值
: N& T+ y1 C' E9 p% p8 K& @9 nDWORD dwHttpStatusCode; //OUT,当前完成的事务状态
: c) h2 |+ R  xCHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容0 p( o. K4 O6 `7 P3 Y
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD' t) A$ [% L5 w: {& y: v& i% E' g
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING6 w. ]( s1 D6 {0 f& n
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO$ Q$ r8 [5 M- L5 p$ ?" o6 m' d+ q
LPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
5 g9 c$ L: z/ P* d* C0 uDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH, f3 Q/ O2 ]$ D
DWORD cbAvailable; //IN,缓冲区中的可用字节数
+ H6 U; Z% e+ V" zLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
% G& x) J: f0 oLPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE( \0 m& Y9 S% @' ^
! c) U3 f/ L6 f7 O
//回调函数,用于返回服务器的连接信息或特定的服务器详细情况$ [) d- h1 J7 n; V, ?3 D' M3 C
BOOL ( WINAPI * GetServerVariable ) , g% G$ I' ]8 {: ?
( HCONN hConn,! K5 ]3 J, S, A, X  m. U# P* r; t
LPSTR lpszVariableName,- m) |& W* l3 D
LPVOID lpvBuffer,
6 {1 Z" n; r+ w# h- }$ P* z0 kLPDWORD lpdwSize );
8 x! k2 o8 e' K$ Y/ ?& N( y/ e. s  z* M) o( |
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据
; h6 q0 s/ l! w3 u6 L# t0 }( HCONN ConnID,4 `! ^' d4 v, W
LPVOID Buffer,3 `( ?. O, T9 k1 I/ ^
LPDWORD lpdwBytes,) T5 g! W6 L4 Z1 h4 V% H0 ?2 o
DWORD dwReserved );/ k, W$ D4 G% Y8 B
" K7 z  v  z4 k
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据' ?& q" `: o) r, R9 i4 _, r
( HCONN ConnID,
3 r' M# O$ S9 {5 ELPVOID lpvBuffer,
2 l) W( l" U/ sLPDWORD lpdwSize );
! l# S4 e  w  _, p) [$ m$ D7 a( N+ x" _0 |7 _
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能4 I8 O  B# f$ W0 ~8 P* Z
( HCONN hConn,
' u) Z1 G( c4 o' u8 e! {  T- Z; qDWORD dwHSERRequest,+ M4 a; Y: T$ W2 b8 N- ?
LPVOID lpvBuffer,0 z% N- G# \  J4 N1 j- R( o- H
LPDWORD lpdwSize,7 r5 U. ^% \* K" k  q( D
LPDWORD lpdwDataType );# U8 @" D: c8 m
' Y) ~, \2 `, L2 t. X
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
" v1 n+ d; P2 n% @- w) @! F' v5 R
7 }3 b9 d" i! |! E( n6 x在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。& Y* r1 |0 N, y" l9 k8 S, A
( A9 E% M8 `( t9 s1 v# ?
三、ISAPI Filter* o6 Z6 O* H0 r* U" \
  O- E+ \9 Y& t
ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
3 o% ^" B: c# w/ f4 r! t( K: e# r- w1 V7 d  V9 b+ L& n8 N, f8 o
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
- Q% B0 Q  X* J5 |) o3 eISAPI Filter都必须引出这两个函数以供服务器调用。
0 Z% T- |3 [' H3 o9 c6 N$ K
% ]- n3 F" H2 h' v. c2 Z1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得6 R7 k! P1 r8 A! @: l
Filter的文件名并加载它们。
  l( C9 T  D2 s7 y9 b3 g) a% Y9 W' e9 {" Q7 w0 d
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
+ c9 p2 T3 k" `( T$ O$ R* K/ s$ e/ {
2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
  Y5 H; ]$ ~( N& y5 d0 B
: `3 @$ o4 }0 bBOOL WINAPI GetFilterVersion(
' M$ X' i3 t, y1 m: S* e* QDWORD dwServerFilterVersion; //IN,服务器使用的版本规范
+ Y, K) q3 Z. @4 p! [" ]DWORD dwFilterVersion; //OUT,过滤器使用的版本规范
* i$ C- W( w+ L8 SCHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
* ?1 I. q4 z. ODWORD dwFlags //OUT,事件和优先级标志* a8 V3 B5 s0 x. O
);4 A. ?; G2 E: x4 n

4 M4 Q  U4 ~( Z# o9 ?9 H事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
/ W( L* |/ X. ^) b' L3 i; {* X; B( ]9 ^# b
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
3 d" P2 {! I) d2 r: ~9 S, O5 k6 @$ w; z8 g; r
typedef struct _HTTP_FILTER_CONTEXT
  h/ k' q+ Y& w) {{
- J3 w2 V& {' ]* {5 \7 O8 g& h
! _  H- Q' |: \2 c1 sDWORD cbSize; //IN,本参数块的大小( J; `( t+ p8 u  I
DWORD Revision; //IN* H; h0 {  h* N3 T4 d( i' R! x( j
PVOID ServerContext; //IN,由server使用本参数
3 w! r4 o0 q7 N6 D" m( H5 hDWORD ulReserved; //IN,由server使用本参数
7 C2 e" _5 L, A8 IBOOL fIsSecurePort; //IN,事件是否发生在安全端口上
3 ~; i2 v) z& n$ \" ?  R( \/ r& T' tPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文9 r+ c& F+ H$ a# y" C

* f7 ?3 U: T% M. t+ W/ T0 j0 ]//回调函数,取得关于服务器和本次连接的信息
) r/ x# p3 A' |( {; ^' h! S% HBOOL (WINAPI * GetServerVariable) (
3 d0 ]1 e1 a5 E4 \. Z6 ^struct _HTTP_FILTER_CONTEXT * pfc,* c8 Z8 W4 @( E8 t; L1 ~- }& U
LPSTR lpszVariableName," f9 z  N7 r4 ?5 T0 G) `- O" ^, ^& F
LPVOID lpvBuffer,
4 p- {7 e" u. T& T6 S/ SLPDWORD lpdwSize
* k. j7 i- o& g' |0 u. x); $ q6 h! h; r6 k& ]6 G& e

7 \7 E3 [; |7 V0 e+ Y/ b4 zBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头; t, Y7 D3 o- r4 A
struct _HTTP_FILTER_CONTEXT * pfc,
6 V# _; v& q# M2 x' `' ULPSTR lpszHeaders,
% @" Y! E8 Z/ r' K/ `DWORD dwReserved- }- Z# s  e' f7 \" B; w2 j
);
* \- r/ Y! q9 Z  W; D% U, i9 V# f
+ r4 J- ^. a* n$ l! |7 [2 P, ]BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端7 [) y/ `1 s8 b
struct _HTTP_FILTER_CONTEXT * pfc,
2 a- e! u! ?% JLPVOID Buffer,
5 o3 G- q+ ^# C- kLPDWORD lpdwBytes,  h0 S! I: |* Y' c  _1 [5 v
DWORD dwReserved
% z' B# U7 _, ^3 s);
/ t9 m7 @- f2 \- |" \+ i" H; B& F: U4 y: l/ [
VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。7 m- R; |: ?+ P* K- H* o
struct _HTTP_FILTER_CONTEXT * pfc,( m; E& f% c, E
DWORD cbSize,; Z. {$ X4 N' @) a1 o- \
DWORD dwReserved% c- ^( Q' o" g/ o
);
5 [/ [) H8 b# u/ N, _8 ?0 J
5 G" F& [) U" I' {1 ]4 y% K  wBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能: y: @, P) e  m7 T& G5 R
struct _HTTP_FILTER_CONTEXT * pfc,! O' P) R5 U. H+ c* e3 f/ N
enum SF_REQ_TYPE sfReq,
* |; A/ J, F; q; v3 x5 J) pPVOID pData,1 v4 r7 V) X& R  I
DWORD ul1,
" i+ k7 T$ e( ]) X9 Z* zDWORD ul2/ U- ?2 a/ k* G: k: E2 M
); * W$ @" X9 h1 ]  @. {

: W6 i* E" P7 q( q! d& k% E} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;0 f0 W, l6 ?) s8 ?+ h

) u; t7 F( n* U7 s* [: u四、VC++ 6.0中对ISAPI的支持0 l) I0 f' |1 C( b9 c' f5 V

6 t- r% j4 E# ]- v5 S# X- j5 |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实例,每个
( a* m8 u! W  d6 i& w2 h7 E1 P+ SCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。; m* O) D" g# V) a0 m; E

. `9 K6 a) H( ?7 t6 M  p- Z一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
  i! J( H5 x% z4 c3 h" w/ a2 L
' b1 w9 b* k1 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为例,该例中有下面这样一个表单:9 K# r0 |7 M! a; l
9 A% c( H5 a# ?4 i3 t
<form method=get action="pinball.dll?">7 a8 z" A2 X. F8 _  R) x; n
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">0 a# D, W( v& j
<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>
5 g5 z& k( L' e5 H<input type="radio" name="Favorite" value="2"> Twilight Zone<br>
! m& h5 Y% a% M* b- e. Q<input type="radio" name="Favorite" value="3"> The Addams Family<br>
0 ^1 b! C. N8 F<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>1 |) N& P$ F( l2 C7 `- D' w
<input type="radio" name="Favorite" value="0"> I don't see it here<br>$ _9 e4 H; @  j: N3 L
<br>( z0 y- V" l# a- L( g
<input type="submit" value="Show Me!">
- ?4 R7 h- k+ X  L</form>
7 ?9 C& }/ Z% I5 X7 }' N
1 n: e- H1 o4 _6 l8 ~当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端0 o9 G- M; k* T3 i% o
最终将得到如下的URL串:( g" g2 @. `8 g- T

0 d. G" r4 \5 i- C5 I6 Z8 Y0 khttp://www.abc.com/pinball.dll?M ... GetImage&Favorite=1# O9 G8 M- F* r: R( o

1 x- a1 P, m! ^& r$ _5 Y在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数% A# ~/ N2 y  M8 A3 @1 |7 F# K
将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:
% B6 I  K/ b7 B, e8 q. W0 N
2 F2 ~% h& B5 u' t  g. tvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
) N  F% o. o" L, W1 }4 i4 B6 y  b! x9 t( \8 T( V( ]) h6 G* z
而parse map需要按照下面的形式定义:
/ e5 k3 x" b$ R. F4 d6 ~8 q. P9 b2 P/ }# ]: L* I" S
//CPinballExtension从CHttpServer派生而来
- G( o/ @0 x$ Q, oBEGIN_PARSE_MAP(CPinballExtension, CHttpServer) $ A7 B5 c$ U" R  x4 z3 [. E
! i" }( n) q( v  O# w. N  {
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice
2 N1 D6 O5 `, WON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) ' b# _7 k/ m7 k; L! O2 {% k7 j
9 V/ X0 I% N! D% N$ T
//该参数在URL中的名字为Favorite. R1 D$ V# J% ~( x# r0 S. z
ON_PARSE_COMMAND_PARAMS("Favorite")
" x  U$ t5 a* n( e3 y( K: e
- P) w' ~& D, S( v9 t& C. g1 fEND_PARSE_MAP(CPinballExtension)
7 k2 v, e& j0 ^; k4 G, i$ o1 _9 Y6 u: }: V, ?) S+ N7 I6 O9 I4 L' U, {# B& b
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:) J3 J' t: l- u5 D
% T! d9 w1 M1 H0 Q# l# [3 O( T, t( s, L
OnPreprocHeaders
% c, M; g3 W8 j8 EOnAuthentication; p+ f6 b: u$ I; m
OnUrlMap. q8 H  c5 W5 G7 Q& h! Z% Y5 o4 f
OnSendRawData$ i* {1 N4 U. f' J! @* U/ A
OnReadRawData
$ \2 }! N& N- ?+ h: y6 r' \OnLog8 J0 I; P; g7 }. j
OnEndOfNetSession3 M+ A5 l4 K( |7 }4 L: {" ~

. j2 K! s8 y" [& b- {7 n5 UMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。( q9 c3 ]6 n0 g6 N. y# k0 L1 X

$ `9 G! a( ^) S. h参考资料:  e8 P. o$ G8 c9 x0 }( _% _

. {* S" k" f0 y- U  C# U1、MSDN
. u2 B5 C0 |2 M2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

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

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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