基於Vc++開發IIS7以及IIS6的萬能篩選器
php
1、iis6篇c++
1)新建工程:準備好vs6之後,新建新工程,選擇"ISAPI Filter Wizard" 工程類型,一路下一步以後,工程目錄就出來了(暫時將咱們初始化的filter類命令爲CTestFilter)。web
2)打開vs初始化的工程,打開CTestFilter.cpp文件,咱們能夠看到有一個自動生成的函數:windows
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;
}api
不要關注其餘代碼,只須要關注紅色字體部分,這幾個系統常量是用來指定篩選器須要攔截哪些http內容的,我簡單攔截類型有好幾種,詳細信息能夠查詢msdn的文檔,我只簡單介紹下其中的SF_NOTIFY_PREPROC_HEADERS和 SF_NOTIFY_SEND_RAW_DATA,SF_NOTIFY_PREPROC_HEADERS表明在iis處理http的header請求時觸發一次攔截,而SF_NOTIFY_SEND_RAW_DATA表明在iis向客戶端回寫http的response內容前觸發攔截,在這個攔截當中,你能夠接收到response的指針,修改response的內容。服務器
3)仍是用wizard,去實現CHttpFilter基類的各個攔截方法(須要與前面的GetFilterVersion方法中指定的攔截事件對應,不然iis不會觸發這些方法的),這裏我仍是介紹下與上一步向對應的OnSendRawData(CHttpFilterContext* pfc, PHTTP_FILTER_RAW_DATA pRawData)和OnPreprocHeaders(CHttpFilterContext* pfc,PHTTP_FILTER_PREPROC_HEADERS pHeaders)兩個函數,你們能夠很容易地看出這兩個函數與前面兩個攔截類型的對應關係。OnPreprocHeaders是專門用來在iis處理http請求前,對客戶端的http header進行處理,其中入參pHeaders是個結構型數據,其中包含了一個指向header對象的指針,咱們能夠拿到這個指針進行預處理,並能夠修改headers的內容。app
OnSendRawData能夠說是這些方法中最強悍的了,它能夠拿到iis的response內容,並能夠修改response的內容,其中pRawData這個結構型的入參就包含了response內容的指針,仍是上代碼給你們講講這個函數是如何去修改一切的吧:asp.net
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提供的申請內存方法,才能正常返回http內容,用isapi的方法申請的內存,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;
}ide
這樣修改事後,全部客戶端的請求都變成了」Hello isqpi!!「了,並且連正常的http頭都被去掉了。函數
4)部署:編譯完成剛纔的工程後,咱們能夠獲得一個TestFilter.dll的文件,打開iis6的管理頁面,進入web站點的屬性對話框,到isapi的選項卡中,添加咱們剛纔生成的dll文件,並重啓iis服務器,ok,iis就只會返回」Hello isapi!!「了。
2、iis7篇
伴隨着windows server2008的出現,iis7也一塊兒推出,iis7雖然也能夠經過很曲折的方式,實現isapi接口功能,但根據實踐的狀況來開,iis7在使用isapi擴展來實現攔截功能時,常常會出現攔截不了的煩人問題,這時咱們在查閱了大量資料後發現,iis7對isapi的支持原本就很差,由於微軟又在iis7這個平臺上推出了一個更強大的攔截實現,那就是iis擴展模塊,iis7的全部功能都是經過加載不一樣的模塊來實現的,並且爲咱們實現攔截器特別提供了一個httpmodule的模塊,這個模塊是凌駕於全部http請求的,無論你使用的asp仍是asp.net甚至是借用iis作服務器的php服務器,全部的請求都會先通過這個模塊來處理,難怪微軟不會去關注iis7對isapi的兼容問題,它要推新東西了嘛~
下面咱們就介紹下如何經過iis的模塊來實現攔截並修改http請求的功能:
1)環境準備:visual studio2008/2005+iis7+windows server2008。
2)新建c++工程:在vs中新建一個空的動態連接庫的工程。
3)新建一個CHttpModule的子類CMyHttpModule(須要基於對頭文件httpserv.h的引用),並實現一個虛方法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 );
// 建立一個 HRESULT 來接收方法返回值.
HRESULT hr;
// 獲取一個指向response對象的指針.
IHttpResponse * pHttpResponse = pHttpContext->GetResponse();
if (pHttpResponse != NULL)
{
// 直接清理掉原來的response內容.
pHttpResponse->Clear();
// 設置response的格式.
pHttpResponse->SetHeader(
HttpHeaderContentType,"text/plain",
(USHORT)strlen("text/plain"),TRUE);
PCSTR pszBuffer = "Hello HttpModule!!!";
// 建立一個數據塊.
HTTP_DATA_CHUNK dataChunk;
// 把數據塊類型設置成http類型的(後續的內存清理工做就會由iis容器本身完成).
dataChunk.DataChunkType = HttpDataChunkFromMemory;
DWORD cbSent;
// 給數據塊賦值.
dataChunk.FromMemory.pBuffer =
(PVOID) pszBuffer;
dataChunk.FromMemory.BufferLength =
(USHORT) strlen(pszBuffer);
// 將數據塊插入到response內容中.
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)新建一個實現了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()
{
// 清理本身的內存.
delete this;
}
};
// 用來註冊模塊工廠的方法.
HRESULT
__stdcall
RegisterModule(
DWORD dwServerVersion,
IHttpModuleRegistrationInfo * pModuleInfo,
IHttpServer * pGlobalInfo
)
{
UNREFERENCED_PARAMETER( dwServerVersion );
UNREFERENCED_PARAMETER( pGlobalInfo );
// 設置須要攔截的方式,這裏設置的是給客戶端返回response內容前,和咱們以前iis6的示例相似,能夠設置多個,但必須和你的httpmodule中對應.
return pModuleInfo->SetRequestNotifications(
new CMyHttpModuleFactory,
RQ_BEGIN_REQUEST,
0
);
}
5)編譯並生成一個MyHttpModule.dll的動態連接庫,將保存MyHttpModule.dll的全路徑添加到%windir%\system32\inetsrv\config\applicationHost.config文件的globalModules節點下,重啓iis,ok,這下訪問你iis中的任何一個文件,返回的內容都會是:「Hello HttpModule!!!」。