Desarrollo de un módulo nativo de C\C++ para IIS 7.0

por Mike Volodarsky

Introducción

IIS 7.0 y versiones posteriores permiten extender el servidor mediante módulos que se desarrollan de dos maneras:

  • Uso de código administrado y las API de extensibilidad del servidor ASP.NET
  • Uso de código nativo y las API de extensibilidad del servidor nativo de IIS

A diferencia de las versiones anteriores de IIS, la mayoría de los escenarios de extensibilidad del servidor no requieren el desarrollo de código nativo (C++), y se pueden acomodar mediante código administrado y las API de ASP.NET. El uso de ASP.NET para ampliar el servidor le permite reducir drásticamente el tiempo de desarrollo y aprovechar las ventajas de la funcionalidad enriquecida de ASP.NET y .NET Framework. Para más información sobre la extensión de IIS con ASP.NET, consulte Desarrollo de un módulo IIS con .NET.

IIS también proporciona una API de servidor principal nativo (C++), que reemplaza la API de extensión y el filtro ISAPI de las versiones anteriores de IIS. Si tiene requisitos específicos que exigen el desarrollo de código nativo o desea convertir los componentes de ISAPI nativos existentes, aproveche esta API para compilar componentes de servidor. La nueva API de servidor nativa incluye el desarrollo orientado a objetos con un modelo de objetos intuitivo, proporciona más control sobre el procesamiento de solicitudes y usa patrones de diseño más sencillos para ayudarle a escribir código sólido.

En este tutorial se examinan las siguientes tareas:

  • Desarrollo de un módulo nativo mediante la API de servidor nativa (C++)
  • Implementación de un módulo nativo en el servidor

Para compilar el módulo, debe instalar el SDK de plataforma que contiene los archivos de encabezado de IIS. El SDK de la plataforma de Windows Vista más reciente está disponible aquí.

Para usar el SDK de la plataforma con Visual Studio 2005, debe registrar el SDK. Después de instalar el SDK, hágalo a través de Iniciar > programas > Microsoft Windows SDK > Registro de Visual Studio > Registro de directorios de Windows SDK con Visual Studio.

El código fuente de este módulo está disponible en el ejemplo de módulo nativo de IIS7 de Visual Studio.

Desarrollo de un módulo nativo

En esta tarea, examinamos el desarrollo de un módulo nativo mediante la nueva API de servidor nativa (C++). Un módulo nativo es un archivo DLL de Windows que contiene lo siguiente:

  • Función exportada RegisterModule. Esta función es responsable de crear un generador de módulos y registrar el módulo para uno o varios eventos de servidor.
  • Implementación de la clase de módulo que hereda de la clase base CHttpModule. Esta clase proporciona la funcionalidad principal del módulo.
  • Implementación de la clase de generador de módulos que implementa la interfaz IHttpModuleFactory. La clase es responsable de crear instancias del módulo.

Nota:

En algunos casos, también puede implementar la interfaz IGlobalModule, con el fin de ampliar parte de la funcionalidad del servidor que no está relacionada con el procesamiento de solicitudes. Este es un tema avanzado y no se trata en este tutorial.

El módulo nativo tiene el siguiente ciclo de vida:

  1. Cuando se inicie el proceso de trabajo del servidor, cargará el archivo DLL que contiene el módulo e invocará su función exportada RegisterModule. En esta función, podrá:

    a. Crear el generador de módulos.
    b. Registrar el generador de módulos para los eventos de canalización de solicitud que implementa el módulo.

  2. Cuando llega una solicitud, el servidor:

    a. Crea una instancia de la clase de módulo mediante el generador que proporcionó.
    b. Llama al método de controlador de eventos adecuado en la instancia del módulo para cada uno de los eventos de solicitud que registró.
    c. Elimina la instancia del módulo al final del procesamiento de solicitudes.

Ahora, para compilarlo.

El código fuente completo del módulo está disponible en el ejemplo de módulo nativo de Visual Studio IIS7. Los siguientes pasos son los más importantes para desarrollar el módulo y no incluyen compatibilidad con el código y el control de errores.

