因为ISAPI扩展和调用它的进程(IIS)在同一的进程地址空间中,这样它们就可以互相直接联系。这种方式一个最大的隐患就是会导致整个IIS当机,而且在有些时候是整个网站当掉。看一下下面的图:
你看到,如果ISAPI扩展程序遇到问题且处理不当,将会影响整个网站的服务进程。就像上图所示,ISAPI扩展和IIS的通讯是通过一个ECB(Extension Control Block)结构的指针,其结构然如下:
typedef struct _EXTENSION_CONTROL_BLOCK
{
DWORD cbSize; // size of this struct.
DWORD dwVersion; // version info of this spec
HCONN ConnID; // Context number not to be modified!
DWORD dwHttpStatusCode; // HTTP Status code
CHAR lpszLogData[HSE_LOG_BUFFER_LEN];// null terminated log info
LPSTR lpszMethod; // REQUEST_METHOD
LPSTR lpszQueryString; // QUERY_STRING
LPSTR lpszPathInfo; // PATH_INFO
LPSTR lpszPathTranslated; // PATH_TRANSLATED
DWORD cbTotalBytes; // Total bytes indicated from client
DWORD cbAvailable; // Available number of bytes
LPBYTE lpbData; // pointer to cbAvailable bytes
LPSTR lpszContentType; // Content type of client data
BOOL (WINAPI * GetServerVariable) (HCONN hConn,
LPSTR lpszVariableName,
LPVOID lpvBuffer,
LPDWORD lpdwSize );
BOOL (WINAPI * WriteClient) (HCONN ConnID,
LPVOID Buffer,
LPDWORD lpdwBytes,
DWORD dwReserved );
BOOL (WINAPI * ReadClient) (HCONN ConnID,
LPVOID lpvBuffer,
LPDWORD lpdwSize );
BOOL (WINAPI * ServerSupportFunction)( HCONN hConn,
DWORD dwHSERequest,
LPVOID lpvBuffer,
LPDWORD lpdwSize,
LPDWORD lpdwDataType );
}EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
无论是调用进程还是ISAPI扩展,它们之间的任何信息都是通过EBC来要传递给对方的。我们已简单的看了一下ECB结构。现在,我们来看看IIS是如何通过与ISAPI扩展进行通讯,来为网站访问者服务的。
当一个ISAPI扩展被访问(比如:http://www.mydomain.com/script/example.dll? ID=p05874 & Tx=870250AZT6)时,IIS会检查example.dll是否已经被加载进内存中。如果没有加载,IIS会加载。一旦DLL被加载进内存,一个工作线程便开始运行我们的ISAPI扩展程序(example.dll)。先是DLL的入口函数DllMain被调用。调用完成后,IIS开始调用GetExtensionVersion函数,这个函数主要实现了下面两个功能:
- 报告ISAPI可以实现的扩展服务
- 取得一个简短的描述扩展的字符串。
然后IIS开始调用HttpExtensionProc函数。这个函数会传递一个ECB指针给ISAPI扩展以开始真正的调用。通过这个函数ISAPI可以向客户端(如浏览器)回写数据。过会儿我们会检查。
第三个也是最后一个ISAPI扩展的入口函数是TerminateExtension函数。它在当ISAPI扩展从内存中卸载的时候调用。所有的清除代码可以在这个函数中实现。
简言之,一个ISAPI扩展是一个导出三个函数的非常规范的DLL。
- GetExtensionVersion
- HttpExtensionProc
- TerminateExtension (可选)
手头有了这些资料,我们开始DllMain的代码编写。它是所有的DLL的入口点函数。
DLLMain——入口点函数
就像微软说的,DllMain是每个DLL的入口函数,它是可选的。如DLL中定义了DllMain,在进程或线程初始化/中止时,或者当使用LoadLibrary加载和FreeLibrary卸载时调用这个入口点函数。这个如何函数原型如下:
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwCallReason, LPVOID lpReserved);
如果你的ISAPI扩展程序中提供这个过程,那么它将在初始化和结束时被被调用。dwCallReason参数可以是下面预定义值之一:
- DLL_PROCESS_ATTACHED
- DLL_THREAD_ATTACH
- DLL_THREAD_DETACH
- DLL_PROCESS_DETACH
对每个参数的详细描述超出了本文讨论范围,有兴趣的读者可以到MSDN上了解更多关于本函数的信息。
再有,我们可以保存hMoudle参数以便以后的使用。最后只是简单的返回一个TURE值。我们在开发扩展程序时,通常在这个函数中不做什么事。
GetExtensionVersion——真正的入口函数
这个函数是IIS调用的第一个入口函数,是ISAPI扩展用来向IIS提供信息用的。为了进一步的了解这个函数,我们来看一下这个函数的原型:
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer);
在这个函数的调用时,我们假定使用pver参数来填写扩展信息,HSE_VERSION_INFO结构如下:
typedef struct _HSE_VERSION_INFO
{
DWORD dwExtensionVersion;
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN];
}HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
dwExtensionVersion是扩展的版本号,lpszExtensionDescription是扩展的描述。如果返回TRUE便表示我们告诉IIS我们的扩展程序准备好了,可以使用了;而返回FALSE则表示IIS不可以使用本扩展。
HttpExtensionProc——主要入口函数
一个ISAPI扩展的令人着迷部分是HttpExtensionProc。目前你所能想到的是这个函数可以用来向客户端回写数据。为了看明白这是怎么发生的,我们来一下这个函数原型:
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB);
这里pECB是一个扩展控制块(ECB)的指针。这个结构用来解决IIS和ISAPI扩展之间的通讯问题。在这个函数中,你可以决定你的网页中包含什么内容,如何返回给用户,怎样做?还想起来这个结构的成员了?ECB成员中包含了一个方法,原型如下:
BOOL WriteClient(HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwSync);
使用这个函数,你可以将数据放入缓冲区中,呈现给用ConnId标识的客户端。比如说,要想发送 “A BIG RED ROSE”这几个字给客户,你可以简单地进行下面的调用:
char szBigRedRos[] =
"<font color='#FF0000' size='3'><b>A BIG RED ROSE</b></font>";
DWORD dwSize = strlen(szBigRedRos);
pECB->WriteClient(pECB->ConnID, szBigRedRose, dwSize, 0);
哈哈,到此为止。我想你已经拥有最基础的知识来开发你自己的第一个ISAPI扩展程序了。让我们开始吧。。。
项目需求
1. 一点点耐性
2. 一个MSVC++6编译器
3. 一个装有IIS地MS WINDOWS2000的操作系统
4. 一个网页浏览器
目标
我们决定不用MFC而用WIN32 API来开发开发一个ISAPI扩展程序。这个程序的功能是检查Master Card (万事达)卡号是否有效。我们给这个ISAPI扩展DLL命名为Validate.dll。在这个扩展程序中将简单地往客户端写一串字符以告诉客户:你所提供的卡号是否有效。当然,我们一定会检查,如果用户使用下面的URL地址:
http://mydomain/script/validate.dll?some%20string
或
http://mydomain/script/validate.dll?
或者相似的无效URL地址的情况(当然,所谓“无效”是从我们程序员的角度来看的,使用者可不这么想)。
当上面这种无效URL地址的情况发生时,我们将简单地回复下面一段文字到客户端:What you have entered is an invalid Master Card #.
上面就是我们的ISAPI扩展所能做的一切。
1、打开Visual Studio 2008
2、打开解决方案资源管理器视图->选择项目->添加新建项->C++文件(.cpp)
3、根据第二步,再添加一个模块定义文件(.def)
6、打开解决方案资源管理器视图->选择项目->属性->配置->所有配置->平台->所有平台
- 常规->输出目录:$(SolutionDir)$(PlatformName)\$(ConfigurationName)
- 常规->中间目录:$(PlatformName)\$(ConfigurationName)
- 常规->配置类型:动态库(.dll)
- 常规->MFC使用:使用标准 Windows 库
- 常规->字符集:未设置
- 常规->全程序优化:使用链接时间代码生成
- C/C++->常规->调试信息格式:程序数据库(/Zi)
- (如果需要编译64位的ISAPI)C/C++->常规->检测64位可移植性问题:是(/Wp64)
代码:
2 #include < stdio.h >
3 #include < stdlib.h >
4 // #include <httpfilt.h>
5 #include < httpext.h > //ISAPI扩展的头文件
6
7 void WriteContext(EXTENSION_CONTROL_BLOCK * pECB, char * pszFormat, );
8 void StartContext(EXTENSION_CONTROL_BLOCK * pECB);
9 void EndContext(EXTENSION_CONTROL_BLOCK * pECB);
10
11 BOOL APIENTRY DLLMain(HANDLE hModule, DWORD dwCallReason, LPVOID lpReserved)
12 {
13 return TRUE;
14 }
15
16 BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO * pVer)
17 {
18 pVer -> dwExtensionVersion = HSE_VERSION;
19 strncpy(pVer -> lpszExtensionDesc, " My first ISAPI program " , HSE_MAX_EXT_DLL_NAME_LEN);
20 return TRUE;
21 }
22
23 DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK * pECB)
24 {
25 StartContext(pECB);
26 WriteContext(pECB, " <p>this is my first ISAPI program!!hello money!!</p> " );
27 EndContext(pECB);
28 return HSE_STATUS_SUCCESS;
29 }
30
31 BOOL WINAPI TerminateExtension(DWORD dwFlags)
32 {
33 return TRUE;
34 }
35
36 void WriteContext(EXTENSION_CONTROL_BLOCK * pECB, char * pszFormat, )
37 {
38 char szBuffer[ 1024 ];
39 va_list arg_ptr;
40 va_start(arg_ptr, pszFormat);
41 vsprintf(szBuffer, pszFormat, arg_ptr);
42 va_end(arg_ptr);
43
44 DWORD dwSize = strlen(szBuffer);
45 pECB -> WriteClient(pECB -> ConnID, szBuffer, & dwSize, 0 );
46 }
47
48 void StartContext(EXTENSION_CONTROL_BLOCK * pECB)
49 {
50 WriteContext(pECB, " <html>\r\n<body>\r\n " );
51 }
52
53 void EndContext(EXTENSION_CONTROL_BLOCK * pECB)
54 {
55 WriteContext(pECB, " </body>\r\n</html> " );
56 }
其中EXTENSION_CONTROL_BLOCK结构用来和IIS通信