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

[转载]IIS的ISAPI接口简介

[复制链接]
发表于 2004-6-9 04:43:55 | 显示全部楼层 |阅读模式
ISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。   m" l, w5 ~3 l

0 \6 Z0 U; j7 i" \+ G一、ISAPI接口和CGI接口的不同。
1 m+ H9 R- m& y0 U- ?1 s/ r7 t. |
2 G- d' q# e, e+ Y) X) {$ `5 b& ZISAPI程序和CGI程序完成类似的功能,但是实现方法不同。
, X9 U$ j$ Q) p& C8 Q; L
+ [2 X$ \5 x5 Q  a5 M. _1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。 4 {7 E2 S% |; T
: X# R/ c) O9 f0 C/ S$ H6 B
2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
1 _; S) f) g0 s9 R2 @/ y, z( Z0 R. K; t! p" {( b
ISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。 1 y: i; L+ a0 o7 G5 f+ r% ^7 p

6 `% z% T8 ^) O& [' s9 B9 J1 @" T" a4 a/ N) w" Z7 O5 L/ C
二、ISA
; c6 s* H/ z: N/ g+ _
- `7 A0 I/ v. N3 O, H) p7 l6 VISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下): " c) y" J- D* x+ t5 K) t3 c; f
http://www.abc.com/Scripts/function.dll? 2 M3 t, o, V" g+ T# _$ Y& U6 N3 r
+ X8 E  o% {0 L/ c8 t
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。 ( T* t0 e/ H( I& K* x
) |: R, s; A" A
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:
  y/ o" |7 D% f, f- Q+ }  r( x, X0 O; Z) t7 ]
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version); ! q. l. ^! {- T8 h9 j. b: p
typedef struct _HSE_VERSION_INFO $ o" S$ Q+ `9 l# p7 f7 s/ P
{
3 _4 L6 K8 f! I: T9 r7 a6 Q4 EDWORD dwExtensionVersion; //版本号
$ P4 {$ \. L2 g/ ?- NCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
9 |6 h; j" o! V! d} HSE_VERSION_INFO, *LPHSE_VERSION_INFO; , @; M% j8 d+ H
2 A7 z) Q2 e) p& N3 p" Y9 ]# ~# U
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下: ! E- K6 i! t& T. [8 o7 _
' c9 g7 ~4 O0 x( L( ?) z
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
# J3 t$ T! U$ Q/ B5 D" q% d* g/ i1 E9 U! S* X) |! |) ~
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):
! H' X" [% x6 B! d% j  t# X, x- }
3 p- g( w( b/ B) l2 ttypedef struct _EXTENSION_CONTROL_BLOCK
8 Q3 L8 y5 E( A3 |( V2 M{
8 Y. i) Q2 m" i5 c% _8 G0 RDWORD cbSize; //IN,本结构的大小,只读 % P) n* S  [3 I; F3 W0 K3 t+ n- g
DWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号
" F% |& {7 F. [8 sHCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值
* \8 D* k6 V5 P$ [' p* rDWORD dwHttpStatusCode; //OUT,当前完成的事务状态 9 e0 X! m# c7 z
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
0 W3 q8 w% v7 J$ Z8 z: tLPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD ) y# |( ^3 @% `1 ^/ U
LPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING
; q+ v5 d+ K  n2 @% Q3 ULPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
$ w# @! L$ L" h9 d8 oLPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED * t1 b( ?  w' g1 G+ ~# p
DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH
* b) m" t6 e' K' L2 e# uDWORD cbAvailable; //IN,缓冲区中的可用字节数 + a9 }, W/ v! S3 q! R
LPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据
5 f; l+ p. t/ m0 e, F  WLPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE
5 M  L7 p: x, s$ W3 b. A+ j! T- u% M2 W! g# z& X$ \5 N
//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
& f5 Z1 ?& L. v/ |3 _; e( gBOOL ( WINAPI * GetServerVariable )
6 N- ?- `7 n# D& k( HCONN hConn, 0 m# x: {; Y( B/ x# G' ^
LPSTR lpszVariableName, / n% l1 N! J2 b+ a  k: k# x7 O' ]8 d5 w
LPVOID lpvBuffer,
! {0 z0 ]- A( @2 Y6 V4 y) C/ ALPDWORD lpdwSize );
/ L. V) q( k( H1 y1 O$ \( R
, J3 e, ]- F; Q  E6 e7 RBOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据 # d' C0 I. b5 m6 G' r. {
( HCONN ConnID,
2 D( a: n+ I+ u$ K3 f1 `4 QLPVOID Buffer, . n/ v1 y8 a  ~) _
LPDWORD lpdwBytes,
% J6 l# q. T% m, L) h: i* ]6 [DWORD dwReserved );
1 y( u$ y( p- n# q1 f, _2 R) \7 ~. r" E' p5 q1 C
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据
) ?4 \; x9 Y3 C: A( J! s& w' m- K( HCONN ConnID, 8 i- H  W% f' ?6 R$ C
LPVOID lpvBuffer,
. w7 e" ~0 c- ~- |& p- w) kLPDWORD lpdwSize );
' }: L  k: P2 U- w+ O0 z( `8 y" j$ r2 Z3 O- A
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能
0 p  a; A+ I# l: p) m( HCONN hConn,
& Z$ K- P* ^9 w( ^  w9 m: P2 eDWORD dwHSERRequest, ; E2 g; b! T, @. p( G
LPVOID lpvBuffer,
; G. p4 S8 f% y4 jLPDWORD lpdwSize, $ ~$ x. W: t' D7 `
LPDWORD lpdwDataType );
# q% H0 z0 Z3 Y8 c4 a% S2 q% v7 [+ u
. x2 k# t/ C2 u, C} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
9 B- l7 |* }/ S9 ?% @( k) X% B
  u$ T* ?& e; A% c3 f在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。 & l7 A$ h' U* w

( G$ F* b0 Q  a% X. x三、ISAPI Filter # t# |9 S! T. U+ H5 M  ~4 a/ u

. R$ E7 C9 ~) F$ JISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。
3 W( K/ G) t" p/ W- O$ J9 n8 H  `, x  ~/ {" L; R
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
: x2 D, P% S. u* {/ v7 XISAPI Filter都必须引出这两个函数以供服务器调用。
! a$ o, T  W- V4 M# F' @! P/ B# A- _# s4 a# Q2 G
1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得 - ?) n& [5 q4 N8 l  g
Filter的文件名并加载它们。
# R, Y8 X+ ~5 _) t! ~& k
, }$ F6 ]/ R- S% _HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
/ u$ n8 M# F( Y
) |* B* y% a. ?2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:
7 `* i7 P5 h8 V" l6 ]
- O  M1 `+ o. b9 m( a, c- QBOOL WINAPI GetFilterVersion(
2 f# y& O7 Q2 _, u& H2 ?DWORD dwServerFilterVersion; //IN,服务器使用的版本规范
1 x) g+ n9 d3 z0 `7 O4 MDWORD dwFilterVersion; //OUT,过滤器使用的版本规范 ' z. M+ u* h) @7 ~' m
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
, {# T% w! K* f3 M6 l, l7 @4 X. mDWORD dwFlags //OUT,事件和优先级标志
9 p% c% f7 f2 M+ ?5 X0 b9 Z  V);
% P; v5 c  Z' G7 e* g( g9 Q7 C# q+ A& }: G
事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
. |( ~& ?' K0 @  W# t. c6 _; i" M& G  A' P' x4 K9 ]
3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。
) _; {0 T5 j0 u% v7 a- p! b4 Y8 ]2 M6 A; }
typedef struct _HTTP_FILTER_CONTEXT 1 F; b: C; Q. V' c2 J' O" i
{ ! u2 @7 B* f% @$ V; n2 n; g% j

! b8 q1 P% Q) t, B! D$ ODWORD cbSize; //IN,本参数块的大小 4 z" B! n  v, y8 t2 y6 ~4 H
DWORD Revision; //IN # ^8 ?0 J2 o" J& _3 _0 W
PVOID ServerContext; //IN,由server使用本参数
+ U; ?* d4 a6 y# J$ K7 JDWORD ulReserved; //IN,由server使用本参数
! S% R2 ~9 S6 R$ H; i; `BOOL fIsSecurePort; //IN,事件是否发生在安全端口上 : K- u7 `7 d, J2 x3 O+ _
PVOID pFilterContext; //IN/OUT,与本次请求相关的上下文 & ?! L3 M6 Y$ L$ }8 x
( d/ f, w! y  R2 ~, \
//回调函数,取得关于服务器和本次连接的信息
! t0 c3 P  X0 @& `BOOL (WINAPI * GetServerVariable) (
* j* _2 D1 N8 f! k2 ~& z* cstruct _HTTP_FILTER_CONTEXT * pfc, 6 r* I5 _% `7 ?, w1 z) H& j
LPSTR lpszVariableName,
3 n# s8 s3 F! W  }: ?LPVOID lpvBuffer, 1 |5 @4 o. e! y+ d% q
LPDWORD lpdwSize
3 a( c" P# O+ U0 z3 X6 A);
; Z9 R# H( J+ \/ h# B  [/ ^6 y  \2 x1 I
BOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头
- |% b( ]- R* H5 w& P5 Z0 ~8 Cstruct _HTTP_FILTER_CONTEXT * pfc, & Z2 p/ f$ w" B7 x: W( Z- D
LPSTR lpszHeaders,
( f1 X" f3 B; c4 q* pDWORD dwReserved & J* W; F/ |. C
);
9 H; U: r9 J% U6 W! P' W: I2 J
( Y8 L. }  f5 V& L" c, R- b2 KBOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端 % b. F) `& Q4 e" j) B. B
struct _HTTP_FILTER_CONTEXT * pfc, ' R! f: q5 ]: g
LPVOID Buffer,
$ l; [/ R# I8 k% ~LPDWORD lpdwBytes, . S+ v; c3 R/ r  ~
DWORD dwReserved
. b% B% B8 j5 U7 i); % o3 k- p, Q: m: q

9 k% ~( O2 J: nVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。 0 {% v) F; x7 r- u9 n, W0 ^9 p% i4 |
struct _HTTP_FILTER_CONTEXT * pfc, 6 y3 L4 m2 q; F7 N: B
DWORD cbSize,
5 v5 O2 w% |" u9 a' g! u' Y7 I/ \DWORD dwReserved
$ ~& K" \% Y0 }, u* e+ v); 4 @7 k, b& Y) T: d2 I9 D( e

) a0 W3 N* Y3 }& w. |' C& aBOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能 ; m* }4 w8 m7 r) r& F
struct _HTTP_FILTER_CONTEXT * pfc,
* k" @$ c2 ^0 ]9 r% K- p4 ?: r# genum SF_REQ_TYPE sfReq, 1 H$ w  v/ n' O
PVOID pData, : g3 D5 y" C; w. f! l% B1 z7 B
DWORD ul1,
" U7 c/ _: F3 z$ EDWORD ul2 2 S) `: O% |) x( p; l, W7 J1 a7 e. O
);
; d* o" B' I2 t8 y7 L4 I# }  ^
" Q& A1 v9 O9 R} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT; # t9 X6 W( K! f- |& [
& O( R; O9 A" n3 b7 m1 s+ @
四、VC++ 6.0中对ISAPI的支持 & K6 c" \( J8 b4 o$ E- |* _0 z
  C  {# @& I/ D0 c" _, C
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实例,每个 7 V$ K7 l& w0 V5 |- V5 j
CHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。 % ?& E3 w2 T9 u
; }0 w; p2 g/ L1 `! [4 T, t0 j& N
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
2 ?  `! I6 L$ `8 |1 ?  d+ E$ Y$ D8 B/ O7 Q
Parse 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为例,该例中有下面这样一个表单:
# t1 \, y9 {- B' A& \
5 N' ^" x5 C- I" `0 D<form method=get action="pinball.dll?">
6 H  g2 M, c# `& T<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
' v" \  Y0 D/ ~0 L5 w& U( t6 Q<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br> # z' u- q6 l0 e, W! a. l3 }
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>
9 C- i+ [( H% L" u& m<input type="radio" name="Favorite" value="3"> The Addams Family<br>
4 h* }$ v  T( j3 Z3 _<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
! `* ]" W$ A5 X: h  K<input type="radio" name="Favorite" value="0"> I don't see it here<br> + z6 b+ f; E& l* C7 X5 m/ ]
<br> # n. p! F. v/ K) `7 Y! r4 F
<input type="submit" value="Show Me!">
7 ^) _! j. M) B) A' D</form>
4 {1 P+ k9 Q/ S0 K  N8 [0 U
% _  ?! b2 c- u* H2 }; }当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端
# ^$ W7 ]# Q6 S1 i最终将得到如下的URL串:
3 {7 @& G3 }1 k6 |" N8 y# p( x
5 V+ D' T* p6 rhttp://www.abc.com/pinball.dll?M ... mage&Favorite=1
& G. s" E6 u- B' z  p  Z# \4 t1 j+ S0 _; A
在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
# S; s% o# u% c7 k; ~: A将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:
& n+ l" U: G) _/ x% ?0 z
+ g4 V. B2 g. `& e0 Yvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice); - r5 o% X% E9 o& K" y  ^$ J

$ y, T/ i8 u* {而parse map需要按照下面的形式定义:
: I, [1 P0 e, e1 H
. q7 S0 x$ Q' Y( k8 C* M5 [& d+ U//CPinballExtension从CHttpServer派生而来 ' y9 i" M7 t8 J  F+ p- E7 I6 F
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
6 F0 _- I4 p- H  Y/ r% m; B( V! i
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice ! T2 o# `: g; ~) {8 Y
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) * m6 l7 r3 L+ E
! F6 R. `5 e' r8 f& b
//该参数在URL中的名字为Favorite 7 R0 x& X0 {- K7 g, |
ON_PARSE_COMMAND_PARAMS("Favorite") 4 l$ C+ i7 S( d. w
3 x  j# |6 ^/ v& O
END_PARSE_MAP(CPinballExtension) 6 {9 I, U- B% {3 y  |6 F

4 L$ r2 b1 S- F+ x而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:   L/ p9 E  o+ g5 k+ ?5 W0 b) p

: ~, j( l; E/ s7 I+ K+ \- _" pOnPreprocHeaders
; Z& u2 x6 k! I& u6 z6 _OnAuthentication $ v9 k* c) C3 a
OnUrlMap
3 j) K& D4 b- t' m. z# ?OnSendRawData % W5 m  U) U3 B& c. k5 M
OnReadRawData ; S9 ?: _4 D; l
OnLog ( k5 E5 ~( S% Q8 v
OnEndOfNetSession
9 f& h- p) `2 u3 U1 F0 S: L
) ]! z) [7 w- \! m3 \0 L6 uMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
& Q# _) b/ N6 O. r1 p" Q
# D4 T, }  |8 J) X7 l参考资料: ' s! o! ?6 Z6 z, I1 ^7 a

& K6 V$ `( }+ m0 L4 `+ A1、MSDN 9 D1 D" q; s' f
2、《精通CGI编程》,丁一强等,清华大学出版社
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-5-2 12:14 , Processed in 0.018450 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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