IIS的ISAPI接口簡介
摘要:IIS的ISAPI接口簡介
ISAPI(Internet Server Application Programming Interface)作為一種可用來替代CGI的方法,是由微軟和Process軟件公司聯(lián)合提出的Web服務(wù)器上的API標(biāo)準(zhǔn)。ISAPI與Web服務(wù)器結(jié)合緊密,功能強(qiáng)大,能夠獲得大量的信息,因此利用ISAPI可以開發(fā)出靈活高效的Web服務(wù)器增強(qiáng)程序。由于ISAPI程序與Web服務(wù)器的關(guān)系,使得ISAPI接口在安全方面有一定的研究價(jià)值。本文主要討論ISAPI在IIS和VC++ 6.0中的實(shí)現(xiàn)。
一、ISAPI接口和CGI接口的不同。
ISAPI程序和CGI程序完成類似的功能,但是實(shí)現(xiàn)方法不同。
1、ISAPI程序以DLL形式被Web服務(wù)器加載到自己的進(jìn)程空間中,因此和服務(wù)器共用同一個(gè)地址空間,且在沒有客戶請求時(shí)可以將其從內(nèi)存中卸載;而對(duì)客戶端發(fā)來的每個(gè)對(duì)CGI程序的請求則需要服務(wù)器為它單獨(dú)啟動(dòng)一個(gè)進(jìn)程,這需要耗費(fèi)大量的時(shí)間和內(nèi)存。當(dāng)并發(fā)的請求數(shù)目很大時(shí),使用CGI在效率上不如ISAPI。
2、CGI程序通過環(huán)境塊和標(biāo)準(zhǔn)輸入輸出與Web服務(wù)器進(jìn)行通信,而ISAPI程序與服務(wù)器結(jié)合得更為緊密,與服務(wù)器共享同一個(gè)進(jìn)程上下文,主要通過一個(gè)參數(shù)塊與服務(wù)器進(jìn)行交互,可以從服務(wù)器那里獲得關(guān)于當(dāng)前HTTP連接的大量信息。
ISAPI主要分為ISA和ISAPI Filter兩部分。ISA方法相對(duì)而言要傳統(tǒng)一些,利用一些特殊的鏈接,指向服務(wù)器的作業(yè),供程序開發(fā)人員設(shè)計(jì)一些擴(kuò)展功能;而ISAPI過濾器則傾向于構(gòu)造服務(wù)器直接調(diào)用的模塊,提供一種無縫鏈接部件用于監(jiān)測直接來自于服務(wù)器的HTTP請求。
二、ISA
ISA(Internet Server Application)也可稱為ISAPI DLL,其功能和CGI程序的功能直接相對(duì)應(yīng),使用方法和CGI也類似,由客戶端在URL中指定其名稱而激活。例如下面的請求將調(diào)用服務(wù)器的虛擬可執(zhí)行目錄Scripts下的function.dll(ISAPI DLL必須放在服務(wù)器的虛擬可執(zhí)行目錄下):
http://www.abc.com/Scripts/function.dll?
ISA和服務(wù)器之間的接口主要有兩個(gè):GetExtentionVersion( )和HttpExtentionProc( )。任何ISA都必須在其PE文件頭的引出表中定義這兩個(gè)引出函數(shù),以供Web服務(wù)器在適當(dāng)?shù)臅r(shí)候調(diào)用。
1、當(dāng)服務(wù)器剛加載ISA時(shí),它會(huì)調(diào)用ISA提供的GetExtentionVersion( )來獲得該ISA所需要的服務(wù)器版本,并與自己的版本相比較,以保證版本兼容。函數(shù)原型如下:
BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
typedef struct _HSE_VERSION_INFO
{
DWORD dwExtensionVersion; //版本號(hào)
CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //關(guān)于ISA的描述字符串
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
2、ISA的真正入口是HttpExtentionProc( ),它相當(dāng)于普通C程序的main( )函數(shù),在這個(gè)函數(shù)中根據(jù)不同的客戶請求作不同的處理。服務(wù)器和HttpExtentionProc( )之間是通過擴(kuò)展控制塊(Extention Control Block)來進(jìn)行通信的,即ECB中存放入口參數(shù)和出口參數(shù),包括服務(wù)器提供的幾個(gè)回調(diào)函數(shù)的入口地址。函數(shù)原型如下:
DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );
ECB的結(jié)構(gòu)定義如下(IN表示入口參數(shù),OUT表示出口參數(shù)):
typedef struct _EXTENSION_CONTROL_BLOCK
{
DWORD cbSize; //IN,本結(jié)構(gòu)的大小,只讀
DWORD dwVersion //IN,版本號(hào),高16位為主版本號(hào),低16位為次版本號(hào)
HCONN ConnID; //IN,連接句柄,由服務(wù)器分配,ISA只能讀取該值
DWORD dwHttpStatusCode; //OUT,當(dāng)前完成的事務(wù)狀態(tài)
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT,需要寫入到日志文件中的內(nèi)容
LPSTR lpszMethod; //IN,等價(jià)于CGI的環(huán)境變量REQUEST_METHOD
LPSTR lpszQueryString; //IN,等價(jià)于環(huán)境變量QUERY_STRING
LPSTR lpszPathInfo; //IN,等價(jià)于環(huán)境變量PATH_INFO
LPSTR lpszPathTranslated; //IN,等價(jià)于環(huán)境變量PATH_TRANSLATED
DWORD cbTotalBytes; //IN,等價(jià)于環(huán)境變量CONTENT_LENGTH
DWORD cbAvailable; //IN,緩沖區(qū)中的可用字節(jié)數(shù)
LPBYTE lpbData; //IN,緩沖區(qū)指針,指向客戶端發(fā)來的數(shù)據(jù)
LPSTR lpszContentType; //IN,等價(jià)于環(huán)境變量CONTENT_TYPE
//回調(diào)函數(shù),用于返回服務(wù)器的連接信息或特定的服務(wù)器詳細(xì)情況
BOOL ( WINAPI * GetServerVariable )
( HCONN hConn,
LPSTR lpszVariableName,
LPVOID lpvBuffer,
LPDWORD lpdwSize );
BOOL ( WINAPI * WriteClient ) //回調(diào)函數(shù),從客戶端的HTTP請求中讀取數(shù)據(jù)
( HCONN ConnID,
LPVOID Buffer,
LPDWORD lpdwBytes,
DWORD dwReserved );
BOOL ( WINAPI * ReadClient ) //回調(diào)函數(shù),向客戶端發(fā)送數(shù)據(jù)
( HCONN ConnID,
LPVOID lpvBuffer,
LPDWORD lpdwSize );
BOOL ( WINAPI * ServerSupportFunction ) //回調(diào)函數(shù),訪問服務(wù)器的一般和特定功能
( HCONN hConn,
DWORD dwHSERRequest,
LPVOID lpvBuffer,
LPDWORD lpdwSize,
LPDWORD lpdwDataType );
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
在上述ECB中,服務(wù)器不但提供了當(dāng)前HTTP連接的句柄和一些變量,而且提供了4個(gè)回調(diào)函數(shù)給ISA調(diào)用,從而使ISA可以獲得更詳盡的信息。
三、ISAPI Filter
ISAPI Filter位于服務(wù)器和客戶端之間,能夠?qū)Ψ?wù)器和客戶端之間的通信進(jìn)行預(yù)處理和后處理,比如對(duì)通信進(jìn)行加密/解密、提供對(duì)客戶進(jìn)行身份驗(yàn)證的新方法、提供自定義的日志記錄等,在CGI中沒有與ISAPI Filter直接相對(duì)應(yīng)的部分。
ISAPI Filter與服務(wù)器之間的接口有兩個(gè):GetFilterVersion( )和HttpFilterProc( )。任何
ISAPI Filter都必須引出這兩個(gè)函數(shù)以供服務(wù)器調(diào)用。
1、在注冊表的如下鍵值中存放著所有ISAPI Filter的文件名,IIS服務(wù)器啟動(dòng)時(shí)從該鍵值中獲得
Filter的文件名并加載它們。
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
2、然后服務(wù)器調(diào)用每個(gè)Filter提供的GetFilterVersion( )函數(shù),獲得版本號(hào)以及該Filter希望處理的事件,即ISAPI Filter通過引出GetFilterVersion( )函數(shù)來告知服務(wù)器自己希望處理什么類型的事件,因?yàn)镮SAPI Filter是通過事件來激活的,當(dāng)滿足條件的事件到達(dá)時(shí),服務(wù)器就會(huì)調(diào)用Filter引出的主函數(shù)HttpFilterProc( )對(duì)該事件進(jìn)行處理。GetFilterVersion( )的原型如下:
BOOL WINAPI GetFilterVersion(
DWORD dwServerFilterVersion; //IN,服務(wù)器使用的版本規(guī)范
DWORD dwFilterVersion; //OUT,過濾器使用的版本規(guī)范
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,對(duì)該過濾器的描述字符串
DWORD dwFlags //OUT,事件和優(yōu)先級(jí)標(biāo)志
);
事件和優(yōu)先級(jí)標(biāo)志dwFlasg的取值在MSDN中有詳細(xì)解釋,其中包括該Filter被調(diào)用的優(yōu)先級(jí),一般應(yīng)使用默認(rèn)的低優(yōu)先級(jí),否則可能會(huì)對(duì)系統(tǒng)的性能造成很大影響。
3、HttpFilterProc( )是ISAPI Filter主要的入口函數(shù),它根據(jù)當(dāng)前的事件的不同作出不同的處理。服務(wù)器通過如下的參數(shù)塊和Filter進(jìn)行交互,這個(gè)參數(shù)塊的作用和ISA中的ECB類似。
typedef struct _HTTP_FILTER_CONTEXT
{
DWORD cbSize; //IN,本參數(shù)塊的大小
DWORD Revision; //IN
PVOID ServerContext; //IN,由server使用本參數(shù)
DWORD ulReserved; //IN,由server使用本參數(shù)
BOOL fIsSecurePort; //IN,事件是否發(fā)生在安全端口上
PVOID pFilterContext; //IN/OUT,與本次請求相關(guān)的上下文
//回調(diào)函數(shù),取得關(guān)于服務(wù)器和本次連接的信息
BOOL (WINAPI * GetServerVariable) (
struct _HTTP_FILTER_CONTEXT * pfc,
LPSTR lpszVariableName,
LPVOID lpvBuffer,
LPDWORD lpdwSize
);
BOOL (WINAPI * AddResponseHeaders) ( //回調(diào)函數(shù),給HTTP響應(yīng)添加一個(gè)標(biāo)頭
struct _HTTP_FILTER_CONTEXT * pfc,
LPSTR lpszHeaders,
DWORD dwReserved
);
BOOL (WINAPI * WriteClient) ( //回調(diào)函數(shù),將原始數(shù)據(jù)發(fā)送給客戶端
struct _HTTP_FILTER_CONTEXT * pfc,
LPVOID Buffer,
LPDWORD lpdwBytes,
DWORD dwReserved
);
VOID * (WINAPI * AllocMem) ( //回調(diào)函數(shù),分配內(nèi)存。
struct _HTTP_FILTER_CONTEXT * pfc,
DWORD cbSize,
DWORD dwReserved
);
BOOL (WINAPI * ServerSupportFunction) ( //回調(diào)函數(shù),訪問服務(wù)器的一般和特定功能
struct _HTTP_FILTER_CONTEXT * pfc,
enum SF_REQ_TYPE sfReq,
PVOID pData,
DWORD ul1,
DWORD ul2
);
} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONTEXT;
四、VC++ 6.0中對(duì)ISAPI的支持
VC++ 6.0中定義了5個(gè)相關(guān)的類以簡化ISAPI的編程工作:CHttpServer、CHttpServerContext、CHttpFilter、CHttpFilterContext、CHtmlStream,這5個(gè)類都沒有父類。其中CHttpServer和CHttpServerContext主要用來編寫ISA,CHttpFilter和CHttpFilterContext則用來編寫ISAPI Filter,而CHtmlStream則用來操作內(nèi)存中的HTML文件,為其它的4個(gè)類提供服務(wù)。CHttpServer在每個(gè)ISA中只能有一個(gè)實(shí)例,一個(gè)CHttpServer可以對(duì)應(yīng)多個(gè)CHttpServerContext實(shí)例,每個(gè)
CHttpServerContext處理一個(gè)客戶請求,這樣可以處理并發(fā)的HTTP請求;CttpFilter和CHttpFilterContext之間的關(guān)系與此類似,在每個(gè)ISAPI Filter中只能有一個(gè)CHttpFilter實(shí)例,但是可以有多個(gè)CHttpFilterContext來處理并發(fā)的事件。CHttpServer和CHttpFilter是獨(dú)立的類,它們可以共存于一個(gè)DLL中,也可以分別在不同的DLL中。
一個(gè)ISA可以提供多個(gè)命令,每個(gè)命令對(duì)應(yīng)于CHttpServer(或其子類)的一個(gè)成員函數(shù),客戶端可以在URL中指定命令名及其參數(shù)。在VC++ 6.0中是通過parse map來實(shí)現(xiàn)這種對(duì)應(yīng)的。
Parse map類似MFC中的Windows消息分發(fā)機(jī)制,通過使用VC提供的DECLARE_PARSE_MAP、BEGIN_PARSE_MAP、ON_PARSE_COMMAND、ON_PARSE_COMMAND_PARAMS、DEFAULT_PARSE_COMMAND、END_PARSE_MAP等宏,可以實(shí)現(xiàn)對(duì)不同的命令的處理。每個(gè)CHttpServer中只能建立一個(gè)parse map,當(dāng)客戶端給ISA發(fā)來命令的時(shí)候,parse map可以分析HTTP請求中的命令名及其參數(shù),將該命令與相應(yīng)的成員函數(shù)關(guān)聯(lián)起來,即由該成員函數(shù)處理該命令。以MSDN中的例子程序pinball為例,該例中有下面這樣一個(gè)表單:
<form method=get action="pinball.dll?">
<input type="hidden" name="MfcISAPICommand" VALUE="GetImage">
<input type="radio" name="Favorite" value="1" checked> Attack from Mars<br>
<input type="radio" name="Favorite" value="2"> Twilight Zone<br>
<input type="radio" name="Favorite" value="3"> The Addams Family<br>
<input type="radio" name="Favorite" value="4"> Cirqus Voltaire<br>
<input type="radio" name="Favorite" value="0"> I don't see it here<br>
<br>
<input type="submit" value="Show Me!">
</form>
當(dāng)客戶端選中了上面的表單中的“Attack from Mars”這一項(xiàng)并點(diǎn)擊了submit按鈕后,服務(wù)器端
最終將得到如下的URL串:
http://www.abc.com/pinball.dll?MfcISAPICommand=GetImage&Favorite=1
在該URL串中,命令名是GetImage,參數(shù)Favorite的值是1,因此pinball.dll中的如下成員函數(shù)
將被調(diào)用以處理該請求,其中參數(shù)dwChoice對(duì)應(yīng)URL中的參數(shù)Favorite:
void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);
而parse map需要按照下面的形式定義:
//CPinballExtension從CHttpServer派生而來
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer)
//GetImage是CPinballExtension的成員函數(shù),且有一個(gè)long型的參數(shù)即dwChoice
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4)
//該參數(shù)在URL中的名字為Favorite
ON_PARSE_COMMAND_PARAMS("Favorite")
END_PARSE_MAP(CPinballExtension)
而對(duì)于ISAPI Filter,在VC中可以通過重載CHttpFilter(或其子類)的不同的成員函數(shù)來實(shí)現(xiàn)對(duì)不同事件的處理。可重載的函數(shù)如下,每一個(gè)成員函數(shù)均對(duì)應(yīng)一個(gè)或多個(gè)事件:
OnPreprocHeaders
OnAuthentication
OnUrlMap
OnSendRawData
OnReadRawData
OnLog
OnEndOfNetSession
MSDN提供了4個(gè)關(guān)于ISAPI的編程實(shí)例:counter、MFCUCASE、pinball、wwwquote,有興趣的可看看,本文主要不是介紹編程,所以不再贅述。
參考資料:
1、MSDN
2、《精通CGI編程》,丁一強(qiáng)等,清華大學(xué)出版社