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

IIS的ISAPI接口简介

[复制链接]
发表于 2003-10-13 13:22:17 | 显示全部楼层 |阅读模式
作者:netguy (mailto:netguy@nsfocus.com) . _: r) b6 O9 O' V. ~" a+ B& m& n
' r! L, Y! H: P) B; _
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。; B# t: W- a) q* r: t

  w8 I, a6 X1 J一、ISAPI接口和CGI接口的不同。' v9 [$ f' F$ x) v

* ^. L! H" M/ J6 O2 `  e2 HISAPI程序和CGI程序完成类似的功能,但是实现方法不同。; M) z, v, P$ H: t/ o8 K- M
( f/ ^( t, |, T& E1 y6 C- {( u! e, p
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。# ^, R9 w! D2 K6 W) C8 S
: |; u4 `  e' C1 V1 ?$ L# z
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
; ]$ U5 T$ }7 \# m$ @! g3 H4 ^1 p/ e. j- p2 e
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。
3 m- C+ ?; F$ }  b6 Y4 c( W! m$ Y+ K' D3 u. L( l

2 ^5 h. s- d7 V, J, O二、ISA  ^. B  r$ ^% @) j" [
$ ]7 B' _* S0 k: ~+ W* m2 e
ISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):
. A- l8 g' f5 A1 Fhttp://www.abc.com/Scripts/function.dll?
/ l2 D" r# R# X5 F& p! h3 U3 |9 Z) A8 f) U9 _0 f+ b- n5 t
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。
8 h. y% p3 ^+ N" ?2 Y  Z1 K% i6 m3 o! h; p- W) {* O
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
: @( G' @! N2 ^* V8 @
1 r3 X8 \. p  ^, C- }, ^! HBOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);, ?) x% g+ f% a  k) F9 R4 N7 ]
typedef struct _HSE_VERSION_INFO
/ A, ]3 W. z: K4 H, D1 z( Q{
; g; O1 e; L3 S" z/ b. gDWORD dwExtensionVersion; //版本号+ N2 U9 D- t/ P" A8 Q. K( [$ W
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
0 _/ h, P+ D; K, [5 p6 @} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
, l4 v8 ?, u3 D; a
5 _$ B9 }2 C$ D2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:
6 R8 U3 S8 N& g) S/ }- c7 d( h# Z4 x& [3 L1 Z& A
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );. z% H# C" a1 `' A0 C: a
' \5 }! n, S% b7 O+ q
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):0 r4 ?5 ^: Z* E/ N7 b6 Y# f
% V1 t# n. z- m4 Z. V& n7 b
typedef struct _EXTENSION_CONTROL_BLOCK . S7 O2 x' M1 |
{
+ N0 a; V9 \* K$ `) ~DWORD cbSize; //IN,本结构的大小,只读' B; w( Y# ?3 \# ^
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号2 a* k: t0 B! R6 f+ e
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值6 M5 t9 v; q+ c( U
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态
' y" r5 u  h: t- J" K! N: QCHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容5 P5 u% t3 ]0 j0 o. k) p: [
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD9 ~% J2 A$ P) F9 ]) H! ?
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING
+ }% n: j% g1 e. r; WLPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
" l! t- k# ?, H: h: E/ o1 wLPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
7 z4 _+ l' z2 |8 R* ~3 J3 J8 wDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH0 n" u0 @2 o' z0 Y4 K1 @
DWORD cbAvailable; //IN,缓冲区中的可用字节数
, ?8 f8 W$ ^0 eLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据5 ~9 q; m  L2 \( S; `$ q
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE* Z2 d& v3 S9 l* ^

# ]5 d$ C" n3 t0 x+ ]+ z$ S7 p( r//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
; F. [( r/ J- |6 Z( pBOOL ( WINAPI * GetServerVariable )
3 K+ G5 F  M6 t6 c' U3 M# G' l6 O( HCONN hConn,
- O( \6 i( y9 v5 ULPSTR lpszVariableName,
: W! q& h6 q" {1 A( `0 ]LPVOID lpvBuffer,
& i  Y& \) u3 [2 j1 S( v. TLPDWORD lpdwSize );( O8 ~# Q/ n/ ?! J- z
& H( ^& c8 J4 o2 Z. e, K  K: g- J
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据+ F, A9 l& |$ E' E4 b6 N. F
( HCONN ConnID,6 X6 S. S0 G; U% c( g- G
LPVOID Buffer,- e  k7 q. W' z
LPDWORD lpdwBytes,1 u: H# M6 E$ E/ X% G& r
DWORD dwReserved );
+ T) {* m7 l% H3 n- J* d. ], g0 M
: Z- G4 ~1 \+ l4 d8 FBOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据9 |* u9 W1 U8 e1 {" U
( HCONN ConnID,5 t6 d! y! `/ a& G! l
LPVOID lpvBuffer,+ b! Y- I5 c6 X3 ?4 i
LPDWORD lpdwSize );; p5 F4 g: k: \

/ I' r; K' r3 a* m6 c3 v: lBOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能2 |$ V0 e& R0 \4 s- [6 M
( HCONN hConn,( y! n8 j( n; A( `& C% [8 i
DWORD dwHSERRequest,
& Z7 w* r; J" eLPVOID lpvBuffer,
, a3 K. G% d' q& O) }# L( R7 N$ ~LPDWORD lpdwSize,
% `# E3 S1 i9 Z4 U1 q0 s* A) ^LPDWORD lpdwDataType );
2 _; ?5 C) s* r* o6 h& q2 w2 {: x% q+ P: W: B
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;. f- e; S& n( p- i6 Q; t& x3 g

- O8 O' L+ J, |6 ?在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。. `! ^. o9 W. b& G7 M7 w. u
* ]$ l2 k' Y. \- l0 H0 o+ Y
三、ISAPI Filter$ j9 D' _( h) |0 {3 N- x; }' w1 o* P2 k

# x/ b4 c$ K9 H/ ^4 P1 f. qISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。7 ]  h/ F/ h, {* L
+ N* T5 G" S0 e! o1 r1 c
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
; O: P/ A3 t, b, yISAPI Filter都必须引出这两个函数以供服务器调用。
, l% p4 Z! S; j; ~9 {
! i' L& M" A4 G- S1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得
& I, {& Z. i7 F, P8 iFilter的文件名并加载它们。3 T' Y, T+ U2 W" Y& R; E8 v

9 f2 R& F% y' BHKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
" L, ^0 d# K: K1 r
4 d# N( I) {! y# l9 U3 C2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
! D4 N/ {: W: @1 x& t" ?: N  E4 s" v8 u; x2 k( [4 s
BOOL WINAPI GetFilterVersion( * T% M  N7 [3 f; r) T
DWORD dwServerFilterVersion; //IN,服务器使用的版本规范
6 `# R2 ~3 j: p. Z& O- N4 CDWORD dwFilterVersion; //OUT,过滤器使用的版本规范
# K* f3 ]9 ~" Z& n$ DCHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串+ d2 j1 W+ z) K3 W4 g0 Q
DWORD dwFlags //OUT,事件和优先级标志
5 H7 r; J/ n! |. \8 V# I0 k);* H- U9 O! [  |( ?, Z$ Z0 a1 d

6 k9 }# Z. ^% B8 H; ]: q9 j# u  `, V( X事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
, n, d+ [+ z, U- Q+ H# C+ _1 X* G$ j" G0 p4 |" V
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。2 ]+ B8 X; h1 |! y2 W

4 x7 t9 L( T* Wtypedef struct _HTTP_FILTER_CONTEXT2 o' [# k" ^* k: [5 F; g! n. p
{% b' b4 ]( t0 t* c) b* e

) K5 m# M. d& O. O4 z4 uDWORD cbSize; //IN,本参数块的大小
# W# @5 W; Q1 p2 l6 L4 d. ?DWORD Revision; //IN
1 h) ^  c, y6 V& u3 i* zPVOID ServerContext; //IN,由server使用本参数3 _8 F; ]) i/ W- P; m' z7 u% Q
DWORD ulReserved; //IN,由server使用本参数
9 K  m3 [7 A3 j9 ABOOL fIsSecurePort; //IN,事件是否发生在安全端口上
8 a- P# |9 L1 r! m; qPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文/ H5 Y# q9 Z& R% W# p
! d7 R- D9 `* A+ p1 E5 w
//回调函数,取得关于服务器和本次连接的信息. c- w7 V! m- y+ a
BOOL (WINAPI * GetServerVariable) (
8 |5 s6 P% n7 s2 R5 P6 T0 Qstruct _HTTP_FILTER_CONTEXT * pfc,# Z4 K# Q* Y$ U8 Q' ~3 w% _2 H
LPSTR lpszVariableName,
9 J1 B7 N% o; [! T5 ?LPVOID lpvBuffer,: S' `- _. v; i
LPDWORD lpdwSize2 \; z; {  L: k3 F
);
  T- T7 V# ~" I+ W8 L
( j) V) E0 m/ C- j) J% ?BOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头1 U1 P' U  s: o3 [3 U! }5 n
struct _HTTP_FILTER_CONTEXT * pfc,( ^" C$ `$ J8 n( |7 u
LPSTR lpszHeaders,% W, M  [# a! t2 V4 K% y/ E
DWORD dwReserved
, p! i% W7 a* G& y7 U" l6 V);
; R" D& |4 n9 Y/ F3 y
6 o9 O$ ]* C# `8 M3 I9 x) Z% vBOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
, g* {; N" M: b+ Astruct _HTTP_FILTER_CONTEXT * pfc,4 B8 N, w8 T  [
LPVOID Buffer,
. m* Y. D7 h/ xLPDWORD lpdwBytes,+ x) A9 ^& A, ], v
DWORD dwReserved
& g# s2 U/ y/ P" }/ c% c0 });
- W4 e/ M+ i; G5 a
: J- f# Y! ~. I2 w7 l3 r, i, xVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
" q. c8 i% L5 @struct _HTTP_FILTER_CONTEXT * pfc,2 a: q, h8 Z( X
DWORD cbSize,
4 F) B& q5 k0 |1 K. ZDWORD dwReserved
* N" S6 }2 z+ a+ R9 V2 H$ L); / ]6 ]0 Q- o! ]; u  C9 x% R8 D$ i
! v$ h, o1 r1 L- l3 @
BOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能3 Q! n5 y) N1 K# G
struct _HTTP_FILTER_CONTEXT * pfc,/ E% U) S- O; W  g- t0 h
enum SF_REQ_TYPE sfReq,
- @4 t0 a0 y  L3 TPVOID pData,
! d6 B& m+ r) J" F/ H: {5 e& MDWORD ul1,
; A% _  ]# i6 `7 S4 l$ v* }! QDWORD ul2$ H& C  R/ l+ [; z& j. q
);
$ a& _& S8 `! O3 w) P5 k' L1 A) K( Q" q3 R+ V$ K5 B5 A
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;* N# y& V5 z% }

) d# C) y. H. c8 @: S% }3 C/ I四、VC++ 6.0中对ISAPI的支持
+ S  `/ m% g- x7 ]  F0 X
, |7 o# }, U& y. |! U2 `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实例,每个/ K* n+ {7 v5 R2 h/ k1 I1 F
CHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
* p! D; U. w5 u: f0 T5 g$ c' U$ |- T% G0 @/ F
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
- X( I2 {. y. ]
2 B7 @6 Z* U/ d+ h9 D, z) 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为例,该例中有下面这样一个表单:
5 N. e, D+ J2 Z( Y% H# Y5 a+ r- z$ F/ i6 c8 g
<form method=get action="pinball.dll?">
1 C0 u/ @4 _+ t; M. T* B7 U<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
5 |/ J( o0 u$ f; i3 @6 {! E<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>
, v( M$ c. H6 X9 Y<input type="radio" name="Favorite" value="2"> Twilight Zone<br>7 J$ L6 J& z# N/ L/ V( a; b! [/ J9 V$ t
<input type="radio" name="Favorite" value="3"> The Addams Family<br>+ A- B! E3 K) W" A5 C$ w8 s+ g
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
6 o8 Q5 U, f* [: l# n<input type="radio" name="Favorite" value="0"> I don't see it here<br>
6 J2 a( k4 n! x( ?<br>
! t9 k5 }% }8 o! O7 x* g3 y<input type="submit" value="Show Me!">' F6 B( f3 Q4 `! Z3 W. K& o
</form>: k& j9 d+ K  q7 n
  W- B2 D5 T2 z+ U" C
当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端/ E0 g$ Z4 k8 ^! a" c' B- Z5 f& \' F
最终将得到如下的URL串:5 Y( N  I9 b2 i9 W. G5 }3 M

% u7 o  ~0 C" `& Ehttp://www.abc.com/pinball.dll?M ... GetImage&Favorite=1
3 z' L5 A( l& P+ N
4 `( K2 M( X# w4 z% s# T在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数$ l# a! \4 l: `2 @
将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:* K' c+ F  F; y0 P2 z- x: c
# S: G1 z% T2 G5 g9 m. M
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
1 \3 o0 Y# y  f$ `2 W2 y6 Q. D: {7 n1 C' L* w  `
而parse map需要按照下面的形式定义:
3 w; H: G+ F6 g# {- B) @4 ]" x
: ?$ |& r5 E7 m4 C( D9 A- i//CPinballExtension从CHttpServer派生而来
( a4 K) J- \0 E7 }% W) Z4 W% BBEGIN_PARSE_MAP(CPinballExtension, CHttpServer) ! i, G$ b% N- ^$ b; \- `/ I
4 N! n. ~' g% L4 b
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice9 G- e# j+ o0 \7 s
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
9 C2 z$ m0 a3 A/ C* z- ]# _7 O* B) t2 F: F8 [3 \5 `
//该参数在URL中的名字为Favorite
  S. j& c1 ?# n# x; XON_PARSE_COMMAND_PARAMS("Favorite") * J& [5 U3 N" O' f. H
& ?: V" n0 N. p: C  n/ {
END_PARSE_MAP(CPinballExtension)
5 u/ {  h+ l  ~1 u3 {5 c
5 W$ f$ \) M; H- _, y) s而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:! e9 R# t! }8 b& t

) M# c1 W/ u3 y* Q0 _1 yOnPreprocHeaders$ G/ `; B- o8 _7 n
OnAuthentication
& r0 x& [) A( w1 UOnUrlMap, h/ B" P$ F9 }: \
OnSendRawData) h* M5 f/ X0 ?0 P+ D" X* _3 g
OnReadRawData0 i2 J+ n+ j9 z* H% t1 _
OnLog
  @3 R. w- V: G8 HOnEndOfNetSession
0 C; _1 v: Y+ C' Q0 s
$ s1 n8 `) V' A$ j3 VMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
5 f6 J& F$ S% x, C2 n
* Y. {1 k3 P1 A5 J8 w) I参考资料:
( F3 c2 x, b& e& y1 b6 n, K
( B& [# V# c- x; T' G9 H; d1、MSDN9 E- `9 [% L: L! L& z
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-8-13 22:46 , Processed in 0.019825 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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