Exemplo: Módulo de marca d'água da imagem

por Fabio Yeon

Este é um exemplo de como escrever um módulo nativo (C++) que inserirá dinamicamente uma marca d'água configurada pelo usuário em uma imagem que está sendo servida, bem como estender a configuração e ferramenta de interface de usuário do inetmgr para fornecer uma administração fácil do novo módulo.

O módulo de marca d'água tem os seguintes recursos:

  • Ele pode ser habilitado/desabilitado em qualquer nível de configuração (ou seja, site, aplicativo, diretório virtual etc.
  • Ele pode inserir marca d'água nos formatos de imagem JPG, GIF e PNG.
  • A imagem de marca d'água pode estar nos formatos JPG, GIF ou PNG. A imagem de marca d'água não precisa ser do mesmo tipo que a imagem a ser marcada (ou seja, uma imagem de marca d'água GIF pode ser usada para marcar uma imagem PNG).
  • É possível configurar o local da imagem da marca d'água ("superior esquerdo", "superior direito", "inferior esquerdo", "inferior direito", "centro", bem como as opções de "mosaico" e "esticar"). Apenas a opção "esticar" alterará/modificará a imagem da marca d'água, se necessário.
  • Também é possível selecionar o nível de transparência da imagem da marca d'água, de 0 a 100%.

Para compilar o exemplo, você deve instalar o SDK da Plataforma para Windows Vista ou o Windows Server 2008. O arquivo de projeto incluído no exemplo pode ser carregado no Visual Studio 2005 ou 2008.

O código-fonte desses exemplos está disponível aqui.

Módulo de marca d'água

O primeiro componente deste exemplo é o módulo de marca d'água em si. É um módulo C++ nativo que observa como as solicitações são atendidas e, se o tipo MIME da solicitação indicar que é uma imagem, aplicará dinamicamente uma marca d'água configurada pelo usuário à imagem e substituirá a imagem de saída. Tudo isso é feito de forma transparente em um módulo executado após o manipulador de solicitação. Para ilustrar:

Diagrama que mostra uma árvore de decisão do Manipulador de Solicitações para o marcador de água da imagem. Se houver uma imagem do Manipulador de Solicitações, uma nova imagem será enviada, caso contrário, a imagem original será enviada.

  1. A lógica do código é bastante simples:
  2. Quando o módulo é carregado pela primeira vez, ele se registra no evento pós-RQ_EXECUTE_REQUEST_HANDLER, que indica ao sistema que ele deseja ser notificado imediatamente após o manipulador de solicitações ser executado.
  3. Agora, quando o evento pós-RQ_EXECUTE_REQUEST_HANDLER for acionado, ele recuperará a configuração de seu caminho. Se não estiver habilitado, não fará mais nada ao pedido e fiança.
  4. Se estiver habilitado, ele verificará o cabeçalho de resposta "Tipo de Conteúdo" e verá se ele é um tipo de imagem. Se não for, vai desistir.
  5. Se for uma imagem, ela verá se a resposta é um buffer ou um identificador de arquivo. Se for posterior, ele carregará a imagem na memória, aplicará a marca d'água com base nas configurações configuradas pelo usuário (ou seja, arquivo de imagem, posição, transparência), salvará a imagem resultante em um buffer na memória, substituirá os dados de resposta pelo buffer na memória e retornará.
  6. O buffer na memória será liberado no método "Dispose" do módulo.

O uso das classes CImage da ATL torna o carregamento e o processamento da imagem muito simples, especialmente porque ela pode manipular vários formatos de imagem facilmente.

Agora, há algumas ressalvas neste exemplo e devem ser consideradas, especialmente se desejar usá-la em um ambiente de produção "real":

  • O código só funciona para tipos de imagem em que a resposta está em um identificador de arquivo. O objeto HTTP_RESPONSE pode conter a imagem em uma série de buffers. Um exercício interessante seria criar uma implementação IStream na memória que encapsula os buffers no objeto de resposta (cuidado com as várias respostas de buffer!).
  • O módulo não faz nenhuma tentativa de modificar ou atualizar o cabeçalho de resposta "ETag". Isso significa que a imagem com marca d'água resultante será considerada a imagem "real" no que diz respeito ao cliente (como deveria), mas também pode confundir a lógica de cache do cliente. Uma implementação correta disso teria que modificar a "ETag" para levar em conta a configuração da imagem marcada com marca d'água. Por exemplo, na implementação atual, se você marcasse uma imagem com "WatermarkFileA" na posição "UpperLeft" com uma transparência de "50%", e depois fizesse uma solicitação com um navegador (ou seja, Internet Explorer), o IE armazenaria essa imagem em cache com base nos vários cabeçalhos de resposta. Se você alterasse a configuração do módulo de marca d'água (por exemplo, mudar a imagem, a posição ou a transparência) e solicitasse novamente o arquivo (pressionando "F5"), como a imagem foi armazenada em cache pelo IE, em vez de solicitar a imagem novamente, ele simplesmente faria uma solicitação "HEAD", enviando as informações que possui sobre a imagem. O manipulador de solicitação, sem saber que a imagem foi alterada, responderá que a resposta enviada anteriormente ainda é válida e encerrará o processamento da solicitação. Infelizmente, nesse caso, o módulo de marca d'água não terá a chance de executar e marcar novamente a imagem com sua nova configuração, portanto, o cliente continuará mostrando a imagem antiga com marca d'água. Uma correção adequada para isso faria com que o módulo de marca d'água incorporasse sua configuração na "ETag" da resposta, de modo que qualquer alteração na imagem ou no módulo de marca d'água causasse a invalidação de imagens armazenadas em cache pelo cliente e o reprocessamento delas.

Configuração

A configuração do módulo de marca d'água é feita por meio de uma nova seção no namespace "system.webServer". O arquivo de esquema é o seguinte:

<configSchema> 
 <sectionSchema name="system.webServer/watermark"> 
  <attribute name="enabled" type="bool" defaultValue="false" /> 
  <attribute name="watermarkImage" type="string" /> 
  <attribute name="transparency" type="uint" defaultValue="50" validationType="integerRange" validationParameter="0,100" /> 
  <attribute name="position" type="enum" defaultValue="LowerRight" > 
    <enum name="UpperLeft" value="0" /> 
    <enum name="UpperRight" value="1" /> 
    <enum name="LowerLeft" value="2" /> 
    <enum name="LowerRight" value="3" /> 
    <enum name="Center" value="4" /> 
    <enum name="Stretch" value="5" /> 
    <enum name="Tile" value="6" /> 
  </attribute> 
 </sectionSchema> 
</configSchema>

O arquivo "watermark.xml" deve ser colocado no diretório %windir%\system32\inetsrv\config\schema para que ele entre em vigor, bem como adicionar a definição de seção no arquivo "applicationhost.config", no namespace "system.webServer".

<section name="Watermark" overrideModeDefault="Allow" />

Para usar o módulo, é necessário instalar o módulo na lista de módulos global, "system.webServer\globalModules":

<add name="WatermarkModule" image="c:\Watermark\Watermark.dll" />

E para a lista de módulos do aplicativo, "system.webServer\modules":

<add name="WatermarkModule" />

Inetmgr

Junto com o exemplo de módulo, há um conjunto de classes gerenciadas que são plug-ins de gerenciamento para a nova ferramenta de administração de interface do usuário "Inetmgr". Há várias outras documentações sobre como gravar e estender o novo "Inetmgr", que estão disponíveis aqui. Em suma, para usá-las, é necessário compilar, adicionar as dlls ao GAC (Cache de Assembly Global) e adicionar a seguinte configuração ao %windir%\system32\inetsrv\config\administration.config arquivo:

<Na coleção moduleProviders>, adicione a seguinte entrada:

<add name="Watermark" type="WatermarkServer.WatermarkModuleProvider, Watermarkserver, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5f6f8f3f74d67fe4" />

E adicione a seguinte linha à <modules> coleção:

<add name="Watermark" />

Reinicie a ferramenta e um novo ícone deve estar disponível em seu site.