Implemente la función RegisterModule que el servidor invoca cuando se carga el archivo DLL del módulo. Su firma y el resto de la API nativa se definen en el archivo de encabezado httpserv.h, que forma parte del SDK de plataforma (si no tiene el SDK de plataforma, consulte la Introducción para obtener información sobre cómo obtenerla):

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

El RegisterModule

Hay tres tareas básicas que necesitamos realizar dentro de RegisterModule:

Guardado del estado global

Almacenaremos la instancia global del servidor y el identificador de contexto del módulo para su uso posterior en variables globales. Aunque en este ejemplo no se usa esta información, muchos módulos le resultarán útiles para guardar y usar más adelante durante el procesamiento de solicitudes. La interfaz IHttpServer proporciona acceso a muchas funciones de servidor, como abrir archivos y acceder a la memoria caché. El identificador de contexto del módulo se usa para asociar el estado del módulo personalizado a varios objetos de servidor, como la solicitud y la aplicación.

Creación de un generador de módulos

Implementaremos nuestra clase de fábrica, CMyHttpModuleFactory, más adelante en este tutorial. Esta fábrica es responsable de las instancias de fabricación de nuestro módulo para cada solicitud.

Registro del generador de módulos para los eventos de procesamiento de solicitudes deseadas

El registro se realiza mediante el método SetRequestNotificatons, que ordena al servidor: crear la instancia de módulo para cada solicitud mediante el generador especificado y, para invocar los controladores de eventos adecuados en él para cada una de las fases de procesamiento de solicitudes especificadas.

En este caso, solo nos interesa la fase RQ_ACQUIRE_REQUEST_STATE. La lista completa de las fases que componen la canalización de procesamiento de solicitudes se define en 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

Además, puede suscribirse a varios eventos no deterministas que pueden producirse durante el procesamiento de solicitudes debido a acciones que realizan otros módulos, como vaciar la respuesta al cliente:

#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

Para que la implementación de RegisterModule sea accesible para el servidor, debemos exportarla. Use un archivo .DEF que contiene la palabra clave EXPORTS para exportar nuestra función RegisterModule.

A continuación, implemente la clase de fábrica del módulo:

mymodulefactory.h:

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

   virtual void Terminate()
    {
    }

};

El generador de módulos implementa la interfaz IHttpModuleFactory y sirve para crear instancias del módulo en cada solicitud.

El servidor llama al método GetHttpModule al principio de cada solicitud para obtener la instancia del módulo que se va a usar para esta solicitud. La implementación simplemente devuelve una nueva instancia de nuestra clase de módulo, CMyHttpModule, que implementaremos a continuación. Como veremos en breve, esto nos permite almacenar fácilmente el estado de solicitud sin preocuparnos por la seguridad de subprocesos, ya que el servidor siempre crea y usa una nueva instancia del módulo para cada solicitud.

Las implementaciones de fábrica más avanzadas pueden decidir usar un patrón singleton en lugar de crear una nueva instancia cada vez, o usar la interfaz IModuleAllocator proporcionada para asignar memoria de módulo en el grupo de solicitudes. Estos patrones avanzados no se describen en este tutorial.

El servidor llama al método Terminate cuando el proceso de trabajo se cierra para realizar la limpieza final del módulo. Si inicializa cualquier estado global en RegisterModule, implemente su limpieza en este método.

Implementación de la clase Module

Esta clase es responsable de proporcionar la funcionalidad principal del módulo durante uno o varios eventos de servidor:

myhttpmodule.h:

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

La clase de módulo hereda de la clase base CHttpModule, que define un método de controlador de eventos para cada uno de los eventos de servidor descritos anteriormente. Cuando la canalización de procesamiento de solicitudes ejecuta cada evento, invoca el método de controlador de eventos asociado en cada una de las instancias de módulo que se han registrado para ese evento.

Cada método de controlador de eventos tiene la siguiente firma:

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

La interfaz IHttpContext proporciona acceso al objeto de contexto de solicitud, que se puede usar para realizar tareas de procesamiento de solicitudes, como inspeccionar la solicitud y manipular la respuesta.

