开发适用于 IIS 7.0 的本机 C\C++ 模块

作者:Mike Volodarsky

介绍

IIS 7.0 及更高版本允许通过以两种方式开发的模块来扩展服务器:

  • 使用托管代码和 ASP.NET 服务器扩展性 API
  • 使用本机代码和 IIS 本机服务器扩展性 API

与以前版本的 IIS 不同,大多数服务器扩展性方案不需要进行本机 (C++) 代码开发,并且可以使用托管代码和 ASP.NET API 来适应要求。 使用 ASP.NET 扩展服务器可以显著减少开发时间,并利用 ASP.NET 和 .NET Framework 的丰富功能。 若要详细了解如何使用 ASP.NET 扩展 IIS,请参阅使用 .NET 开发 IIS 模块

IIS 还提供一个 (C++) 本机核心服务器 API,该 API 取代了以前 IIS 版本中的 ISAPI 筛选器和扩展 API。 如果你的具体要求需要进行本机代码开发,或者你想要转换现有的本机 ISAPI 组件,请利用此 API 来生成服务器组件。 新的本机服务器 API 采用直观的对象模型进行面向对象的开发,提供对请求处理的更多控制,并使用更简单的设计模式来帮助编写可靠的代码。

本演练探讨以下任务:

  • 使用本机 (C++) 服务器 API 开发本机模块
  • 在服务器上部署本机模块

若要编译模块,必须安装包含 IIS 头文件的平台 SDK。 最新的 Windows Vista 平台 SDK 可在此处获取。

若要将平台 SDK 与 Visual Studio 2005 配合使用,必须注册该 SDK。 安装 SDK 后,请通过“开始”>“程序”>“Microsoft Windows SDK”>“Visual Studio 注册”>“向 Visual Studio 注册 Windows SDK 目录”来执行此操作

此模块的源代码可在 Visual Studio IIS7 本机模块示例中找到。

开发本机模块

在此任务中,我们将使用新的本机 (C++) 服务器 API 探讨本机模块的开发。 本机模块是一个 Windows DLL,其中包含:

  • RegisterModule 导出的函数。 此函数负责创建模块工厂,并为一个或多个服务器事件注册模块。
  • CHttpModule 基类继承的模块类的实现。 此类提供模块的主要功能。
  • 模块工厂类的实现,实现 IHttpModuleFactory 接口。 该类负责创建模块的实例。

注意

在某些情况下,还可以实现 IGlobalModule 接口,以扩展一些与请求处理无关的服务器功能。 这是一个进阶主题,本演练不予讲述。

本机模块具有以下生命周期:

  1. 服务器工作进程启动时,它会加载包含模块的 DLL,并调用其导出的 RegisterModule 函数。 在此函数中,可以:

    a. 创建模块工厂。
    b. 为模块实现的请求管道事件注册模块工厂。

  2. 当请求抵达时,服务器将:

    a. 使用提供的工厂创建模块类的实例。
    b. 针对注册的每个请求事件,在模块实例上调用相应的事件处理程序方法。
    c. 在请求处理结束时处置模块的实例。

现在生成它。

该模块的完整源代码可在 Visual Studio IIS7 本机模块示例中找到。 以下步骤是用于开发模块的最重要步骤,不包括支持代码和错误处理。

实现加载模块 DLL 时由服务器调用的 RegisterModule 函数。 其签名和本机 API 的其余部分在 httpserv.h 头文件中定义,该头文件是平台 SDK 的一部分(如果你没有平台 SDK,请参阅简介来了解如何获取它):

main.cpp

HRESULT        
__stdcall        
RegisterModule(        
    DWORD                           dwServerVersion,    
    IHttpModuleRegistrationInfo *   pModuleInfo,
    IHttpServer *                   pHttpServer            
)
{
   // step 1: save the IHttpServer and the module context id for future use 
    g_pModuleContext = pModuleInfo->GetId();
    g_pHttpServer = pHttpServer;

    // step 2: create the module factory 
    pFactory = new CMyHttpModuleFactory();

    // step 3: register for server events 
    hr = pModuleInfo->SetRequestNotifications( pFactory, 
                                              RQ_ACQUIRE_REQUEST_STATE,
                                               0 );            
}

