|
|
作者:netguy (mailto:netguy@nsfocus.com) , f! C. P1 T7 E; n C
. S3 f" `; k5 Q: c5 JISAPI(Internet Server Application Programming Interface)作为一种可用来替代CGI的方法,是由微软和Process软件公司联合提出的Web服务器上的API标准。ISAPI与Web服务器结合紧密,功能强大,能够获得大量的信息,因此利用ISAPI可以开发出灵活高效的Web服务器增强程序。由于ISAPI程序与Web服务器的关系,使得ISAPI接口在安全方面有一定的研究价值。本文主要讨论ISAPI在IIS和VC++ 6.0中的实现。
. O( s, y- c! ?4 a0 d5 Q, V. B) g& ~) [0 i, \7 ?8 V
一、ISAPI接口和CGI接口的不同。
) S. V5 j4 U) T. R$ J5 `* ~. C7 W- i% _' w9 I5 B1 A, \8 i
ISAPI程序和CGI程序完成类似的功能,但是实现方法不同。( D. v% S; ]9 v% D4 D% C4 @
6 o" { ^6 h& Q
1、ISAPI程序以DLL形式被Web服务器加载到自己的进程空间中,因此和服务器共用同一个地址空间,且在没有客户请求时可以将其从内存中卸载;而对客户端发来的每个对CGI程序的请求则需要服务器为它单独启动一个进程,这需要耗费大量的时间和内存。当并发的请求数目很大时,使用CGI在效率上不如ISAPI。
8 @& J- ]+ c: ^
2 k$ s4 Q+ k% a! ?3 V" j, b2、CGI程序通过环境块和标准输入输出与Web服务器进行通信,而ISAPI程序与服务器结合得更为紧密,与服务器共享同一个进程上下文,主要通过一个参数块与服务器进行交互,可以从服务器那里获得关于当前HTTP连接的大量信息。
. O! H% v- `% Y7 I5 o- L
! `# D2 k1 u/ j6 ?9 N: iISAPI主要分为ISA和ISAPI Filter两部分。ISA方法相对而言要传统一些,利用一些特殊的链接,指向服务器的作业,供程序开发人员设计一些扩展功能;而ISAPI过滤器则倾向于构造服务器直接调用的模块,提供一种无缝链接部件用于监测直接来自于服务器的HTTP请求。1 [3 G5 I9 X1 y2 X$ _- A
9 J1 d6 J4 u$ P9 {/ i }8 F) c1 T
% L- N9 e0 R" {; |6 R: f
二、ISA
6 ` {9 D$ L+ ?6 n1 _7 n& _$ e
% m4 b6 Q! r% w! Z' B, l+ c+ aISA(Internet Server Application)也可称为ISAPI DLL,其功能和CGI程序的功能直接相对应,使用方法和CGI也类似,由客户端在URL中指定其名称而激活。例如下面的请求将调用服务器的虚拟可执行目录Scripts下的function.dll(ISAPI DLL必须放在服务器的虚拟可执行目录下):3 l& Z+ s, D( a$ K
http://www.abc.com/Scripts/function.dll?
$ O* m- { s; e) u$ I+ g2 G# H' f$ p! c2 T( Q6 } R/ ^/ t
ISA和服务器之间的接口主要有两个:GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必须在其PE文件头的引出表中定义这两个引出函数,以供Web服务器在适当的时候调用。, S) Q7 z$ |% X( X0 N% R! \
* d! p# C* ? c. t- x6 ]+ l3 F
1、当服务器刚加载ISA时,它会调用ISA提供的GetExtentionVersion( )来获得该ISA所需要的服务器版本,并与自己的版本相比较,以保证版本兼容。函数原型如下:- E/ P6 O# w/ W, p4 O" @
" N5 R8 p# A1 j2 }& ]+ lBOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
5 U8 |3 e. f2 ?; Wtypedef struct _HSE_VERSION_INFO4 T" n! C( y) F0 Y2 j4 {3 H
{
/ M Z4 F/ X/ c2 y1 C$ T! t! \* a& MDWORD dwExtensionVersion; //版本号
) Y2 _5 O: }) i) Q1 b$ {! i; ]! ZCHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //关于ISA的描述字符串
4 ^6 {3 T4 ]" q0 s5 {} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
|) D9 @2 \7 K9 d" N8 V: L9 e& `2 K6 z3 m. r
2、ISA的真正入口是HttpExtentionProc( ),它相当于普通C程序的main( )函数,在这个函数中根据不同的客户请求作不同的处理。服务器和HttpExtentionProc( )之间是通过扩展控制块(Extention Control Block)来进行通信的,即ECB中存放入口参数和出口参数,包括服务器提供的几个回调函数的入口地址。函数原型如下:" K! S; t& X9 b3 \
9 e7 o$ h1 D: X& f1 {& k6 `: S9 `0 {
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
. B4 \, u2 t8 D1 e: C2 ~5 W9 Y* ^$ _! @, w4 l5 s' Q
ECB的结构定义如下(IN表示入口参数,OUT表示出口参数):9 D4 R. s6 g# X$ D- ? R. \
) p! ^$ i! C, ]$ m
typedef struct _EXTENSION_CONTROL_BLOCK
]( u+ h) R6 z: Z{
+ O2 E9 ^, s4 `DWORD cbSize; //IN,本结构的大小,只读
( G3 q( V4 _% P1 S1 cDWORD dwVersion //IN,版本号,高16位为主版本号,低16位为次版本号7 w0 o+ c1 T, B' C( z! @! S# c
HCONN ConnID; //IN,连接句柄,由服务器分配,ISA只能读取该值) g' U% {3 U- T6 y/ e8 {
DWORD dwHttpStatusCode; //OUT,当前完成的事务状态
1 x9 f6 Y' J) o1 }CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要写入到日志文件中的内容
% u, E- Y1 \! a# d1 T% g; o6 TLPSTR lpszMethod; //IN,等价于CGI的环境变量REQUEST_METHOD
' m) k8 }' _8 ]8 J0 y7 ZLPSTR lpszQueryString; //IN,等价于环境变量QUERY_STRING
' v! I7 V: Q2 gLPSTR lpszPathInfo; //IN,等价于环境变量PATH_INFO
3 [8 H. c# B v2 ]/ @8 eLPSTR lpszPathTranslated; //IN,等价于环境变量PATH_TRANSLATED. Z) h0 M( n( [, t1 v; d( j
DWORD cbTotalBytes; //IN,等价于环境变量CONTENT_LENGTH4 o+ R& T1 C, T7 K& {/ z0 _
DWORD cbAvailable; //IN,缓冲区中的可用字节数* W* v! u7 }7 A
LPBYTE lpbData; //IN,缓冲区指针,指向客户端发来的数据1 h% h5 g+ n0 q7 U5 i
LPSTR lpszContentType; //IN,等价于环境变量CONTENT_TYPE3 c+ ]: a8 e8 @5 t
5 y: \" @# Q% f( _! z//回调函数,用于返回服务器的连接信息或特定的服务器详细情况
" b- f" H7 s" WBOOL ( WINAPI * GetServerVariable )
3 C/ {9 i. c- ~: ?( HCONN hConn,
9 J) L& g/ N4 H h+ HLPSTR lpszVariableName,/ E4 U( h% @) Q3 C& @# F
LPVOID lpvBuffer,
% T0 d8 o; r- s' e) \: r+ ~/ \LPDWORD lpdwSize );
, f+ H5 K" ?$ h. s" \ s3 S; I: }9 p: O" U% h3 [/ Q
BOOL ( WINAPI * WriteClient ) //回调函数,从客户端的HTTP请求中读取数据7 b) U2 s* o0 Q. J; ~8 I
( HCONN ConnID,# [, d2 ^5 R% k+ v7 A, q
LPVOID Buffer,
, Q1 ?' L9 e8 ~0 I/ R! t* A) [* TLPDWORD lpdwBytes,
! U" V: U$ }+ G- h @; x+ yDWORD dwReserved );
- z! ~/ U+ g4 t4 x6 `+ ~' m2 u% C. b
BOOL ( WINAPI * ReadClient ) //回调函数,向客户端发送数据
( A) N9 _* \$ ~. a+ C( HCONN ConnID,
% G/ y1 V' R9 y$ ?1 k3 SLPVOID lpvBuffer,- [6 E J0 `: E3 E7 z+ w8 X' d
LPDWORD lpdwSize );
. p) n2 c3 i0 U( z, `! w7 s2 M! E3 P2 H; I, I. Y, r5 k/ `( a
BOOL ( WINAPI * ServerSupportFunction ) //回调函数,访问服务器的一般和特定功能/ B+ d& T t5 `* L1 N
( HCONN hConn,7 B9 h; t/ M7 f# e1 @
DWORD dwHSERRequest,4 f2 J) h; h8 _
LPVOID lpvBuffer,$ C. ~( Q0 T9 w0 b: V
LPDWORD lpdwSize,' ?1 W0 y. ^ J9 R
LPDWORD lpdwDataType );+ l1 \& x1 s% X, j# U0 H0 d o& r
9 [% u2 v7 e& H% p8 r2 U} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;. s+ c1 h$ p$ p( }
# T" @; s! H. U1 c2 C9 Y* P
在上述ECB中,服务器不但提供了当前HTTP连接的句柄和一些变量,而且提供了4个回调函数给ISA调用,从而使ISA可以获得更详尽的信息。# R0 o- i4 ^1 K1 |5 u) M
/ Q& R* F/ Y/ O, b" I4 {3 T" H% i* f
三、ISAPI Filter5 A! k) |8 i8 k5 n! f, R
. B7 \ z7 p; N+ c* g# z# x/ B
ISAPI Filter位于服务器和客户端之间,能够对服务器和客户端之间的通信进行预处理和后处理,比如对通信进行加密/解密、提供对客户进行身份验证的新方法、提供自定义的日志记录等,在CGI中没有与ISAPI Filter直接相对应的部分。# e8 c* t* \/ M+ S' H
' w' w: A% A% u& ^+ Y
ISAPI Filter与服务器之间的接口有两个:GetFilterVersion( )和HttpFilterProc( )。任何
+ S) g4 [0 ]% ?7 uISAPI Filter都必须引出这两个函数以供服务器调用。' ~+ n6 o6 _* L/ h2 C/ r# W
4 o h* Q8 ?& m }2 d" J9 f1、在注册表的如下键值中存放着所有ISAPI Filter的文件名,IIS服务器启动时从该键值中获得) b2 N, x! T' O! ]$ e' a" m8 r
Filter的文件名并加载它们。- ^7 B! |* b- d+ R
" q4 `" z# } |4 B$ u
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
2 L# q7 [! W# Z
5 o+ q3 ^9 j% ^: D' j( O1 I2、然后服务器调用每个Filter提供的GetFilterVersion( )函数,获得版本号以及该Filter希望处理的事件,即ISAPI Filter通过引出GetFilterVersion( )函数来告知服务器自己希望处理什么类型的事件,因为ISAPI Filter是通过事件来激活的,当满足条件的事件到达时,服务器就会调用Filter引出的主函数HttpFilterProc( )对该事件进行处理。GetFilterVersion( )的原型如下:. V8 h# G8 A# N6 y6 T; y, [4 j! z- W
' H: L6 O4 y; }, ?BOOL WINAPI GetFilterVersion( 0 k& B9 {( T( Y( J# X3 x
DWORD dwServerFilterVersion; //IN,服务器使用的版本规范
+ N0 P/ N6 A1 J; w" I6 MDWORD dwFilterVersion; //OUT,过滤器使用的版本规范4 R7 n* v( E! v( M$ s; V
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,对该过滤器的描述字符串
0 R$ S: k8 p! e4 V B; gDWORD dwFlags //OUT,事件和优先级标志
% ?9 O- _7 a; }% D ~" v8 O" R4 n);
q/ ?0 E4 V- y$ H! k9 D, ~ Z9 v' b! }2 [8 `9 }
事件和优先级标志dwFlasg的取值在MSDN中有详细解释,其中包括该Filter被调用的优先级,一般应使用默认的低优先级,否则可能会对系统的性能造成很大影响。
0 q: ]9 u# V. c0 I0 `6 u3 U
: o r# H, L$ ^: q6 D+ A4 ~3 W3、HttpFilterProc( )是ISAPI Filter主要的入口函数,它根据当前的事件的不同作出不同的处理。服务器通过如下的参数块和Filter进行交互,这个参数块的作用和ISA中的ECB类似。: X9 H4 n R8 f, v" {
# N9 M1 A: o2 U2 u! j7 f3 J2 P g- ]
typedef struct _HTTP_FILTER_CONTEXT3 F. f- P4 d+ n1 Y5 y
{8 F/ K X$ ~# y5 w
5 U7 J6 Q# N: x3 l3 GDWORD cbSize; //IN,本参数块的大小
2 {( } H8 D, CDWORD Revision; //IN4 Y2 t; X, H; {$ Y2 Y" A
PVOID ServerContext; //IN,由server使用本参数$ S* v7 M4 y) O9 c
DWORD ulReserved; //IN,由server使用本参数4 X* X: A2 `) Y* d/ x
BOOL fIsSecurePort; //IN,事件是否发生在安全端口上
$ n% C/ i g2 j. _2 iPVOID pFilterContext; //IN/OUT,与本次请求相关的上下文
! b, ~9 ^/ c- g# M& o1 c# T, Q+ U" Q' \& G+ Z( }8 m
//回调函数,取得关于服务器和本次连接的信息6 ~+ J+ L$ j8 w; G, \5 t- I- s
BOOL (WINAPI * GetServerVariable) (
I8 y0 v5 `4 J6 R' [* H- f8 Istruct _HTTP_FILTER_CONTEXT * pfc,0 t/ `' g8 z% [8 H8 m0 u
LPSTR lpszVariableName,! J, ^$ E! V/ W8 l
LPVOID lpvBuffer,
8 z# s8 Y R7 ^% G7 |LPDWORD lpdwSize
) v) ~4 J- M' A4 x8 N: `0 a, i);
0 C7 U! N) f4 p' L7 L) [+ S
/ H# c' ]& t" W5 iBOOL (WINAPI * AddResponseHeaders) ( //回调函数,给HTTP响应添加一个标头
+ X' C( M* I+ b' A5 g& o5 E: C2 |% Lstruct _HTTP_FILTER_CONTEXT * pfc,0 f* E* ?7 A( N7 _; a; y8 M3 w# B
LPSTR lpszHeaders,) e# v. e: f% f( n
DWORD dwReserved
" Z5 S: f: |5 m* Q: x); * v' i% ]6 s. @" j9 z0 B/ ?
7 g2 o( }" q) wBOOL (WINAPI * WriteClient) ( //回调函数,将原始数据发送给客户端
; j# f/ {( W* }9 L1 y5 Estruct _HTTP_FILTER_CONTEXT * pfc,
% d' A3 D7 M) N }6 K$ M J5 J4 ELPVOID Buffer,! |, t3 y" Q4 C3 ~+ L/ _
LPDWORD lpdwBytes,/ T2 w; ^$ h9 Q1 i: r+ Z$ U1 ~
DWORD dwReserved
) Q& O4 z1 B/ G1 ?; [* H; v( ~); ( U; L! q1 m% T5 P0 g
- `+ Q- G0 G3 R6 wVOID * (WINAPI * AllocMem) ( //回调函数,分配内存。
4 E; M/ y( i1 r" Q/ ~7 Sstruct _HTTP_FILTER_CONTEXT * pfc,6 s3 q1 g$ u9 @* n. {6 E
DWORD cbSize,
' ?2 z2 H" q7 hDWORD dwReserved
, j: c. h Z. x8 l); 2 J7 V; ^! v7 q
p/ j+ }& o3 f/ n& ~1 @BOOL (WINAPI * ServerSupportFunction) ( //回调函数,访问服务器的一般和特定功能
4 m$ ~/ D- d6 G7 B' M7 X" Mstruct _HTTP_FILTER_CONTEXT * pfc,1 S8 L6 Y7 y" }) m3 `; k/ d5 J+ `
enum SF_REQ_TYPE sfReq,
* t H- [( }/ f4 t8 d4 e- vPVOID pData,- u/ z0 B0 h. Z0 j
DWORD ul1,9 [6 O7 r: R0 [; o- t
DWORD ul2
1 i# F8 L$ B g+ L! A" I5 @1 v- k( X, y! P);
) y* f, g6 j0 h- H6 V# F. n. J' c, ~. }8 h+ t. B' {3 o% T& G; ]1 V
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;, o0 N u" b$ m
8 t- L R W- ~, c! `8 _四、VC++ 6.0中对ISAPI的支持
1 m9 O1 n, _' w* f
+ }& w' |8 @: y3 O' q5 i5 vVC++ 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 N, @5 H4 O5 e/ XCHttpServerContext处理一个客户请求,这样可以处理并发的HTTP请求;CttpFilter和CHttpFilterContext之间的关系与此类似,在每个ISAPI Filter中只能有一个CHttpFilter实例,但是可以有多个CHttpFilterContext来处理并发的事件。CHttpServer和CHttpFilter是独立的类,它们可以共存于一个DLL中,也可以分别在不同的DLL中。
# Z) N6 o+ }% h% Z( J I+ _+ l m& Z- O' [. d/ v
一个ISA可以提供多个命令,每个命令对应于CHttpServer(或其子类)的一个成员函数,客户端可以在URL中指定命令名及其参数。在VC++ 6.0中是通过parse map来实现这种对应的。
% k! [# ]( J- _2 R) \2 E- \( I% J B0 K7 \9 ~ M% Y* m
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为例,该例中有下面这样一个表单:
. I8 p; p: L7 d E9 Q% J; L9 C5 Q |# H4 O+ \) E( M
<form method=get action="pinball.dll?">: y# y& v) S5 l' O# O7 C6 g8 ^
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
+ r% a0 |; ~$ E: e1 d0 y<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>/ Y' F' l' T3 L1 R7 ~# Y" i
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>; p$ b( j# }5 S! l; a2 E
<input type="radio" name="Favorite" value="3"> The Addams Family<br>3 \$ c, m% g2 t
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br> m1 M" h! W S' b4 ^+ }$ \
<input type="radio" name="Favorite" value="0"> I don't see it here<br>
& R2 T- }% I; X" A% F# [<br>4 {4 W$ X1 O3 w+ h8 y* v
<input type="submit" value="Show Me!">! F3 \3 M* g% q8 a( [+ Q& N7 O" d! B
</form>
2 f" @6 u1 e5 c5 b1 W/ _
1 }; F9 r* S& w. ~当客户端选中了上面的表单中的“Attack from Mars”这一项并点击了submit按钮后,服务器端' x! i: Y) R+ E t; P5 w9 u& t J
最终将得到如下的URL串:
4 J9 Y9 a9 s3 ^( h' K ]# |: c
k& Z b" X8 {8 @( p) K1 xhttp://www.abc.com/pinball.dll?M ... GetImage&Favorite=1! \2 u7 K1 W4 Y; a) {
R+ e- Z3 ]8 `+ Y7 i在该URL串中,命令名是GetImage,参数Favorite的值是1,因此pinball.dll中的如下成员函数
9 F% m: h" x. |: Z/ i8 l将被调用以处理该请求,其中参数dwChoice对应URL中的参数Favorite:6 s# Z1 E" @1 F: H% E9 P. {" g2 z
8 V) T9 ~' r, hvoid CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);7 s& W6 t4 O. g$ o
0 {1 b+ x) Q7 n7 i而parse map需要按照下面的形式定义:9 _8 H6 W$ \) x1 ?6 B8 R$ M3 m
! ~# h- s2 R& q, o3 X: |1 g
//CPinballExtension从CHttpServer派生而来! k% `! [3 t0 I
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer) / J+ e6 _- c3 u' h, D
5 s6 u$ l1 e, _ P p0 f% s' ^
//GetImage是CPinballExtension的成员函数,且有一个long型的参数即dwChoice9 W) O! N+ J" y$ v6 w$ [' j. U
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
" b: V; D7 I8 H1 q7 n) V- Y3 X, }
% D+ A h, F$ P$ i2 o6 Z- W7 f8 J//该参数在URL中的名字为Favorite2 N/ [& @7 s- ^; X
ON_PARSE_COMMAND_PARAMS("Favorite")
( C& J. F" E% x& `' ~" T) _
) Y" G F: B( z" H7 @END_PARSE_MAP(CPinballExtension)
_1 M0 l; H3 L" X' o' h4 E: M0 x& M ?2 W4 b
而对于ISAPI Filter,在VC中可以通过重载CHttpFilter(或其子类)的不同的成员函数来实现对不同事件的处理。可重载的函数如下,每一个成员函数均对应一个或多个事件:% q8 K* V5 c: J7 J6 q- u) ^( S
" s F0 ^" h+ P
OnPreprocHeaders
- j/ W) F M5 g+ D0 zOnAuthentication
& T% H c' C# |8 z9 I# KOnUrlMap
8 i% t0 O+ D, J, Q& fOnSendRawData9 X& a+ j) Y9 h
OnReadRawData
4 |; _& X$ Q3 r; XOnLog" c2 a$ H1 V9 W1 W9 p: M
OnEndOfNetSession
- \( V* w: B5 H- Z
7 L7 f; `0 |9 w7 tMSDN提供了4个关于ISAPI的编程实例:counter、MFCUCASE、pinball、wwwquote,有兴趣的可看看,本文主要不是介绍编程,所以不再赘述。
& i9 Y2 `: ?0 \- L w' N) X" l5 g, o: P' L* K
参考资料:' f* s! R2 R* |6 m: N
' `$ ~+ c* @, h7 L7 {4 m* l1、MSDN! E- b5 i/ Z8 I4 U: ^/ D
2、《精通CGI编程》,丁一强等,清华大学出版社 |
|