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

IIS的ISAPI接口简介

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

& ~3 k( j/ _6 S8 l一、ISAPI接口和CGI接口的不同。! F7 p2 P+ R% P; a
6 y) Z2 A. i/ ]. o' u% X; ^$ w# o
ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。
, U+ y7 x3 j, A$ [+ a; V1 e# d3 z( g9 |0 P4 V  h) k- T1 K0 f
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。
, u0 k6 u+ D0 @5 O# b' x4 L1 @" D( ]
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
9 N, R6 z7 s8 W; \
& w, s) m/ S: b# V6 ?6 u! L# L# \ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。& z' \' u( x" t6 m3 Y: P
. {, @. ~7 x% F0 p* E  T
7 h! M" J: s7 t
二、ISA7 D( h! b+ u. D! P$ W5 I: v7 E

/ w  M. T% F7 n2 W' M6 M6 f0 w, [1 SISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):( [% M9 G- S6 z# |
http://www.abc.com/Scripts/function.dll?/ J" P/ D  d& A3 x$ M( D4 X. f
3 A: U6 V3 k7 j( P; J
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。' f+ {7 x& M. n, {3 }# W. z

- P8 I0 W8 D! v# v, @1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:& H! r, ]1 |4 C! u# y# C) t

" d0 k1 B+ S9 S- q* GBOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
( U9 X# Z) C9 n2 F9 C* t* }1 Y+ ytypedef struct _HSE_VERSION_INFO' J' h6 M( [; _  z/ A
{! _! E. E3 E; Y3 g8 ~, _* c/ N
DWORD dwExtensionVersion; //版本号
8 e6 k( z$ {$ `" F3 r6 fCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串! w( w7 g+ Z: B& ?. `
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;9 H- ?$ B# }' E: y6 Y7 T5 m- p7 F  q+ U

5 Y1 ]  V7 k* _( K5 k" ]$ Y2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:& a" z5 k- _9 m6 e2 D' [  z/ r

% z, Q  P& G) U% p6 x: g5 d1 GDWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
$ i$ m8 F4 S( ]* d3 n7 s4 ?* ?9 d0 r3 U
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):
: I9 @# M- @  j; M' h8 M. B- B% W) R. k6 `) O+ o  j' c
typedef struct _EXTENSION_CONTROL_BLOCK
8 |0 j; J5 c- y) u3 E3 S3 q3 D{
5 Z, r$ I; B+ F+ zDWORD cbSize; //IN,本结构的大小,只读
3 t5 b# Q# g2 F& ?: o- kDWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号; X0 s' Q0 H% G
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值. B; G7 |% v. \. W% u8 B( O% J
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态
# g! [- l* ]- H% ICHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容2 g% o) E$ V9 p" g
LPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD9 n7 E  t- C, }1 K  l, V
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING$ d' ^" D8 x+ d* f$ \
LPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
. j: x0 h0 P2 d7 BLPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED
5 M% I" F8 [+ Y$ N4 k8 j' mDWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH2 R! F( {+ ~+ ?( e2 p9 I
DWORD cbAvailable; //IN,缓冲区中的可用字节数
$ H5 g8 z" L9 c- m4 Y  H7 JLPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据9 u+ e% R& X# E/ S- n
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE
& i$ c: y: X2 j5 n4 O' Q" y" {" b2 d# p" f
//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
+ Y: D7 [% J! wBOOL ( WINAPI * GetServerVariable ) , a. n* X# a+ H: ^3 m4 Y" g) M
( HCONN hConn,
8 i1 R! U4 O! y+ J( ~" BLPSTR lpszVariableName,
+ H% H- A% t- G5 P; \9 `: \LPVOID lpvBuffer,
, ^/ J6 U8 w, @4 BLPDWORD lpdwSize );
* I! r: ~1 L3 y3 S/ H9 Y" Z6 `+ F' V' i3 v" L
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据' t3 u' b7 K- A! k, ~: |6 _/ \, k
( HCONN ConnID,9 h& Z. `+ v% m% x  A
LPVOID Buffer,
" @* |" V. i7 h+ S( w# MLPDWORD lpdwBytes,
7 w. \, h& \$ o. f; p$ a+ rDWORD dwReserved );
$ d: X4 E& i. ?. y8 ^
5 j+ M6 u: p. A! ?! S# fBOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据! z( n& J+ b+ P2 J  z6 D
( HCONN ConnID,
  y* L3 D8 i' |. sLPVOID lpvBuffer,. H2 ^5 [! i, q
LPDWORD lpdwSize );
8 W* f. @9 o# i4 Q5 Z' s
; f& ~6 F/ ]: SBOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能
" V6 q* a$ r) R7 J( V, C# O( HCONN hConn,- ], R3 |3 {. K- \9 ~
DWORD dwHSERRequest,8 d2 P2 K8 `' v; X
LPVOID lpvBuffer,$ Z+ x- y3 a  C8 w6 K' @  [' b5 m
LPDWORD lpdwSize,
/ O1 t- e0 R( ^$ H" hLPDWORD lpdwDataType );/ |, S( s, P3 a: n- o+ C/ `
% b! l) U7 i$ @
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;+ X/ w6 c5 n; s* E

1 o8 Z+ W: ]2 v* a9 \# A$ {在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。
8 f0 r& K# {9 D+ x7 V
- a  w% a$ H( Z$ L' C9 _三、ISAPI Filter! C/ w! ]% [* Q; ?' H2 U' A
& a* N4 ^4 O- G+ p' e* K- }/ Z
ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
7 S4 J7 I+ n# Z( G+ s2 C
3 F8 X& W' c0 [. u8 t7 SISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何$ N1 r% I$ i* t/ G/ T1 ^! V
ISAPI Filter都必须引出这两个函数以供服务器调用。  x/ P1 ]* l. ~
. D4 q% a2 E" M7 v! r
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得  V7 P# w3 J" ^. ]9 r) \- @3 I
Filter的文件名并加载它们。
' Q7 s% A, i! j) i  Z, o" j) L5 ^/ z/ D( A
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
4 `7 z  ~5 E3 M) _9 F
7 p* M  t! V/ n5 ?- p2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
  g" f/ G% y% f- Y  w) N/ r! U* a1 t$ T! r% T( G) h; g
BOOL WINAPI GetFilterVersion( + U) X0 b1 a  I" f3 V$ a
DWORD dwServerFilterVersion; //IN,服务器使用的版本规范 3 y  u" j# Z( w  g8 z" W
DWORD dwFilterVersion; //OUT,过滤器使用的版本规范% g( P/ S+ \5 |( @0 ?
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串* C% Z. _& P- c) P  Y5 j
DWORD dwFlags //OUT,事件和优先级标志
1 R# p' E/ Y; q  k+ m);
5 f* j; [2 D( W+ C) p& k
' H/ J$ e  x) C3 v, x: w' K事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
& X- ^6 x- u: a/ w
* E; D- i4 Q& F9 v3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。/ Y- @& c& J1 Q9 h6 a

( K) ]" r5 v! @/ {! Jtypedef struct _HTTP_FILTER_CONTEXT
! C; H1 o3 [) v+ N# ~* D{
' g+ e% q5 D7 l' F* e. f% z: R3 q) M! m; q9 Z3 d6 D# Z7 P/ E
DWORD cbSize; //IN,本参数块的大小
; |, f- {, t( u' r8 RDWORD Revision; //IN
  n8 u/ a, X) D; A1 P# x3 jPVOID ServerContext; //IN,由server使用本参数  ]' ]2 d' W1 X
DWORD ulReserved; //IN,由server使用本参数% @' |1 `( T- e) k! K$ z
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上
/ B- ~* Y6 p: ^4 f% x0 ~: Q1 LPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文
/ h- p) a0 S% q2 d
- i. J: a# w7 f$ _$ e//回调函数,取得关于服务器和本次连接的信息2 g+ ^, B. ~; [6 N7 d  Y- ?8 \
BOOL (WINAPI * GetServerVariable) (
$ A) i8 E( X4 ]! {6 M) t, B# @struct _HTTP_FILTER_CONTEXT * pfc,9 W% `  N' \4 C$ z: A
LPSTR lpszVariableName,' H+ i7 R, t, F+ ?
LPVOID lpvBuffer,' i% i# `4 Q- p8 b; W
LPDWORD lpdwSize
3 ^1 E. p* z1 c: Z6 M# o. N: t& ]" H); . t- R3 L1 h: U* n- {4 g# H

) g: b# A, m  OBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头& S3 I4 {2 M6 q& G
struct _HTTP_FILTER_CONTEXT * pfc," p; ^5 U* N2 h+ S( h. h
LPSTR lpszHeaders,
3 [$ H5 {* Y- _- {4 G8 LDWORD dwReserved
. e+ b$ _5 ?9 b! t% T  H); : _1 u# y, f5 B8 R0 _1 ^7 y  @
+ [. y( v4 [9 U4 ?( L- k5 Y6 ^; n
BOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
& m/ ^+ f. u1 Z  v0 z8 f% ~struct _HTTP_FILTER_CONTEXT * pfc,* |( O5 n, n' C) }1 f: E! X
LPVOID Buffer,
7 k  M2 b; ]* ^4 c1 h* d- T7 B. ~LPDWORD lpdwBytes,
1 z8 e* q  g2 zDWORD dwReserved
/ I1 B( Y$ O1 E/ A; `);
: J: K6 Z, K7 m+ r; T9 r6 U8 U+ }" p- y* A  V4 o9 O5 H% U# I
VOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
" t' j4 P2 I6 Y6 j3 |! m+ Ystruct _HTTP_FILTER_CONTEXT * pfc,
5 {% K7 G8 ]0 U, e8 v. p! i- N  SDWORD cbSize,7 _$ F& C) H9 s* C; m2 V/ d* Y% L( ^! b
DWORD dwReserved. R9 `6 }5 ]- j. I
); , P' u2 q7 K/ }4 l$ {) A

- t2 o1 g- n+ v% n* RBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能/ H% V' q& {/ A. ~' o5 j
struct _HTTP_FILTER_CONTEXT * pfc,
1 s3 D& U7 R# F7 v0 S5 a) c+ S& benum SF_REQ_TYPE sfReq,
% w! T9 q  s4 R; t* j/ M# [PVOID pData,: ?2 q8 c6 W: }5 O+ Y, c' Z
DWORD ul1,3 O9 _( v6 B& a! j- t7 {
DWORD ul2
2 V$ l) F/ ~) D1 R- w5 u# J* U); % D, c! }& e2 Y- f$ p3 u+ d; s

1 }0 p! o) _- ~" Q! j& F7 e* r} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;4 Q$ N# Z; \/ B* b1 K/ y

2 d2 v$ y; W& i* x+ F1 q四、VC++ 6.0中对ISAPI的支持% }5 m: f7 t4 k& _9 i5 S& n, j3 J2 C

3 V: P# e* I/ D3 K$ u. d; |, P# L: _5 }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实例,每个3 m9 x* E" i* C( V! d9 k
CHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
) L# O" G2 L! ]. m7 N( B& C+ q5 x# C0 q
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。0 ?* q3 B3 q" {3 V

+ X4 [5 `' b3 f  u- _5 l) Y* aParse 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为例,该例中有下面这样一个表单:$ \7 _9 p6 Y1 o! H6 G

) H# Q, p5 w0 d; g) J2 _<form method=get action="pinball.dll?">
  L* R  r, b# H4 M<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
7 n$ |" M7 V3 N, Y' h. F0 Z+ C<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>
5 ?8 X  w, L7 S<input type="radio" name="Favorite" value="2"> Twilight Zone<br>6 h7 O# f' M) P" B
<input type="radio" name="Favorite" value="3"> The Addams Family<br>8 K& ]9 t4 e0 F7 w6 T- v7 d( Z
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
' @, _; @! ?& `, S! C& S4 V) q# y" A<input type="radio" name="Favorite" value="0"> I don't see it here<br>
6 E% }% ^" b) L<br>6 s( k0 n/ x* n" L; B' x  F0 ]
<input type="submit" value="Show Me!">
% X: }' S9 Z6 n</form>
+ Z' L% @+ b3 [( d+ q; f* n* j( i
1 Y. q' G/ \! P2 h' H5 Q2 t, G当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端" D8 p1 P& [" S  \- i
最终将得到如下的URL串:( L0 ]4 r% W; f: {" c  C
( F$ ]9 N( c. P7 H5 o) v+ A/ s
http://www.abc.com/pinball.dll?M ... GetImage&Favorite=16 \6 Y6 ~! H0 A% L

8 o6 ]; g- {3 B7 H; q+ C' H- z在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
$ N2 G% f9 d& Q$ ]1 y# D( L2 L5 ^) [将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:9 t6 m6 K* A* ?. U

/ f# J, R, h" {; g. m; j8 Dvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);: K' ?) W0 q. I5 V, X
5 {/ Y8 E* X/ j! V
而parse map需要按照下面的形式定义:
( _" N3 D4 I% E8 P  E9 ^
: }* N$ c# T$ `//CPinballExtension从CHttpServer派生而来- [3 V+ @* F# a/ Z
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
9 ~* u  |9 @$ _
/ c: ~0 }7 Z( J3 I8 z//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice" C+ C) i1 L( d
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
; ^8 B. U1 k; b" m
9 t5 Z$ b9 z$ j//该参数在URL中的名字为Favorite
8 K! l0 s. S. L5 c( a. nON_PARSE_COMMAND_PARAMS("Favorite")
0 P4 n2 F$ _; Q* }7 n  F9 c- w4 `# F7 e* y9 `3 A7 D& L% v  C( b
END_PARSE_MAP(CPinballExtension)
' [  j6 h# \5 x8 C" r5 T; K1 a. n+ _6 C, c
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:2 j: w. T0 _+ ]

5 [7 m1 g# x) _! ^8 FOnPreprocHeaders! P  r2 {9 r9 @. ~
OnAuthentication
) |$ {1 d; _# d6 R: NOnUrlMap0 P; b* G$ Y' \! F) u% j! e! B9 s
OnSendRawData6 Z# A% U% C' g5 M
OnReadRawData" U# Z" A* Y$ e
OnLog
  u! S3 R$ E2 v8 }0 e  LOnEndOfNetSession- I) f  H, w* U; ]. c/ |4 N% S% @

! o2 B2 m5 A$ k4 G- ?& u+ D" bMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
6 Y3 E4 J, Z- \- `% c6 T! @5 q7 g; D( i
参考资料:( j  G3 B% n& E( K& z/ x

5 h3 S4 r9 t4 Y1 `4 k1、MSDN# U% \  K) v4 E  f) y. C! \
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-11-14 19:56 , Processed in 0.020654 second(s), 14 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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