您好,登錄后才能下訂單哦!
基于Vc++開發(fā)IIS7以及IIS6的萬能篩選器
一、iis6篇
1)新建工程:準備好vs6以后,新建新工程,選擇"ISAPI Filter Wizard" 工程類型,一路下一步之后,工程目錄就出來了(暫時將我們初始化的filter類命令為CTestFilter)。
2)打開vs初始化的工程,打開CTestFilter.cpp文件,我們可以看到有一個自動生成的函數(shù):
BOOL CTestFilter::GetFilterVersion(PHTTP_FILTER_VERSION pVer)
{
// Call default implementation for initialization
CTestFilter::GetFilterVersion(pVer);
// Clear the flags set by base class
pVer->dwFlags &= ~SF_NOTIFY_ORDER_MASK;
// Set the flags we are interested in
pVer->dwFlags |= SF_NOTIFY_ORDER_HIGH| SF_NOTIFY_PREPROC_HEADERS | SF_NOTIFY_SEND_RAW_DATA;
// Load description string
TCHAR sz[SF_MAX_FILTER_DESC_LEN+1];
ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),
IDS_FILTER, sz, SF_MAX_FILTER_DESC_LEN));
_tcscpy(pVer->lpszFilterDesc, sz);
return TRUE;
}
不要關注其他代碼,只需要關注紅色字體部分,這幾個系統(tǒng)常量是用來指定篩選器需要攔截哪些http內(nèi)容的,我簡單攔截類型有好幾種,詳細信息可以查詢msdn的文檔,我只簡單介紹下其中的SF_NOTIFY_PREPROC_HEADERS和 SF_NOTIFY_SEND_RAW_DATA,SF_NOTIFY_PREPROC_HEADERS代表在iis處理http的header請求時觸發(fā)一次攔截,而SF_NOTIFY_SEND_RAW_DATA代表在iis向客戶端回寫http的response內(nèi)容前觸發(fā)攔截,在這個攔截當中,你可以接收到response的指針,修改response的內(nèi)容。
3)還是用wizard,去實現(xiàn)CHttpFilter基類的各個攔截方法(需要與前面的GetFilterVersion方法中指定的攔截事件對應,否則iis不會觸發(fā)這些方法的),這里我還是介紹下與上一步向?qū)腛nSendRawData(CHttpFilterContext* pfc, PHTTP_FILTER_RAW_DATA pRawData)和OnPreprocHeaders(CHttpFilterContext* pfc,PHTTP_FILTER_PREPROC_HEADERS pHeaders)兩個函數(shù),大家可以很容易地看出這兩個函數(shù)與前面兩個攔截類型的對應關系。OnPreprocHeaders是專門用來在iis處理http請求前,對客戶端的http header進行處理,其中入?yún)Headers是個結(jié)構型數(shù)據(jù),其中包含了一個指向header對象的指針,我們可以拿到這個指針進行預處理,并可以修改headers的內(nèi)容。
OnSendRawData可以說是這些方法中最強悍的了,它可以拿到iis的response內(nèi)容,并可以修改response的內(nèi)容,其中pRawData這個結(jié)構型的入?yún)⒕桶藃esponse內(nèi)容的指針,還是上代碼給大家講講這個函數(shù)是如何去修改一切的吧:
DWORD CTestFilter::OnSendRawData(CHttpFilterContext* pfc, PHTTP_FILTER_RAW_DATA pRawData)
{
// TODO: Add your specialized code here and/or call the base class
CString resp="Hello isapi?。?;
void* mem=(void*)pfc->AllocMem(resp.GetLength());//必須使用isapi提供的申請內(nèi)存方法,才能正常返回http內(nèi)容,用isapi的方法申請的內(nèi)存,iis會自動幫你回收
memset(mem,0,resp.GetLength());
memcpy(mem,resp.GetBuffer(0),resp.GetLength());
pRawData->cbInBuffer=resp.GetLength();
pRawData->cbInData=resp.GetLength();
pRawData->pvInData=mem;
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
這樣修改過后,所有客戶端的請求都變成了”Hello isqpi!!“了,而且連正常的http頭都被去掉了。
4)部署:編譯完成剛才的工程后,我們可以得到一個TestFilter.dll的文件,打開iis6的管理頁面,進入web站點的屬性對話框,到isapi的選項卡中,添加我們剛才生成的dll文件,并重啟iis服務器,ok,iis就只會返回”Hello isapi?。 傲?。
二、iis7篇
伴隨著windows server2008的出現(xiàn),iis7也一起推出,iis7雖然也可以通過很曲折的方式,實現(xiàn)isapi接口功能,但根據(jù)實踐的情況來開,iis7在使用isapi擴展來實現(xiàn)攔截功能時,經(jīng)常會出現(xiàn)攔截不了的煩人問題,這時我們在查閱了大量資料后發(fā)現(xiàn),iis7對isapi的支持本來就不好,因為微軟又在iis7這個平臺上推出了一個更強大的攔截實現(xiàn),那就是iis擴展模塊,iis7的所有功能都是通過加載不同的模塊來實現(xiàn)的,而且為我們實現(xiàn)攔截器特別提供了一個httpmodule的模塊,這個模塊是凌駕于所有http請求的,不管你使用的asp還是asp.net甚至是借用iis做服務器的php服務器,所有的請求都會先經(jīng)過這個模塊來處理,難怪微軟不會去關注iis7對isapi的兼容問題,它要推新東西了嘛~
下面我們就介紹下如何通過iis的模塊來實現(xiàn)攔截并修改http請求的功能:
1)環(huán)境準備:visual studio2008/2005+iis7+windows server2008。
2)新建c++工程:在vs中新建一個空的動態(tài)鏈接庫的工程。
3)新建一個CHttpModule的子類CMyHttpModule(需要基于對頭文件httpserv.h的引用),并實現(xiàn)一個虛方法OnBeginRequest:
#define _WINSOCKAPI_
#include <windows.h>
#include <sal.h>
#include <httpserv.h>
class CMyHttpModule: public CHttpModule
{
public:
REQUEST_NOTIFICATION_STATUS
OnBeginRequest(
IN IHttpContext * pHttpContext,
IN IHttpEventProvider * pProvider
)
{
UNREFERENCED_PARAMETER( pProvider );
// 創(chuàng)建一個 HRESULT 來接收方法返回值.
HRESULT hr;
// 獲取一個指向response對象的指針.
IHttpResponse * pHttpResponse = pHttpContext->GetResponse();
if (pHttpResponse != NULL)
{
// 直接清理掉原來的response內(nèi)容.
pHttpResponse->Clear();
// 設置response的格式.
pHttpResponse->SetHeader(
HttpHeaderContentType,"text/plain",
(USHORT)strlen("text/plain"),TRUE);
PCSTR pszBuffer = "Hello HttpModule!??!";
// 創(chuàng)建一個數(shù)據(jù)塊.
HTTP_DATA_CHUNK dataChunk;
// 把數(shù)據(jù)塊類型設置成http類型的(后續(xù)的內(nèi)存清理工作就會由iis容器自己完成).
dataChunk.DataChunkType = HttpDataChunkFromMemory;
DWORD cbSent;
// 給數(shù)據(jù)塊賦值.
dataChunk.FromMemory.pBuffer =
(PVOID) pszBuffer;
dataChunk.FromMemory.BufferLength =
(USHORT) strlen(pszBuffer);
// 將數(shù)據(jù)塊插入到response內(nèi)容中.
hr = pHttpResponse->WriteEntityChunks(
&dataChunk,1,FALSE,TRUE,&cbSent);
if (FAILED(hr))
{
pHttpResponse->SetStatus(500,"Server Error",0,hr);
}
return RQ_NOTIFICATION_FINISH_REQUEST;
}
return RQ_NOTIFICATION_CONTINUE;
}
}
4)新建一個實現(xiàn)了IHttpModuleFactory接口的工廠類,用來注冊攔截模塊和攔截的方式
class CMyHttpModuleFactory : public IHttpModuleFactory
{
public:
HRESULT
GetHttpModule(
OUT CHttpModule ** ppModule,
IN IModuleAllocator * pAllocator
)
{
UNREFERENCED_PARAMETER( pAllocator );
// 實例化一個模塊的指針.
CMyHttpModule * pModule = new CMyHttpModule;
if (!pModule)
{
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
else
{
*ppModule = pModule;
pModule = NULL;
return S_OK;
}
}
void
Terminate()
{
// 清理自己的內(nèi)存.
delete this;
}
};
// 用來注冊模塊工廠的方法.
HRESULT
__stdcall
RegisterModule(
DWORD dwServerVersion,
IHttpModuleRegistrationInfo * pModuleInfo,
IHttpServer * pGlobalInfo
)
{
UNREFERENCED_PARAMETER( dwServerVersion );
UNREFERENCED_PARAMETER( pGlobalInfo );
// 設置需要攔截的方式,這里設置的是給客戶端返回response內(nèi)容前,和我們之前iis6的示例類似,可以設置多個,但必須和你的httpmodule中對應.
return pModuleInfo->SetRequestNotifications(
new CMyHttpModuleFactory,
RQ_BEGIN_REQUEST,
0
);
}
5)編譯并生成一個MyHttpModule.dll的動態(tài)鏈接庫,將保存MyHttpModule.dll的全路徑添加到%windir%\system32\inetsrv\config\applicationHost.config文件的globalModules節(jié)點下,重啟iis,ok,這下訪問你iis中的任何一個文件,返回的內(nèi)容都會是:“Hello HttpModule?。。 ?。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。