Desenvolver um módulo C\C++ nativo para o IIS 7.0

por Mike Volodarsky

Introdução

O IIS 7.0 e versões superiores permite estender o servidor por módulos desenvolvidos de duas maneiras:

  • Usando código gerenciado e as APIs de extensibilidade do servidor ASP.NET
  • Usando código nativo e as APIs de extensibilidade do servidor nativo do IIS

Ao contrário das versões anteriores do IIS, a maioria dos cenários de extensibilidade do servidor não necessita de desenvolvimento de código nativo (C++) e pode ser acomodada usando código gerenciado e as APIs do ASP.NET. O uso do ASP.NET para estender o servidor permite reduzir drasticamente o tempo de desenvolvimento e aproveitar a funcionalidade avançada do ASP.NET e do .NET Framework. Para saber mais sobre como estender o IIS com o ASP.NET, confira Desenvolver um módulo do IIS com o .NET.

O IIS também fornece uma API nativa do servidor principal (C++), que substitui a API de extensão e filtro ISAPI das versões anteriores do IIS. Se você tiver requisitos específicos que exigem o desenvolvimento de código nativo ou quiser converter seus componentes ISAPI nativos existentes, use essa API para criar os componentes do servidor. A nova API nativa do servidor oferece desenvolvimento orientado a objetos com um modelo de objeto intuitivo, fornece mais controle sobre o processamento de solicitações e usa padrões de design mais simples para ajudar você a escrever um código robusto.

Este passo a passo inclui as seguintes tarefas:

  • Desenvolver um módulo nativo usando a API nativa do servidor (C++)
  • Implantar um módulo nativo no servidor

Para compilar o módulo, é preciso instalar o SDK da Plataforma que contém os arquivos de cabeçalho do IIS. O SDK da Plataforma Windows Vista mais recente está disponível aqui.

Para usar o SDK da Plataforma com o Visual Studio 2005, é preciso registrar o SDK. Depois de instalar o SDK, faça isso acessando Iniciar > Programas > SDK do Microsoft Windows > Registro do Visual Studio > Registrar Diretórios do SDK do Windows com o Visual Studio.

O código-fonte deste módulo está disponível no Exemplo de Módulo Nativo do Visual Studio IIS7.

Desenvolver um módulo nativo

Nesta tarefa, examinamos o desenvolvimento de um módulo nativo usando a nova API nativa do servidor (C++). Um módulo nativo é uma DLL do Windows que contém o seguinte:

  • Função RegisterModule exportada. Essa função é responsável por criar um alocador de módulo e registrar o módulo para um ou mais eventos do servidor.
  • Implementação da classe de módulo herdada da classe base CHttpModule. Essa classe fornece a funcionalidade principal do seu módulo.
  • Implementação da classe do alocador de módulo que implementa a interface IHttpModuleFactory. A classe é responsável por criar instâncias do módulo.

Observação

Em alguns casos, você também pode implementar a interface IGlobalModule, a fim de estender algumas das funcionalidades do servidor não relacionadas ao processamento de solicitações. Esse é um tópico avançado não abordado neste passo a passo.

Seu módulo nativo apresenta o seguinte ciclo de vida:

  1. Quando o processo de trabalho do servidor for iniciado, carregará a DLL que contém o módulo e invocará a função RegisterModule exportada. Nessa função, você:

    a. Cria o alocador de módulo.
    b. Registra o alocador de módulo dos eventos do pipeline de solicitação implementados pelo módulo.

  2. Quando uma solicitação chega, o servidor:

    a. Cria uma instância da classe do módulo usando o alocador fornecido.
    b. Chama o método de manipulador de eventos apropriado na instância do módulo para cada um dos eventos de solicitação para os quais você se registrou.
    c. Elimina a instância do módulo ao final do processamento da solicitação.

Agora, para compilá-lo.

O código-fonte completo do módulo está disponível no Exemplo de Módulo Nativo do Visual Studio IIS7. As etapas abaixo são as mais importantes para o desenvolvimento do módulo e não incluem suporte ao código nem tratamento de erro.

