ISAPI篩選器介紹

ISAPI(Internet Server Application Programming Interface)作爲一種可用來替代CGI的方
法,是由微軟和Process軟件公司聯合提出的Web服務器上的API標準。ISAPI與Web服務器結合緊
密,功能強大,能夠獲得大量的信息,因此利用ISAPI可以開發出靈活高效的Web服務器增強程序
。由於ISAPI程序與Web服務器的關係,使得ISAPI接口在安全方面有一定的研究價值。本文主要
討論ISAPI在IIS和VC++ 6.0中的實現。


一、ISAPI接口和CGI接口的不同。

ISAPI程序和CGI程序完成類似的功能,但是實現方法不同。

1、ISAPI程序以DLL形式被Web服務器加載到自己的進程空間中,因此和服務器共用同一個地址空
間,且在沒有客戶請求時可以將其從內存中卸載;而對客戶端發來的每個對CGI程序的請求則需
要服務器爲它單獨啓動一個進程,這需要耗費大量的時間和內存。當併發的請求數目很大時,使
用CGI在效率上不如ISAPI。

2、CGI程序通過環境塊和標準輸入輸出與Web服務器進行通信,而ISAPI程序與服務器結合得更爲
緊密,與服務器共享同一個進程上下文,主要通過一個參數塊與服務器進行交互,可以從服務器
那裏獲得關於當前HTTP連接的大量信息。

ISAPI主要分爲ISA和ISAPI Filter兩部分。ISA方法相對而言要傳統一些,利用一些特殊的鏈接
,指向服務器的作業,供程序開發人員設計一些擴展功能;而ISAPI過濾器則傾向於構造服務器
直接調用的模塊,提供一種無縫鏈接部件用於監測直接來自於服務器的HTTP請求。


二、ISA

ISA(Internet Server Application)也可稱爲ISAPI DLL,其功能和CGI程序的功能直接相對應
,使用方法和CGI也類似,由客戶端在URL中指定其名稱而激活。例如下面的請求將調用服務器的
虛擬可執行目錄Scripts下的function.dll(ISAPI DLL必須放在服務器的虛擬可執行目錄下):
                              http://www.abc.com/Scripts/function.dll?
                              
ISA和服務器之間的接口主要有兩個:GetExtentionVersion( )和HttpExtentionProc( )。任何
ISA都必須在其PE文件頭的引出表中定義這兩個引出函數,以供Web服務器在適當的時候調用。

1、當服務器剛加載ISA時,它會調用ISA提供的GetExtentionVersion( )來獲得該ISA所需要的服
務器版本,並與自己的版本相比較,以保證版本兼容。函數原型如下:

BOOL WINAPI GetExtentionVersion(HSE_VERSION_INFO *version);
typedef struct   _HSE_VERSION_INFO
{
    DWORD  dwExtensionVersion;                          //版本號
    CHAR   lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; //關於ISA的描述字符串
}   HSE_VERSION_INFO, *LPHSE_VERSION_INFO;

2、ISA的真正入口是HttpExtentionProc( ),它相當於普通C程序的main( )函數,在這個函數中
根據不同的客戶請求作不同的處理。服務器和HttpExtentionProc( )之間是通過擴展控制塊(
Extention Control Block)來進行通信的,即ECB中存放入口參數和出口參數,包括服務器提供
的幾個回調函數的入口地址。函數原型如下:

DWORD HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB );

ECB的結構定義如下(IN表示入口參數,OUT表示出口參數):

