码迷,mamicode.com
首页 > Windows程序 > 详细

使用ISAPI过滤器开发来增强IIS的功能

时间:2016-07-15 13:45:12      阅读:411      评论:0      收藏:0      [点我收藏+]

标签:

作为一个WWW服务器(WEB)软件,微软公司的Internet Infomation Server(IIS)简单易学,管理方便,得到了广泛的使用。您还可以通过ISAPI过滤器,进行自己定制的处理,来增强IIS的功能。ISAPI过滤器可以定制以下的处理:接收HTTP协议头预处理、发送HTTP协议头预处理、发送生数据预处理、获得生数据预处理、HTTP会话结束信息处理、自定义的安全认证机制、URL映射信息处理、日志记录处理等。灵活利用这些定制处理,您可以完成许多看似难以实现的功能,得到意想不到的效果。但是ISAPI过滤器使用不当也会影响服务器的性能。

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

用VS2008开发ISAPI程序

因为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函数,这个函数主要实现了下面两个功能:

  1. 报告ISAPI可以实现的扩展服务
  2. 取得一个简短的描述扩展的字符串。

然后IIS开始调用HttpExtensionProc函数。这个函数会传递一个ECB指针给ISAPI扩展以开始真正的调用。通过这个函数ISAPI可以向客户端(如浏览器)回写数据。过会儿我们会检查。

第三个也是最后一个ISAPI扩展的入口函数是TerminateExtension函数。它在当ISAPI扩展从内存中卸载的时候调用。所有的清除代码可以在这个函数中实现。

简言之,一个ISAPI扩展是一个导出三个函数的非常规范的DLL。

  1. GetExtensionVersion
  2. HttpExtensionProc
  3. 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事件,以便在每次网络会话结束时就释放资源,这样可以减轻服务器的负荷。


使用ISAPI过滤器开发来增强IIS的功能

标签:

原文地址:http://blog.csdn.net/enweitech/article/details/51915932

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!