Implemente a função RegisterModule que o servidor invoca quando a DLL do módulo é carregada. A assinatura dela e o restante da API nativa são definidos no arquivo de cabeçalho httpserv.h, que faz parte do SDK da Plataforma (se você não tiver o SDK da Plataforma, confira a Introdução para obter informações sobre como obtê-lo):

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

O RegisterModule

É preciso realizar três tarefas básicas dentro do RegisterModule:

Salvar o estado global

Armazenaremos a instância do servidor global e a ID de contexto do módulo para uso posterior em variáveis globais. Embora este exemplo não use essas informações, é útil para muitos módulos salvar e usar posteriormente durante o processamento da solicitação. A interface IHttpServer fornece acesso a muitas funções de servidor, como abrir arquivos e acessar o cache. A ID de contexto do módulo é usada para associar o estado do módulo personalizado a vários objetos de servidor, como solicitação e aplicativo.

Criar o alocador de módulo

Implementaremos nossa classe de alocador, CMyHttpModuleFactory, mais adiante neste passo a passo. Esse alocador é responsável pela fabricação das instâncias do módulo para cada solicitação.

Registrar o alocador de módulo para os eventos de processamento de solicitações desejados

O registro é realizado usando o método SetRequestNotificatons, que instrui o servidor: a criar a instância do módulo para cada solicitação usando o alocador especificado e a invocar os manipuladores de eventos apropriados nela para cada uma das fases especificadas de processamento de solicitações.

Nesse caso, estamos interessados apenas na fase RQ_ACQUIRE_REQUEST_STATE. A lista completa das fases que formam o pipeline de processamento de solicitações é definida em 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

Além disso, você pode assinar vários eventos não determinísticos que podem ocorrer durante o processamento de solicitações devido a ações que outros módulos executam, como liberar a resposta para o 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 nossa implementação do RegisterModule possa ser acessada pelo servidor, devemos exportá-la. Use um arquivo .DEF que contém a palavra-chave EXPORTS para exportar a função RegisterModule.

Depois, implemente a classe do alocador de módulo:

mymodulefactory.h:

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

   virtual void Terminate()
    {
    }

};

O alocador de módulo implementa a interface IHttpModuleFactory e serve para criar instâncias do módulo em cada solicitação.

O servidor chama o método GetHttpModule no início de cada solicitação para obter a instância do módulo a ser usada nessa solicitação. A implementação simplesmente retorna uma nova instância da classe do módulo, CMyHttpModule, que implementaremos em seguida. Como veremos em instantes, isso nos permite armazenar facilmente o estado da solicitação sem nos preocuparmos com o acesso thread-safe, pois o servidor sempre cria e usa uma nova instância do módulo em cada solicitação.

Implementações de alocadores mais avançadas podem optar por usar um padrão singleton em vez de criar uma instância todas as vezes ou usar a interface IModuleAllocator fornecida para alocar a memória do módulo no pool de solicitações. Esses padrões avançados não são abordados neste passo a passo.

O método Terminate é chamado pelo servidor quando o processo de trabalho é desligado para executar a limpeza final do módulo. Se você inicializar qualquer estado global em RegisterModule, implemente a limpeza nesse método.

Implementar a classe do módulo

Essa classe é responsável por fornecer a funcionalidade principal do módulo durante um ou mais eventos do servidor:

myhttpmodule.h:

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

A classe do módulo é herdada da classe base CHttpModule, que define um método de manipulador de eventos para cada um dos eventos de servidor discutidos anteriormente. Quando o pipeline de processamento de solicitações executa cada evento, invoca o método de manipulador de eventos associado em cada uma das instâncias do módulo registradas para o evento.

Cada método de manipulador de eventos tem a seguinte assinatura:

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

A interface IHttpContext fornece acesso ao objeto de contexto da solicitação, que pode ser usado para executar tarefas de processamento de solicitações, como inspecionar a solicitação e manipular a resposta.

A interface IHttpEventProvider é substituída por uma interface mais específica em cada um dos eventos que fornecem funcionalidade específica para o módulo. Por exemplo, o manipulador de eventos OnAuthenticateRequest recebe a interface IAuthenticationProvider que permite que o módulo defina o usuário autenticado.

