通过


为 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,它取代了以前的 IIS 版本中的 ISAPI 筛选器和扩展 API。 如果你有需要本机代码开发的特定要求,或者想要转换现有的本机 ISAPI 组件,请利用此 API 生成服务器组件。 新的本机服务器 API 使用直观的对象模型实现面向对象的开发,可更好地控制请求处理,并使用更简单的设计模式来帮助编写可靠的代码。

本演练将探讨以下任务:

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

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

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

此模块的源代码在 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 );            
}

The 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 关键字以导出 RegisterModule 函数的 DEF 文件。

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

mymodulefactory.h:

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

   virtual void Terminate()
    {
    }

};

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

服务器在每个请求的开头调用 GetHttpModule 方法,以获取要用于此请求的模块实例。 该实现只返回我们接下来实现的模块类 CMyHttpModule 的新实例。 正如我们不久看到的,这使我们能够轻松地存储请求状态,而无需担心线程安全,因为服务器始终为每个请求创建和使用模块的新实例。

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

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

实现 Module 类

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

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 才能进行编译。 请参阅简介,以了解有关获取它以及启用 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 开发和部署自定义本机模块。 请参阅 Native-Code 开发概述 ,了解有关本机 (C++) 服务器 API 的详细信息。

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