typedef struct _EXTENSION_CONTROL_BLOCK 
{
   DWORD     cbSize;        //IN,本結構的大小,只讀
   DWORD     dwVersion      //IN,版本號,高16位爲主版本號,低16位爲次版本號
   HCONN     ConnID;        //IN,連接句柄,由服務器分配,ISA只能讀取該值
   DWORD     dwHttpStatusCode;                 //OUT,當前完成的事務狀態
   CHAR      lpszLogData[HSE_LOG_BUFFER_LEN];  //OUT,需要寫入到日誌文件中的內容
   LPSTR     lpszMethod;    //IN,等價於CGI的環境變量REQUEST_METHOD
   LPSTR     lpszQueryString;                  //IN,等價於環境變量QUERY_STRING
   LPSTR     lpszPathInfo;                     //IN,等價於環境變量PATH_INFO
   LPSTR     lpszPathTranslated;               //IN,等價於環境變量PATH_TRANSLATED
   DWORD     cbTotalBytes;                     //IN,等價於環境變量CONTENT_LENGTH
   DWORD     cbAvailable;                      //IN,緩衝區中的可用字節數
   LPBYTE    lpbData;                          //IN,緩衝區指針,指向客戶端發來的數據
   LPSTR     lpszContentType;                  //IN,等價於環境變量CONTENT_TYPE

//回調函數,用於返回服務器的連接信息或特定的服務器詳細情況
   BOOL ( WINAPI * GetServerVariable ) 
      ( HCONN       hConn,
       LPSTR       lpszVariableName,
       LPVOID      lpvBuffer,
       LPDWORD     lpdwSize );

   BOOL ( WINAPI * WriteClient )      //回調函數,從客戶端的HTTP請求中讀取數據
      ( HCONN      ConnID,
      LPVOID     Buffer,
      LPDWORD    lpdwBytes,
      DWORD      dwReserved );

   BOOL ( WINAPI * ReadClient )       //回調函數,向客戶端發送數據
      ( HCONN      ConnID,
      LPVOID     lpvBuffer,
      LPDWORD    lpdwSize );

   BOOL ( WINAPI * ServerSupportFunction )  //回調函數,訪問服務器的一般和特定功能
      ( HCONN      hConn,
      DWORD      dwHSERRequest,
      LPVOID     lpvBuffer,
      LPDWORD    lpdwSize,
      LPDWORD    lpdwDataType );

} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;

在上述ECB中,服務器不但提供了當前HTTP連接的句柄和一些變量,而且提供了4個回調函數給
ISA調用,從而使ISA可以獲得更詳盡的信息。

三、ISAPI Filter

ISAPI Filter位於服務器和客戶端之間,能夠對服務器和客戶端之間的通信進行預處理和後處理
,比如對通信進行加密/解密、提供對客戶進行身份驗證的新方法、提供自定義的日誌記錄等,
在CGI中沒有與ISAPI Filter直接相對應的部分。


ISAPI Filter與服務器之間的接口有兩個:GetFilterVersion( )和HttpFilterProc( )。任何
ISAPI Filter都必須引出這兩個函數以供服務器調用。

1、在註冊表的如下鍵值中存放着所有ISAPI Filter的文件名,IIS服務器啓動時從該鍵值中獲得
Filter的文件名並加載它們。

HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/FilterDLL
                  
2、然後服務器調用每個Filter提供的GetFilterVersion( )函數,獲得版本號以及該Filter希望
處理的事件,即ISAPI Filter通過引出GetFilterVersion( )函數來告知服務器自己希望處理什
麼類型的事件,因爲ISAPI Filter是通過事件來激活的,當滿足條件的事件到達時,服務器就會
調用Filter引出的主函數HttpFilterProc( )對該事件進行處理。GetFilterVersion( )的原型如
下:

BOOL WINAPI GetFilterVersion( 
DWORD  dwServerFilterVersion;                    //IN,服務器使用的版本規範          
DWORD  dwFilterVersion;                          //OUT,過濾器使用的版本規範
CHAR   lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1]; //OUT,對該過濾器的描述字符串
DWORD  dwFlags                                   //OUT,事件和優先級標誌
);

事件和優先級標誌dwFlasg的取值在MSDN中有詳細解釋,其中包括該Filter被調用的優先級,一
般應使用默認的低優先級,否則可能會對系統的性能造成很大影響。

3、HttpFilterProc( )是ISAPI Filter主要的入口函數,它根據當前的事件的不同作出不同的處
理。服務器通過如下的參數塊和Filter進行交互,這個參數塊的作用和ISA中的ECB類似。