RegisterModule

我们需要在 RegisterModule 中完成三个基本任务:

保存全局状态

我们将存储全局服务器实例和模块上下文 ID,供稍后在全局变量中使用。 虽然此示例不使用此信息,但许多模块发现,在请求处理期间保存和使用该信息很有用。 IHttpServer 接口提供对许多服务器功能的访问,例如打开文件和访问缓存。 模块上下文 ID 用于将自定义模块状态与多个服务器对象(例如请求和应用程序)相关联。

创建模块工厂

在本演练的后面部分,我们将实现工厂类 CMyHttpModuleFactory。 此工厂负责根据每个请求制造模块的实例。

为所需的请求处理事件注册模块工厂

注册是通过 SetRequestNotificatons 方法完成的,该方法指示服务器:使用指定的工厂为每个请求创建模块实例;为每个指定的请求处理阶段调用相应的事件处理程序。

在本例中,我们只对 RQ_ACQUIRE_REQUEST_STATE 阶段感兴趣。 构成请求处理管道的阶段的完整列表在 httpserv.h 中定义:

#define RQ_BEGIN_REQUEST               0x00000001 // request is beginning 
#define RQ_AUTHENTICATE_REQUEST        0x00000002 // request is being authenticated             
#define RQ_AUTHORIZE_REQUEST           0x00000004 // request is being authorized 
#define RQ_RESOLVE_REQUEST_CACHE       0x00000008 // satisfy request from cache 
#define RQ_MAP_REQUEST_HANDLER         0x00000010 // map handler for request 
#define RQ_ACQUIRE_REQUEST_STATE       0x00000020 // acquire request state 
#define RQ_PRE_EXECUTE_REQUEST_HANDLER 0x00000040 // pre-execute handler 
#define RQ_EXECUTE_REQUEST_HANDLER     0x00000080 // execute handler 
#define RQ_RELEASE_REQUEST_STATE       0x00000100 // release request state 
#define RQ_UPDATE_REQUEST_CACHE        0x00000200 // update cache 
#define RQ_LOG_REQUEST                 0x00000400 // log request 
#define RQ_END_REQUEST                 0x00000800 // end request

此外,你还可以订阅在请求处理期间由于其他模块执行的操作(例如刷新对客户端的响应)而可能发生的多个非确定性事件:

#define RQ_CUSTOM_NOTIFICATION         0x10000000 // custom notification 
#define RQ_SEND_RESPONSE               0x20000000 // send response 
#define RQ_READ_ENTITY                 0x40000000 // read entity 
#define RQ_MAP_PATH                    0x80000000 // map a url to a physical path

为了使服务器能够访问我们的 RegisterModule 实现,我们必须将其导出。 使用包含 EXPORTS 关键字的 .DEF 文件导出 RegisterModule 函数。

接下来,实现模块工厂类:

mymodulefactory.h:

class CMyHttpModuleFactory : public IHttpModuleFactory
{
public:
    virtual HRESULT GetHttpModule(
        OUT CHttpModule            **ppModule, 
        IN IModuleAllocator        *
    )
            
    {
    }

   virtual void Terminate()
    {
    }

};

模块工厂实现 IHttpModuleFactory 接口,用于根据每个请求创建模块的实例。

服务器在每个请求开始时调用 GetHttpModule 方法来获取用于该请求的模块实例。 实现仅返回模块类 CMyHttpModule 的新实例,我们接下来将实现该实例。 我们很快就会看到,它使我们能够轻松存储请求状态,而不必担心线程安全,因为服务器始终为每个请求创建并使用模块的新实例。

更高级的工厂实现可能决定使用单一实例模式而不是每次都创建新实例,或者使用提供的 IModuleAllocator 接口在请求池中分配模块内存。 本演练不讨论这些高级模式。

当工作进程关闭以执行模块的最终清理时,服务器会调用 Terminate 方法。 如果在 RegisterModule 中初始化任何全局状态,请在此方法中实现其清理。

实现模块类

此类负责在一个或多个服务器事件期间提供模块的主要功能:

myhttpmodule.h:

class CMyHttpModule : public CHttpModule
{
public:
    REQUEST_NOTIFICATION_STATUS
    OnAcquireRequestState(
        IN IHttpContext *                       pHttpContext,
        IN OUT IHttpEventProvider *             pProvider
    );
};

模块类继承自 CHttpModule 基类,该基类为前面所述的每个服务器事件定义一个事件处理程序方法。 当请求处理管道执行每个事件时,它会在已注册该事件的每个模块实例上调用关联的事件处理程序方法。

每个事件处理程序方法有以下签名:

REQUEST_NOTIFICATION_STATUS
    OnEvent(
        IN IHttpContext *                       pHttpContext,
        IN OUT IHttpEventProvider *             pProvider
    );

IHttpContext 接口提供对请求上下文对象的访问,该对象可用于执行请求处理任务,例如检查请求和操作响应。

对于为模块提供特定功能的每个事件,IHttpEventProvider 接口将被替换为更具体的接口。 例如,OnAuthenticateRequest 事件处理程序接收 IAuthenticationProvider 接口,该接口允许模块设置经过身份验证的用户。

每个事件处理程序方法的返回结果是一个 REQUEST_NOTIFICATION_STATUS 枚举值。 如果模块成功执行任务,则必须返回 RQ_NOTIFICATION_CONTINUE;管道应该继续执行。

如果发生失败,并且你希望因错误而中止请求处理,则必须设置错误状态并返回 RQ_NOTIFICATION_FINISH_REQUEST。 RQ_NOTIFICATION_PENDING 返回结果允许你异步执行工作,并释放处理请求的线程,以便可以将其重用于另一个请求。 本文不会讨论异步执行。

模块类将重写 OnAcquireRequestState 事件处理程序方法。 为了在任何管道阶段提供功能,模块类必须重写相应的事件处理程序方法。 如果在 RegisterModule 中注册事件,但不重写模块类上的相应事件处理程序方法,则模块将在运行时失败(如果在调试模式下编译,则会触发调试时断言)。 请务必小心,并确保重写方法的方法签名与你要重写的 CHttpModule 类的基类方法完全相同。

编译模块

需要安装平台 SDK 才能进行编译。 有关如何获取平台 SDK 以及使 Visual Studio 能够引用它的详细信息,请参阅简介

部署本机模块

编译模块后,必须将其部署到服务器上。 编译模块,然后将 IIS7NativeModule.dll(以及 IIS7NativeModule.pdb 调试符号文件,如果需要)复制到运行 IIS 的计算机上的任何位置。

与可以直接添加到应用程序的托管模块不同,本机模块需要首先安装在服务器上。 这需要管理权限。

若要安装本机模块,可以采用多种做法:

  • 使用 APPCMD.EXE 命令行工具
    APPCMD 使模块安装变得简单。 转到“开始”>“程序”>“附件”,右键单击“命令行提示符”,然后选择“以管理员身份运行”。 在命令行窗口中执行以下命令:
    %systemroot%\system32\inetsrv\appcmd.exe install module /name:MyModule /image:[FULL\_PATH\_TO\_DLL]
    其中 [FULL_PATH_TO_DLL] 是包含刚刚生成的模块的已编译 DLL 的完整路径。
  • 使用 IIS 管理工具
    这样可以使用 GUI 添加模块。 转到“开始”>“运行”,键入 inetmgr,然后按 Enter。 连接到 localhost,找到“模块”任务,并双击将其打开。 然后,单击右侧窗格中的“添加本机模块”任务
  • 手动安装模块
    通过将模块添加到 applicationHost.config 配置文件中的 <system.webServer>/<globalModules> 配置部分来手动安装该模块,并在同一文件的 <system.webServer>/<modules> 配置部分中添加对它的引用,以便启用它。 建议使用前两种做法之一来安装模块,而不要直接编辑配置。

任务已完成 – 我们已经完成了新本机模块的配置。

总结

在本演练中,你已了解如何使用新的本机 (C++) 扩展性 API 开发和部署自定义本机模块。 请查阅本机代码开发概述来详细了解本机 (C++) 服务器 API。

若要了解如何使用托管代码和 .NET Framework 扩展 IIS,请参阅使用 .NET 开发 IIS 模块。 若要详细了解如何管理 IIS 模块,请参阅模块概述白皮书