O retorno de cada método do manipulador de eventos é um dos valores da enumeração REQUEST_NOTIFICATION_STATUS. Você precisará retornar RQ_NOTIFICATION_CONTINUE se o módulo executar a tarefa com êxito. O pipeline deverá continuar a execução.

Se ocorrer uma falha e você quiser anular o processamento da solicitação com um erro, defina o status do erro e retorne RQ_NOTIFICATION_FINISH_REQUEST. O retorno RQ_NOTIFICATION_PENDING permite que você execute o trabalho de maneira assíncrona e libere o thread processando a solicitação para que ela seja reutilizada em outra solicitação. A execução assíncrona não é abordada neste artigo.

Nossa classe de módulo substitui o método de manipulador de eventos OnAcquireRequestState. Para fornecer funcionalidade em qualquer uma das fases do pipeline, a classe de módulo deve substituir o respectivo método de manipulador de eventos. Se você se registrar para um evento no RegisterModule, mas não substituir o método de manipulador de eventos apropriado na classe de módulo, ocorrerá a falha do módulo no runtime (e disparará uma instrução de tempo de depuração se compilado no modo de depuração). Tenha cuidado e verifique se a assinatura do método de substituição é exatamente equivalente ao método da classe base de CHttpModule que você está substituindo.

Compilar o módulo

Lembre-se: você precisa do SDK da Plataforma para a compilação. Confira a Introdução para obter mais informações sobre como obtê-lo e habilitar o Visual Studio para fazer referência a ele.

Implantar um módulo nativo

Depois de compilar o módulo, você precisa implantá-lo no servidor. Compile o módulo e copie IIS7NativeModule.dll (e o arquivo de símbolos de depuração IIS7NativeModule.pdb, se quiser) para qualquer local no computador que executa o IIS.

Os módulos nativos, ao contrário dos módulos gerenciados que podem ser adicionados diretamente ao aplicativo, precisam ser instalados no servidor. Isso requer privilégios administrativos.

Para instalar um módulo nativo, há várias opções:

  • Use a ferramenta de linha de comando APPCMD.EXE
    O APPCMD simplifica a instalação do módulo. Acesse Iniciar>Programas>Acessórios, clique com o botão direito do mouse no Prompt de Linha de Comando e escolha Executar como Administrador. Na janela da linha de comando, execute o seguinte:
    %systemroot%\system32\inetsrv\appcmd.exe install module /name:MyModule /image:[FULL\_PATH\_TO\_DLL]
    Em que [FULL_PATH_TO_DLL] é o caminho completo para a DLL compilada que contém o módulo que você acabou de criar.
  • Use a ferramenta de administração do IIS
    Ela permite adicionar um módulo usando uma GUI. Acesse Iniciar>Executar, digite inetmgr e pressione Enter. Conecte-se ao localhost, localize a tarefa Módulos e clique duas vezes para abri-la. Depois, clique na tarefa Adicionar um Módulo Nativo no painel direito.
  • Instalar o módulo manualmente
    Para instalar o módulo manualmente, adicione-o à seção de configuração <system.webServer>/<globalModules> no arquivo de configuração applicationHost.config e adicione uma referência a ele na seção de configuração <system.webServer>/<modules> no mesmo arquivo para habilitá-lo. Recomendamos que você use uma das duas opções anteriores para instalar o módulo em vez de editar a configuração diretamente.

A tarefa está concluída. Terminamos de configurar o novo módulo nativo.

Resumo

Neste passo a passo, você aprendeu a desenvolver e implantar um módulo nativo personalizado usando as novas APIs de extensibilidade nativas (C++). Confira a Visão geral do desenvolvimento de código nativo para saber mais sobre as APIs nativas de servidor (C++).

Para saber mais sobre como estender o IIS usando código gerenciado e o .NET Framework, confira Desenvolver um módulo do IIS com o .NET. Para saber mais sobre como gerenciar os módulos do IIS, confira o white paper de visão geral do módulo.