What are and how to use the ISAPI


Home Page | Comments | Articles | Faq | Documents | Search | Archive | Tales from the Machine Room | Contribute | Set language to:en it | Login/Register


Da quando il Web e' diventato molto popolare, sono stati fatti molti sforzi per rendere cio' che era fondamentalmente un modo per ottenere documenti statici in qualche cosa che permettesse una certa interazione tra l'utente ed il documento stesso.

Per una trattazione delle varie metodologie di programmazione si veda l'articolo corrispondente.

Per migliorare le possibilita' di interazione, dalla versione 2 di IIS, sono state introdotte le IISAPI.

In sostanza, si fornisce al programmatore la possibilita' di creare una DLL che richiami determinate funzioni all'interno del Server Web stesso e che sia richiamata dal Server Web in determinate circostanze, in questo modo la DLL e' in grado di ricevere e scambiare informazioni direttamente con il server.

Chiaramente questa soluzione e' molto piu' efficiente in termini di utilizzo della memoria e del processore del server, rende estremamente piu' semplice il passaggio di dati tra il server e la DLL dato che la stessa viene caricata in memoria ed eseguita nello spazio di memoria del server, il trasferimento di grossi blocchi di dati binari non e' un problema.

D'altra parte pero', questo comporta anche un serio problema di stabilita' del server: una DLL malfatta potrebbe causare il blocco del server o il crash dell'intero sistema. Inoltre lo sviluppo ed il test della DLL sono lunghi ed onerosi, dato che ogni volta che si fanno modifiche e' necessario fermare il Server Web, sostituire la DLL e quindi riavviare il Server. Farlo un una macchina che e' usata per altre funzioni oltre che lo sviluppo comporta un rallentamento dei servizi che e' inaccettabile in molte situazioni.

Fondamentalmente possono essere sviluppate due tipi di DLL da utilizzare con IIS: Filtri ed Estensioni.

Un Filtro e' una DLL che viene richiamata automaticamente all'avvio del Server e rimane sempre nella memoria del server stesso, la DLL riceve delle notifiche ogni volta che un certo tipo di file viene richiesto da un client al server, e puo' eseguire svariate operazioni in risposta alla richiesta.

Il tipo di filtro piu' semplice che si trova a disposizione e' un contatore, che viene notificato ogni volta che un utente richiede una pagina HTML e che tiene traccia del numero di richieste e di altre informazioni che sono desumibili dalla richiesta stessa (indirizzo IP, dominio...).

E' possibile verificare (ed eventualmente modificare) quali filtri sono installati su un Server controllando l'apposita chiave del Registro:


HKEY_LOCAL_MACHINE
	\System
	\CurrentControlSet
	\Services
	\W3SVC
	\Parameters
		Filter DLLs