typedef struct _HTTP_FILTER_CONTEXT
{

DWORD    cbSize;                                  //IN,本參數塊的大小
DWORD    Revision;                                //IN
PVOID    ServerContext;                           //IN,由server使用本參數
DWORD    ulReserved;                              //IN,由server使用本參數
BOOL     fIsSecurePort;                           //IN,事件是否發生在安全端口上
PVOID    pFilterContext;                          //IN/OUT,與本次請求相關的上下文

//回調函數,取得關於服務器和本次連接的信息
BOOL    (WINAPI * GetServerVariable) (            
   struct _HTTP_FILTER_CONTEXT *    pfc,
   LPSTR      lpszVariableName,
   LPVOID     lpvBuffer,
   LPDWORD    lpdwSize
   );    

BOOL    (WINAPI * AddResponseHeaders) (          //回調函數,給HTTP響應添加一個標頭
   struct _HTTP_FILTER_CONTEXT *    pfc,
   LPSTR    lpszHeaders,
   DWORD    dwReserved
   );     

BOOL    (WINAPI * WriteClient)  (                //回調函數,將原始數據發送給客戶端
   struct _HTTP_FILTER_CONTEXT *    pfc,
   LPVOID     Buffer,
   LPDWORD    lpdwBytes,
   DWORD      dwReserved
   );     

VOID *     (WINAPI * AllocMem) (                 //回調函數,分配內存。
   struct _HTTP_FILTER_CONTEXT *    pfc,
   DWORD      cbSize,
   DWORD      dwReserved
   );    

BOOL    (WINAPI * ServerSupportFunction) (   //回調函數,訪問服務器的一般和特定功能
   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中對ISAPI的支持

VC++ 6.0中定義了5個相關的類以簡化ISAPI的編程工作:CHttpServer、CHttpServerContext、
CHttpFilter、CHttpFilterContext、CHtmlStream,這5個類都沒有父類。其中CHttpServer和
CHttpServerContext主要用來編寫ISA,CHttpFilter和CHttpFilterContext則用來編寫ISAPI 
Filter,而CHtmlStream則用來操作內存中的HTML文件,爲其它的4個類提供服務。CHttpServer
在每個ISA中只能有一個實例,一個CHttpServer可以對應多個CHttpServerContext實例,每個
CHttpServerContext處理一個客戶請求,這樣可以處理併發的HTTP請求;CHttpFilter和
CHttpFilterContext之間的關係與此類似,在每個ISAPI Filter中只能有一個CHttpFilter實例
,但是可以有多個CHttpFilterContext來處理併發的事件。CHttpServer和CHttpFilter是獨立的
類,它們可以共存於一個DLL中,也可以分別在不同的DLL中。

一個ISA可以提供多個命令,每個命令對應於CHttpServer(或其子類)的一個成員函數,客戶端
可以在URL中指定命令名及其參數。在VC++ 6.0中是通過parse map來實現這種對應的。

Parse map類似MFC中的Windows消息分發機制,通過使用VC提供的DECLARE_PARSE_MAP、
BEGIN_PARSE_MAP、ON_PARSE_COMMAND、ON_PARSE_COMMAND_PARAMS、DEFAULT_PARSE_COMMAND、
END_PARSE_MAP等宏,可以實現對不同的命令的處理。每個CHttpServer中只能建立一個parse 
map,當客戶端給ISA發來命令的時候,parse map可以分析HTTP請求中的命令名及其參數,將該
命令與相應的成員函數關聯起來,即由該成員函數處理該命令。以MSDN中的例子程序pinball爲
例,該例中有下面這樣一個表單:

<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>

當客戶端選中了上面的表單中的“Attack from Mars”這一項並點擊了submit按鈕後,服務器端
最終將得到如下的URL串:

http://www.abc.com/pinball.dll?MfcI ... tImage&Favorite=1
                        
在該URL串中,命令名是GetImage,參數Favorite的值是1,因此pinball.dll中的如下成員函數
將被調用以處理該請求,其中參數dwChoice對應URL中的參數Favorite:

void CPinballExtension::GetImage(CHttpServerContext* pCtxt, long dwChoice);

而parse map需要按照下面的形式定義:

//CPinballExtension從CHttpServer派生而來
BEGIN_PARSE_MAP(CPinballExtension, CHttpServer) 

//GetImage是CPinballExtension的成員函數,且有一個long型的參數即dwChoice
ON_PARSE_COMMAND(GetImage, CPinballExtension, ITS_I4) 

//該參數在URL中的名字爲Favorite
ON_PARSE_COMMAND_PARAMS("Favorite")                   

END_PARSE_MAP(CPinballExtension)

而對於ISAPI Filter,在VC中可以通過重載CHttpFilter(或其子類)的不同的成員函數來實現
對不同事件的處理。可重載的函數如下,每一個成員函數均對應一個或多個事件:

OnPreprocHeaders
OnAuthentication
OnUrlMap
OnSendRawData
OnReadRawData
OnLog
OnEndOfNetSession

MSDN提供了4個關於ISAPI的編程實例:counter、MFCUCASE、pinball、wwwquote,有興趣的可看
看,本文主要不是介紹編程,所以不再贅述。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章