作為一個WWW服務器(WEB)軟件,微軟公司的Internet Infomation Server(IIS)簡單易學,管理方便,得到了廣泛的使用。您還可以通過ISAPI過濾器,進行自己定制的處理,來增強IIS的功能。ISAPI過濾器可以定制以下的處理:接收HTTP協議頭預處理、發送HTTP協議頭預處理、發送生數據預處理、獲得生數據預處理、HTTP會話結束信息處理、自定義的安全認證機制、URL映射信息處理、日志記錄處理等。靈活利用這些定制處理,您可以完成許多看似難以實現的功能,得到意想不到的效果。但是ISAPI過濾器使用不當也會影響服務器的性能。
ISAPI可以完成很多功能,asp.net的實現底層也是通過isapi來解析asp.net的代碼的。
通過開發isapi,然后在iis中配置后可以解析不同的文件。
比如java,perl的代碼也可以通過加載isapi的方式在iis中進行訪問
ISAPI兩種開發方式:擴展(如:aspnet_isapi.dll)和過濾(如:aspnet_filter.dll)
過濾可以實現許多功能,比如將簡體轉成繁體,大小寫轉換等, 參見UpCase
引用:
在 Microsoft Visual Studio 2005, 中創建新項目時在 新建項目 對話框中沒有找到 VisualC++ 下 MFC ISAPI Extension DLL 模板。
因為 Microsoft 已刪除 MFC ISAPI Extension DLL 模板為 Visual Studio 2005 發生此行為。
用于新 ISAPI 篩選或擴展開發, 我們建議您使用 MicrosoftInternet 信息服務 (IIS) 軟件開發工具包 (SDK) 中 ISAPI 入口 - Point 函數代替 MFC ISAPI 類。 Microsoft Windows Server 2003 Service Pack 1 (SP 1) 平臺 SDK 包括許多 ISAPI 示例。 請以獲取 PlatformSDK, 訪問以下 MicrosoftWeb 站點:
http://www.microsoft.com/downloads/details.aspx?FamilyID=eba0128f-a770-45f1-86f3-7ab010b398a3&DisplayLang=en (http://www.microsoft.com/downloads/details.aspx?FamilyID=eba0128f-a770-45f1-86f3-7ab010b398a3&DisplayLang=en)
注意 時間少, 必須安裝 Microsoft Windows 核心 SDK 和 IIS SDK。
默認情況下, 從 SDK ISAPI 示例位于以下文件夾:
程序 Files\Microsoft 平臺 SDK\Samples\Web\iis
有關如何創建 ISAPI 篩選器和擴展, 請訪問以下 Microsoft Developer Network (MSDN) 網站:
最主要的代碼如下:
1 DWORD WINAPI __stdcall HttpFilterProc(HTTP_FILTER_CONTEXT * pfc, DWORD NotificationType, VOID * pvData)
2 {
3 CHAR *pchIn, *pPhysPath;
4 DWORD cbBuffer, cbtemp;
5 PHTTP_FILTER_URL_MAP pURLMap;
6 PHTTP_FILTER_RAW_DATA pRawData;
7
8 switch (NotificationType) {
9
10 case SF_NOTIFY_URL_MAP :
11
12 /* Check the URL for a subdirectory in the form of /UC/ or /uc/ */
13
14 pURLMap = (PHTTP_FILTER_URL_MAP)pvData;
15
16 pPhysPath = pURLMap->pszPhysicalPath;
17
18 pfc->pFilterContext = 0;
19
20 for ( *pPhysPath; pPhysPath++)
21 {
22 /* Ensure that there are at least 4 characters (checking for "\UC\") left in the URL before checking */
23
24 if (strlen(pPhysPath) > 3)
25 {
26 if (*pPhysPath == '\\' && (*(pPhysPath + 1) == 'u' || *(pPhysPath + 1) == 'U') && (*(pPhysPath + 2) == 'c' || *(pPhysPath + 2) == 'C') && *(pPhysPath + 3) == '\\')
27 {
28 /* Now that we've found it, remove it by collapsing everything down */
29
30 for ( *(pPhysPath + 3) pPhysPath++)
31 *pPhysPath = *(pPhysPath + 3);
32
33 /* NULL terminate the string */
34
35 *pPhysPath = '\0';
36
37 /* And set the flag to let the SF_NOTIFY_SEND_RAW_DATA handler know to uppercase the content */
38
39 pfc->pFilterContext = (VOID *)1;
40
41 /* Break us out of the loop - note that this will only find the first instance of /UC/ in the URL */
42
43 break;
44 }
45 }
46 }
47
48 break;
49
50 case SF_NOTIFY_SEND_RAW_DATA :
51
52 if (pfc->pFilterContext)
53 {
54 pRawData = (PHTTP_FILTER_RAW_DATA)pvData;
55
56 pchIn = (BYTE *)pRawData->pvInData;
57
58 cbBuffer = 0;
59
60 if (pfc->pFilterContext == (VOID *)1)
61 {
62 /*
63 As this is the first block, scan through it until 2 CRLFs are seen, to pass
64 all of the headers.
65 */
66
67 for ( cbBuffer < pRawData->cbInData - 2; cbBuffer++)
68 {
69 if (pchIn[cbBuffer] == '\n' && pchIn[cbBuffer + 2] == '\n')
70 {
71 cbBuffer += 3;
72
73 break;
74 }
75
76 cbBuffer++;
77 }
78
79 for (cbtemp = 0; cbtemp < (cbBuffer - 3); cbtemp++)
80 {
81 if (pchIn[cbtemp] == '/' && pchIn[cbtemp + 1] == 'h' && pchIn[cbtemp + 2] == 't' && pchIn[cbtemp + 3] == 'm')
82 {
83 pfc->pFilterContext = (VOID *)2;
84
85 break;
86 }
87 }
88
89 if (cbtemp == cbBuffer)
90 pfc->pFilterContext = 0; /* not an html file */
91 }
92
93 /* Now uppercase everything */
94
95 if (pfc->pFilterContext)
96 for ( cbBuffer < pRawData->cbInData; cbBuffer++)
97 pchIn[cbBuffer] = (pchIn[cbBuffer] >= 'a' && pchIn[cbBuffer] <= 'z') ? (pchIn[cbBuffer] - 'a' + 'A') : pchIn[cbBuffer];
98 }
99
100 break;
101
102 default :
103
104 break;
105 }
106
107 return SF_STATUS_REQ_NEXT_NOTIFICATION;
108}
ISAPI過濾器的開發非常簡單,只需要完成三個接口DLL函數即可。它們是GetFilterVersion()、HttpFilterProc()、TerminateFilter(),大家可以查看MSDN了解詳細的用法。ISAPI過濾器是DLL文件,一般用C/C++語言開發。為使ISAPI過濾器能夠運行,您需要在注冊表的HKEY_LOCAL_MACHINESystemCurrentControlSet
ServicesW3SVCParameters下建立一個字符串項,其名稱為"Filter Dlls",值為ISAPI過濾器文件的全路徑名稱。若這個字符串項已經存在,只需把它的全路徑名稱加入其中,不同的ISAPI過濾器文件之間用""分隔,您可以根據執行的優先順序加在適當的位置。設置好后重新啟動IIS服務,您的ISAPI過濾器就發揮作用了。
下面作者舉一個具體的應用例子。
對訪問內容進行統計分析:
通常我們在需要計數的頁面內放一個計數器,或者使用ASP文件來實現計數功能。這種方法不能適用于如README.TXT等其他非HTML格式的文件。如果使用IIS的日志功能又太占用空間而不方便。作者通過定制URL映射信息處理來跟蹤感興趣的幾個文件的計數統計,將結果記錄在一個文件中。
下面是它的源程序。
fcount.def:
LIBRARY fcount
EXPORTS GetFilterVersion
HttpFilterProc
TerminateFilter
fcount.c:
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <httpfilt.h>
#define logfile "C:\InetPub\fcount.log"
#define pages 5
char* urls[] = {
"/default.htm",
"/banner.gif",
"/product/readme.txt",
"/product/product1.htm",
"/product/product2.htm"
};
int counts[pages];
BOOL WINAPI GetFilterVersion
(HTTP_FILTER_VERSION *pVer)
{
int i;
pVer->dwFilterVersion = HTTP_FILTER_REVISION;
strcpy(pVer->lpszFilterDesc, "fcount");
pVer->dwFlags = SF_NOTIFY_URL_MAP;
for (i=0; i<pages; i++) {
counts[i] = GetPrivateProfileInt("VisitCounter",
urls[i],
0, logfile);
}
return TRUE;
}
DWORD WINAPI HttpFilterProc
(HTTP_FILTER_CONTEXT *pfc,
DWORD noteType, VOID *pvNote)
{
int i;
char lurl[512];
char buf[16];
strcpy(lurl, ((PHTTP_FILTER_URL_MAP)pvNote)
->pszURL);
_strlwr(lurl);
for (i=0; i<pages; i++) {
if (strcmp(lurl, urls[i])==0) {
counts[i] ++;
if (counts[i]%10==0) {
_itoa(counts[i], buf, 10);
WritePrivateProfileString("VisitCounter",
urls[i], buf, logfile);
}
break;
}
}
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
BOOL WINAPI TerminateFilter(DWORD dwFlags)
{
int i;
char buf[16];
for (i=0; i<pages; i++) {
_itoa(counts[i], buf, 10);
WritePrivateProfileString("VisitCounter",
urls[i], buf, logfile);
}
return TRUE;
}
以上二個例子作者使用VC 6.0編譯,在WINNT2000 + SP3 和 IIS 5.0上調試通過。
iis 非法信息過濾器
描述:
該程序主要作用是過濾來自客戶端的get,post提交,如果包含我們既定的關鍵字就拒絕本次訪問,并返回給客戶端錯誤信息.以此達到提交信息過濾功能。
采用開發工具:
vc 6.0
詳細說明:
iis本身是不具有信息過濾的功能的,我們只有自己寫 ISAPI 程序來實現。
ISAPI說明:
ISAPI是iis 提供的編程接口,可以通過寫ISAPI dll來增強iis 的功能,達到用戶需求。寫ISAPI一般都是用vc 和 Delphi 開發,他們都提供了
編寫ISAPI程序的庫函數和結構體,我們調用實現就行了。另外一點(ISAPI程序實際上是加載到iis 進程里面和iis一起運行的,所以調試起來比較費勁)
具體關于ISAPI的詳細說明,網上鋪天蓋地,這里我就不說了。
程序實現:
由于程序要支持iis 6.0,而MSDN上說IIS5.1和IIS6.0好像不支持OnReadRawData方法了(用來截獲客戶端的post請求信息的方法),所以沒辦法獲取客戶端傳過來的原始數據。
這個問題網上也很多問的。iis6.0中我們可以采用通配符應用程序映射實現截獲客戶端得post信息。
實現:
1. vc 6.0 新建ISAPI 擴展工程
2. 選擇服務器擴展對象。
3.完成,vc會生成基本的ISAPI程序代碼。
而我們主要是對DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pecb)函數進行編寫
//用戶和iis服務器之間進行通信,通信通過EXTENSION_CONTROL_BLOCK
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pecb)
{
char *p = NULL;
DWORD dwStatus=HSE_STATUS_ERROR;
BOOL rc;
HSE_EXEC_URL_INFO ExecUrlInfo={0};
HSE_EXEC_URL_ENTITY_INFO *pEntity=NULL;
char *lpszTemp=NULL, *lpszContent=NULL;
DWORD cbQuery;
char *lpszQuery = NULL;
BOOL Success=FALSE;
BOOL MethodGet=TRUE;//get
if (!stricmp(pecb->lpszMethod, "get"))
{
lpszQuery = pecb->lpszQueryString;
}
else//post
{
MethodGet = FALSE;
if(pecb->cbTotalBytes > 0)
{
if(!(lpszTemp = (CHAR *)LocalAlloc(LPTR, pecb->cbTotalBytes)))
{
return HSE_STATUS_ERROR;
}
strcpy(lpszTemp,(const char *)pecb->lpbData);
if((cbQuery = pecb->cbTotalBytes - pecb->cbAvailable) > 0)
pecb->ReadClient(pecb->ConnID, (LPVOID) (lpszTemp pecb->cbAvailable), &cbQuery);
lpszQuery = lpszTemp;
}
}
if (strlen(lpszQuery) !=0)
{
char * mem_name=NULL;
CString memname=""
CString fileData;
CString translateData;
for (int i=0;i < strarray.GetSize();i )
{
GB2312ToUTF_8(fileData,strarray[i].GetBuffer(strarray[i].GetLength()),strarray[i].GetLength());
fileData=URLEncode(fileData);
//如何進入的為gb2312字碼,就將文件數據轉化為gb2312字碼
if (strstr(lpszQuery,fileData) || strstr(lpszQuery,strarray[i]))
{
goto Mark;
}
}
ExecUrlInfo.dwExecUrlFlags = HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR;
rc=pecb->ServerSupportFunction(pecb->ConnID, HSE_REQ_IO_COMPLETION, HseIoCompletionProc, NULL,(LPDWORD)pEntity);
if(!rc) goto Failed;
rc=pecb->ServerSupportFunction(pecb->ConnID, HSE_REQ_EXEC_URL, &ExecUrlInfo, NULL, NULL);
if(!rc) goto Failed;
return HSE_STATUS_PENDING;
}
//正常網頁求情數據傳輸
else
{
ExecUrlInfo.dwExecUrlFlags = HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR;
rc=pecb->ServerSupportFunction(pecb->ConnID, HSE_REQ_IO_COMPLETION,HseIoCompletionProc, NULL, (LPDWORD)pEntity);
if(!rc) goto Failed;
rc=pecb->ServerSupportFunction(pecb->ConnID, HSE_REQ_EXEC_URL, &ExecUrlInfo, NULL, NULL);
if(!rc) goto Failed;
return HSE_STATUS_PENDING;
}
Failed:
if(dwStatus!=HSE_STATUS_PENDING)
{
if(NULL!=pEntity)
{
LocalFree(pEntity);
pEntity=NULL;
}
}
Mark:
char * message= "您提交的信息中含有非法字符,請求被終止"
DWORD dwSize = strlen(message);
pecb->WriteClient(pecb->ConnID,message, &dwSize, 0);
return dwStatus;
}
因為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)
代碼:
1 #include <windows.h>
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通信
【知識點開發和總結】
ISAPI是Internet Server Application Programming Interface 的簡稱,ISAPI過濾器是IIS進程內的DLL(進程名為inetinfo),它在web服務啟動時被加載,服務停止時退出。
IASPI過濾器可以用來定制認證過程、壓縮、加密、流量分析、請求分析、請求過濾等等,從某種意義上來說,ISAPI過濾器可以被看作是對IIS進行定制和優化的工具。
ISAPI過濾器的基本工作原理是基于事件觸發的單線程過濾器,一個標準的ISAPI過濾器由以下幾個部分組成:
1、過濾器初始化
2、消息接收過濾
3、事件處理
4、退出處理
過濾器初始化一般是設置版本信息、優先級、消息過濾器,這些工作在GetFilterVersion中完成,例如:
BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
{
pVer->dwFilterVersion = HTTP_FILTER_REVISION;
lstrcpy(pVer->lpszFilterDesc, "Filter Shotgun");
pVer->dwFlags = SF_NOTIFY_ORDER_DEFAULT | SF_NOTIFY_URL_MAP;
return TRUE;
}
上面這段程序設置版本信息為"Filter Shotgun",程序的優先級為默認(SF_NOTIFY_ORDER_DEFAULT),過濾器程序在映射文件物理地址時工作( SF_NOTIFY_URL_MAP)
其中,消息過濾允許關注以下的事件:
SF_NOTIFY_READ_RAW_DATA 讀取數據
SF_NOTIFY_SEND_RAW_DATA 發送數據
SF_NOTIFY_END_OF_REQUEST 結束請求
SF_NOTIFY_PREPROC_HEADERS 任何一個HTTP頭都會觸發這個事件
SF_NOTIFY_URL_MAP 這個事件在IIS將web路徑映射為物理路徑時發生
SF_NOTIFY_AUTHENTICATION 開始認證
SF_NOTIFY_AUTH_COMPLETE 認證結束
SF_NOTIFY_SEND_RESPONSE 發送回應
SF_NOTIFY_LOG 產生日志
SF_NOTIFY_END_OF_NET_SESSION 結束會話
事件之間用 | 連接,當系統發生了ISAPI過濾器程序所關注的事件時,IIS會傳送一個NotificationType的變量給HttpFilterProc函數,由該函數判斷事件的類型并調用響應的處理函數。
DWORD WINAPI __stdcall HttpFilterProc( HTTP_FILTER_CONTEXT * pfc,DWORD NotificationType, VOID * pvData )
{
switch ( NotificationType )
{
case SF_NOTIFY_PREPROC_HEADERS:
return OnPreprocHeaders(pfc, (PHTTP_FILTER_PREPROC_HEADERS)pvData);
case SF_NOTIFY_URL_MAP:
return OnUrlMap(pfc, (PHTTP_FILTER_URL_MAP)pvData );
}
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
事件處理函數可以通過服務器支持的函數調用對url, header, path等參數進行處理,例如,我們需要截獲所有的值為Shotgun.com的HOST頭并替換為Shotgun.net,就可以通過GetHeader和SetHeader函數來實現:
DWORD inline OnPreprocHeaders(PHTTP_FILTER_CONTEXT pfc, PHTTP_FILTER_PREPROC_HEADERS pvData)
{ char Host[1024];
DWORD cb;
cb=sizeof(Host);
pvData->GetHeader(pfc,"Host:",Host,&cb);
if(!_strnicmp(Host,"Shotgun.com",5))
pvData->SetHeader(pfc,"Host","Shotgun.net");
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
事件處理函數可以返回以下的值:
SF_STATUS_REQ_FINISHED 過濾器接管了HTTP請求,服務器可以斷開會話。
SF_STATUS_REQ_FINISHED_KEEP_CONN 過濾器接管了HTTP請求。但是服務器需要保持TCP會話。
SF_STATUS_REQ_NEXT_NOTIFICATION 正常的返回,IIS將調用消息隊列中的下一個過濾器。
SF_STATUS_REQ_HANDLED_NOTIFICATION 過濾器接管了這個消息,這種事件不需要再通知其他的句柄。
SF_STATUS_REQ_ERROR 錯誤返回,服務器應該調用GetLastError,并且將錯誤信息返回給客戶端。
SF_STATUS_REQ_READ_NEXT 過濾器是不透明的流過濾器(加密/壓縮HTTP請求)會話參數已經通過。這個返回僅當使用RAW-READ事件處理時有效,這個消息指出請求并沒有全部完成,服務器需要繼續進行讀操作并再次通知過濾器。
服務器支持的函數通過一個HTTP_FILTER_CONTEXT或者HTTP_FILTER_PREPROC_HEADERS 結構調用:
1、GetHeader 取得HTTP頭
2、SetHeader 設置HTTP頭
3、AddHeader 增加HTTP頭
4、AddResponseHeaders 增加返回給用戶的HTTP頭
5、AllocMem 分配內存
6、GetServerVariable 取得服務器變量
7、WriteClient 直接往客戶端寫數據
8、ServerSupportFunction 支持以下多種靈活的功能:
SF_REQ_ADD_HEADERS_ON_DENIAL 這個功能允許過濾器在服務器產生拒絕訪問的
事件時增加特定的HTTP頭,此函數一般用于認證過濾器
SF_REQ_DISABLE_NOTIFICATIONS 禁止某個事件消息
SF_REQ_GET_CONNID 這個功能在IIS4.0以后的版本已不再支持
SF_REQ_GET_PROPERTY 取得IIS屬性
SF_REQ_NORMALIZE_URL 規整URL
SF_REQ_SEND_RESPONSE_HEADER 發送回應HTTP頭
SF_REQ_SET_NEXT_READ_SIZE 設置下次數據讀尺寸
SF_REQ_SET_PROXY_INFO 設置代理信息
當ISAPI過濾器卸載時,TerminateFilter將會被調用,在這個函數中,你需要釋放過濾器申請的所有資源,值得注意的是。微軟推薦關注SF_NOTIFY_END_OF_NET_SESSION事件,以便在每次網絡會話結束時就釋放資源,這樣可以減輕服務器的負荷。
該文章在 2024/7/5 18:45:21 編輯過