Un'Estensione e' invece un tipo di DLL completamente diversa, che viene richiamata dal Server solo in risposta ad una esplicita richiesta da parte di un Client (di solito tramite un apposito Link previsto dall'autore della pagina HTML di origine), e che puo' eseguire differenti operazioni a seconda dei desideri del programmatore che l'ha realizzata.

La gestione di file .ASP e .ASA e' effettuata per l'appunto da una Estensione di IIS, la quale viene richiamata automaticamente dal IIS ogni volta che l'utente richiede un file con quell'estensione.

Quando IIS viene avviato, viene letto l'elenco dei filtri da installare, tali DLL sono caricate in memoria in sequenza ed ognuna viene inizializzata dal server mediante il richiamo della funzione GetFilterVersion(), questa deve essere implementata dal programmatore che ha realizzato la DLL.

Tipicamente, il filtro, durante l'inizializzazione fornisce al server le informazioni relative a:

Un tipico filtro potrebbe richiedere di essere informato quando il client richiede una pagina .HTM o .HTML, quando l'utente chiude la sessione (abbandona il sito quindi) e cose cosi'.

Il filtro puo' istruire il server per essere richiamato anche prima che il server fornisca la pagina HTML completa al client, in questo modo ha la possibilita' per operare dei cambiamenti alla pagina stessa prima che questa sia inviata completamente al client. E' in questo modo che i vari "contatori" inseriscono il conteggio sulla pagina Web.

Una volta che il filtro e' installato e funzionante, il server Web lo "informera'" degli eventi richiamando la funzione HttpFilterProc() che deve essere stata implementata dal programmatore, fornendo informazioni relative al documento richiamato o all'evento occorso.

In questo modo il filtro viene "attivato" in maniera assolutamente trasparente ed invisibile per l'utente stesso.

NOTA Per poter compilare il codice di questo esempio vi serve un compilatore C/C++ in grado di produrre un eseguibile per Windows con le relative librerie e .h. Quale compilatore voi usiate non e' troppo importante.

Un contatore e' fondamentalmente un filtro che viene richiamato quando l'utente richiede un documento HTML per tenere conto di quante volte un documento viene visionato, inoltre puo' essere notificato ogni qual volta un utente lascia il sito per avere un conteggio (non eccessivamente preciso) di quanti visitatori sono entrati, ed utilizza la notifica di "pagina pronta per essere spedita" per inserire un conteggio nella pagina prima che questa sia inviata al client.

Il mio (semplicissimo) contatore esegue queste tre operazioni richiedendo al server di essere richiamato utilizzando 3 codici:


SF_NOTIFY_URL_MAP
SF_NOTIFY_END_OF_NET_SESSION
SF_NOTIFY_SEND_RAW_DATA

Il primo per la richiesta delle pagine, il secondo per la conclusione della sessione dell'utente, il terzo prima dell'invio della pagina.

La funzione di inizializzazione del filtro e' definita come segue:


/*
* Inizializzazione del filtro.
* la struttura HTTP_FILTER_VERSION viene passata da 
* IIS e deve essere riempita prima di essere ritornata
*/
BOOL WINAPI GetFilterVersion (HTTP_FILTER_VERSION *fv)
{
   /* HTTP_FILTER_REVISION 
   e' una costante definita in httpfilt.h */
   fv->dwFilterVersion = HTTP_FILTER_REVISION;

   /* inserisco una descrizione per il contatore */  
   lstrcpyn( fv->lpszFilterDesc, "ContatoreScemoNumero1",
   SF_MAX_FILTER_DESC_LEN);
   
   /* imposto gli eventi di cui voglio sapere */
   fv->dwFlags = (SF_NOTIFY_URL_MAP         |
   SF_NOTIFY_SEND_RAW_DATA      |
   SF_NOTIFY_END_OF_NET_SESSION |
   SF_NOTIFY_ORDER_LOW          |
   SF_NOTIFY_NONSECURE_PORT);
   
   return TRUE;
}

SF_NOTIFY_ORDER_LOW indica ad IIS di caricare il filtro con una bassa priorita', questo evita che un problema nella DLL inchiodi tutto il server.

SF_NOTIFY_NONSECURE_PORT e' un flag generico, la documentazione MS non specifica cosa faccia ma specifica che e' meglio usarlo sempre.

Ogni volta che IIS deve richiamare il filtro, la funzione HttpFilterProc() viene richiamata, questa funzione e' il "nucleo" dell'intero filtro. Praticamente la funzione riceve un parametro nel quale viene specificato cosa e' successo. La funzione deve interpretare il parametro e prendere i necessari provvedimenti per gestire l'evento, quindi puo' ritornare un valore che indica al server di richiamare di nuovo il filtro quando succede qualche cosa d'altro oppure no.


/*
Processa le operazioni del filtro
Questa funzione viene chiamata da IIS ogni volta 
che si verifica un evento di quelli indicati dalla 
GetFilterVersion()

Gli eventi gestiti in questa procedura sono: 
la richiesta di un documento, l'abbandono del sito 
da parte dell'utente e l'invio di un documento al
browser dell'utente.

*/
DWORD WINAPI HttpFilterProc (HTTP_FILTER_CONTEXT *fc,
	DWORD notificationType,
	VOID  *notification)
{
   
   HTTP_FILTER_URL_MAP  *urlMap;     /* documento richiesto */
   HTTP_FILTER_RAW_DATA *rawData;    /* pagina pronta */
   
   switch (notificationType)
   {
      case SF_NOTIFY_URL_MAP:
         /*
         l'utente ha richiesto un documento, incremento
         il contatore di richieste per quel documento
         */
         urlMap = notification;
         fc->pFilterContext = 
         (PVOID) ContaEventi( urlMap->pszPhysicalPath );
         break;
         
      case SF_NOTIFY_SEND_RAW_DATA:
         /*
         Stiamo per inviare una pagina, devo aggiornare il 
         contatore sulla pagina prima che questa sia 
         spedita al client
         */
         rawData = notification;
         ScriviConteggio( (DWORD) fc->pFilterContext, 
         rawData->pvInData,
         rawData->cbInData);
         break;
         
      case SF_NOTIFY_END_OF_NET_SESSION:
         /*
         L'utente se ne' andato, aumento il contatore di 
         utenti che hanno visitato il sito
         */
         ContaEventi( "FINE_SESSIONI" );
         break;
         
      default:
         /* non gestisco altri eventi */
         break;
   }
   
   /* voglio ricevere altri eventi ? SIIIII!!! */
   return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

Per tenere il conto degli eventi dobbiamo appoggiarci ad un file esterno, il nostro file puo' essere un database o un semplice file ASCII, data la semplicita' del filtro ed il supporto insito nelle API di Windows, utilizzero' un semplice file .INI per tenere traccia degli eventi.

Il nostro file .INI puo' trovarsi ovunque, abbiamo due scelte: lo mettiamo assieme alla DLL ed accediamo usando il path relativo, oppure lo mettiamo in una directory specifica ed usiamo un path assoluto. Nel mio caso utilizzo il path relativo.

La funzione che "tiene il conto" risulta molto semplice:


/*
Tiene il conto degli eventi che si verificano nel nostro
sito Web. Il conteggio viene scritto in un file .INI denominato 
"CONTATORE.INI" che si trova nella stessa directory della DLL. 
Gli eventi sono inseriti nella sezione del file denominata 
[EVENTI].
*/
DWORD ContaEventi(char *evento )
{
   UINT conteggio;
   char stringa[11];
   char *nomeIni = "CONTATORE.INI";
   char *sezioneEventi = "EVENTI";
   
   /* leggo il conteggio attuale */
   conteggio = GetPrivateProfileInt( sezioneEventi,
   evento,
   0,
   nomeIni );
   
   /* incremento il conteggio e mi preparo a riscriverlo */
   wsprintf( stringa, "%u", ++conteggio );
   
   /* scrivo il conteggio nel file */
   WritePrivateProfileString( sezioneEventi, evento, stringa, nomeIni );
   
   /* ritorno il conteggio attuale */
   return conteggio;

}

La GetPrivateProfileInt() consente di recuperare dal file .INI il contatore attuale associato all'evento verificatosi o al documento richiesto.

Se l'evento non esiste la funzione ritorna 0. Questo ci consente di aggiungere documenti al nostro sito e vederli aggiunti automaticamente al conteggio senza dover modificare nulla.

La WritePrivateProfileString() scrive nel file il numero aggiornato.

Per inserire il conteggio nella pagina dobbiamo intercettare l'evento SF_NOTIFY_SEND_RAW_DATA, quindi andare nel blocco di dati "raw" che ci sono spediti da IIS nella struttura HTTP_FILTER_CONTEXT e scrivere il numero da qualche parte nella pagina HTML.

Questo comporta pero' qualche problema: dobbiamo scrivere il numero in un certo punto della pagina per evitare casini, quindi dobbiamo mettere nella nostra pagina un riferimento di dove vogliamo che il contatore scriva il numero, ma contemporaneamente questo riferimento non puo' essere qualche cosa che possa combinare guai al codice HTML e che possa "passare" se usiamo un editor che verifica la validita' del codice HTML (tipo HoTMetaL).

Nel mio caso ho deciso di utilizzare i "commenti" HTML, indicati con la sequenza . Per la precisione la posizione ove mettere il conteggio, sara' la sequenza: , che occupando 10 caratteri mi permette di scrivere il testo in modo chiaro senza problemi.

Il codice che inserisce il conteggio nella pagina dovra' cercare la sequenza indicata nel testo ed effettuare la sostituzione:


/*
Inserisce il contatore nella pagina HTML.
La funzione cerca la sequenza 
<!--!!!--> e la sostituisce con 
il valore indicato come conteggio.
numDati indica il numero di caratteri 
che compongono la pagina.
*/
ScriviConteggio( DWORD contatore, void *dati, 
DWORD numDati )
{
   char  *pagina;
   DWORD i, j;
   char *indicatore = "<!--!!!-->";
   
   /* trasformo i dati in caratteri */  
   pagina = dati;
   
   /* ciclo e mi leggo tutti i dati */
   i = 0;
   while (i != numDati) {
      /* verifica se quello che sto' leggendo e' 
      la sequenza "indicatore" */
      j = 0;
      while( ( (i+j) != numDati) && indicatore[j] && 
         (*(pagina+i+j) == indicatore[j])) {
         j++;
      }
   
      /* ho trovato l'indicatore ? */
      if( indicatore[j] ) {
         /* no */
         i++;
      } else {
         /* SI, ficca dentro il contatore */
         FormattaContatore( contatore, pagina+i, j);
         i += j;
      }
   }
}
   
/*
Scrive il contatore nella stringa formattandolo in
modo acconcio
*/
void FormattaContatore( DWORD numero, char  *stringa,
   DWORD caratteri)
{
   char cNull = '0';
   DWORD n, i;
   
   n = numero;
   i = caratteri;
   
   /* scrivo i singoli caratteri */
   while (i) {
      *(stringa+(--i)) = (LOBYTE (LOWORD (n % 10))) + cNull;
      if( !(n /= 10) ) {
         cNull = ' ';
      }
   }
}

Dato che il numero contatore e' memorizzato come un DWORD, devo trasformarlo prima di poterlo scrivere. Questo e' fatto dalla FormattaContatore(), in modo che il numero viene scritto sempre in modo leggibile.

La funzione che scrive il contatore sostituisce tutte le sequenze che trova nella pagina, questo significa che possiamo mettere piu' di un contatore sulla stessa pagina.

Fondamentalmente il nostro contatore e' completo, basta aggiungere le necessarie definizioni di esportazione e compilare il tutto (vedere il codice completo piu' sotto).

L'installazione della DLL invece e' qualche cosa di abbastanza rozzo: occorre fermare il servizio IIS, quindi copiare la DLL nella directory che piu' vi aggrada, a questo punto utilizziamo REGEDIT per andare ad aggiungere la nostra DLL (con il path completo) all'elenco presente nella chiave:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SVC\Parameters\Filter DLLs
usando la virgola come separatore.

Riavviando il servizio, un nuovo file denominato CONTATORE.INI dovrebbe apparire nella stessa directory della DLL, all'interno dello stesso file dovremmo trovare:


[EVENTI]
GetFilterVersion:1

Il che ci informa che la DLL e' stata caricata ed avviata da IIS.

A questo punto, proviamo a fare qualche richiesta al nostro server, nel nostro file .INI dovremmo trovare un'elenco dei documenti visitati con l'indicazione del numero di volte che abbiamo visionato ogni documento.

Per verificare il funzionamento completo dobbiamo costruire una semplice pagina HTML che contenga il nostro "indicatore", e caricarla attraverso IIS. La sequenza verra' sostituita dal numero di volte che la pagina e' stata caricata.

Cosi' come e' stato implementato il nostro contatore funziona, ma e' una implementazione minima. Utilizzando le informazioni contenute nella struttura HTTP_FILTER_CONTEXT passataci da IIS, potremmo aggiungere una serie di altre informazioni utili, quali la provenienza del visitatore, il browser usato ed altre caratteristiche interessanti.

Lo scopo di questo articolo comunque non era quello di provvedere un contatore-killer che sostituira' tutti gli altri, ma quello di scacciare un po' di ombre sull'utilizzo di alcune funzioni molto oscure ma molto utili di IIS.

Qui trovate il codice completo della DLL separato in due file: contatore.c e contatore.def. Il file .DEF e' il file di definizione che vi serve per compilare la DLL. Il codice e' stato testato con Microsoft Visual C++ 6.0 e con Borland C++ 5.5. Suppongo che possa essere facilmente compilato anche con altri compilatori/SDK.

Standard CGI: http://hoohoo.ncsa.uiuc.edu/cgi/
Standard WinCGI: http://website.ora.com/wsdocs/32demo/windows-cgi.html
Documentazione ed SDK per le ISAPI reperibile presso: http://www.microsoft.com/workshop/prog/sdk/sdk.htm e http://www.microsoft.com/msdownload/activex.htm


Comments are added when and more important if I have the time to review them and after removing Spam, Crap, Phishing and the like. So don't hold your breath. And if your comment doesn't appear, is probably becuase it wasn't worth it.

No messages this document does not accept new posts

Previous Next

Davide Bianchi, works as Unix/Linux administrator for an hosting provider in The Netherlands.

Do you want to contribute? read how.  
 


This site is made by me with blood, sweat and gunpowder, if you want to republish or redistribute any part of it, please drop me (or the author of the article if is not me) a mail.


This site was composed with VIM, now is composed with VIM and the (in)famous CMS FdT.

This site isn't optimized for vision with any specific browser, nor it requires special fonts or resolution.
You're free to see it as you wish.

Web Interoperability Pleadge Support This Project
Powered By Gojira