La interfaz IHttpEventProvider se reemplaza por una interfaz más específica para cada uno de los eventos que proporcionan funcionalidad específica al módulo. Por ejemplo, el controlador de eventos OnAuthenticateRequest recibe la interfaz IAuthenticationProvider que permite al módulo establecer el usuario autenticado.

La devolución de cada método de controlador de eventos es uno de los valores de la enumeración REQUEST_NOTIFICATION_STATUS. Debe devolver RQ_NOTIFICATION_CONTINUE si el módulo ha realizado correctamente la tarea; la canalización debe continuar con la ejecución.

Si se produjo un error y desea anular el procesamiento de solicitudes con un error, debe establecer el estado del error y devolver RQ_NOTIFICATION_FINISH_REQUEST. La devolución RQ_NOTIFICATION_PENDING le permite realizar el trabajo de forma asincrónica y soltar el subproceso que procesa la solicitud para que pueda ser reutilizado para otra solicitud. La ejecución asincrónica no se describe en este artículo.

Nuestra clase de módulo invalida el método de controlador de eventos OnAcquireRequestState. Para proporcionar funcionalidad en cualquiera de las fases de canalización, la clase de módulo debe invalidar el método de controlador de eventos correspondiente. Si se registra para un evento en RegisterModule, pero no invalida el método de controlador de eventos adecuado en la clase de módulo, el módulo producirá un error en runtime (y desencadenará una aserción en tiempo de depuración si se compila en el modo de depuración). Tenga cuidado y asegúrese de que la firma del método invalidado es exactamente equivalente al método de clase base de la clase CHttpModule que está reemplazando.

Compilación del módulo

Recuerde que necesita el SDK de plataforma para compilarlo. Consulte la introducción para obtener más información sobre cómo obtenerlo y permitir que Visual Studio haga referencia a él.

Implementación de un módulo nativo

Una vez compilado el módulo, debe implementarlo en el servidor. Compile el módulo y, a continuación, copie IIS7NativeModule.dll (y el archivo de símbolos de depuración IIS7NativeModule.pdb si lo desea) en cualquier ubicación de la máquina que ejecuta IIS.

Los módulos nativos, a diferencia de los módulos administrados que se pueden agregar directamente a la aplicación, deben instalarse primero en el servidor. Esto requiere privilegios administrativos.

Para instalar un módulo nativo, tiene varias opciones:

  • Use la herramienta de línea de comandos APPCMD.EXE
    APPCMD facilita la instalación de módulos. Vaya a >Iniciar programas>Accesorios, haga clic con el botón derecho en el símbolo de la línea de comandos y elija Ejecutar como administrador. En la ventana de la línea de comandos, ejecute lo siguiente:
    %systemroot%\system32\inetsrv\appcmd.exe install module /name:MyModule /image:[FULL\_PATH\_TO\_DLL]
    Donde [FULL_PATH_TO_DLL] es la ruta de acceso completa al archivo DLL compilado que contiene el módulo que acaba de compilar.
  • Use la herramienta de administración de IIS
    Esto le permite agregar un módulo mediante una GUI. Ir a Inicio>Ejecutar, escriba inetmgr y presione Entrar. Conéctese a localhost, busque la tarea Módulos y haga doble clic para abrirla. A continuación, haga clic en la tarea Agregar un módulo nativo en el panel derecho.
  • Instalación manual del módulo
    Instale el módulo manualmente agregándolo a la sección de configuración <system.webServer>/<globalModules> en el archivo de configuración applicationHost.config y agregue una referencia a él en la sección de configuración <system.webServer>/<modules> del mismo archivo para habilitarlo. Se recomienda usar una de las dos opciones anteriores para instalar el módulo en lugar de editar la configuración directamente.

La tarea está completa: hemos terminado de configurar el nuevo módulo nativo.

Resumen

En este tutorial, ha aprendido a desarrollar e implementar un módulo nativo personalizado mediante las nuevas API de extensibilidad nativas (C++). Consulte la información general sobre el desarrollo de código nativo para obtener más información sobre las API de servidor nativas (C++).

Para obtener información sobre cómo extender IIS mediante código administrado y .NET Framework, consulte Desarrollo de un módulo IIS con .NET. Para más información sobre la administración de módulos IIS, consulte las notas del producto de información general del módulo.