Internet Server Application Programming Interface
However, you should note that ISAPI modules are loaded directly into the server and run as part of the server process, this does have security and reliability implications. For this reason ISAPI should be seen as a platform to build high-performance applications with, and not entirely as a replacement for other alternatives such as CGI.
The ISAPI specification was designed by Microsoft and Process Software, and is quickly becoming the accepted standard for web server add ons. Microsoft publishes both an overview and a complete ISAPI reference document at http://msdn.microsoft.com. Another useful site is The ISAPI developers site.
The aim of this document is to supplement that information with a gentle introduction with ISAPI along with information on how to integrate ISAPI applications into the Zeus Server.
The module also exports a function called HttpExtensionProc that is called when the extension is requested by the web server. Here is a simple HttpExtensionProc function that increments a counter and returns it./* Import the ISAPI constants and type definitions */ #include "httpext.h" BOOL WINAPI GetExtensionVersion( HSE_VERSION_INFO *pVer ) { pVer->dwExtensionVersion = HSE_VERSION_MAJOR; strncpy(pVer->lpszExtensionDesc, "A simple page counter", HSE_MAX_EXT_DLL_NAME_LEN); return TRUE; }
The file then needs to be compiled as a shared library, and places into a directory that is marked as an ISAPI alias directory. If the file was installed as '/isapi/counter.api' then the server will load in the module, and then run the HttpExtensionProc which will send the output to the browser.int hits=0; DWORD WINAPI HttpExtensionProc( LPEXTENSION_CONTROL_BLOCK ecb ) { char *header = "Content-Type: text/html"; int headerlen = strlen( header ); char msg[256]; int msglen; /* use a server support function to write out a header with our additional header information */ ecb->ServerSupportFunction( ecb->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, 0, &headerlen, (DWORD *)header ); /* write out the number of accesses */ sprintf( msg, "This page has been accessed %d times", ++hits ); msglen = strlen( msg ); ecb->WriteClient( ecb->ConnID, msg, &msglen,0 ); /* return, indicating success */ return 0; }
This example is quite trivial, but it demonstrates the basic elements of the interface between the ISAPI module and the application.
The server contains several hooks at different stages of processing a request. An ISAPI filter, can request to be notified at each of these points, where it will be passed information about the current request, and it can alter connection settings or perform operations through callback functions. The following ISAPI hooks exist:
As with extensions, there is a GetFilterVersion function that is called when the module is loaded.
The actual function that gets notified on the ISAPI hook should be called HttpFilterProc. In this cookie example, we get the url and cookie from the processed headers. If there is a cookie set we log it, along with the url to a log file. If no cookie is set, we generate a random cookie, and issue it to the client.#include "httpfilt.h" #include <unistd.h> #include <fcntl.h> #include <string.h> BOOL WINAPI GetFilterVersion( HTTP_FILTER_VERSION *pVer ) { pVer->dwFilterVersion = HTTP_FILTER_REVISION; strncpy( pVer->lpszFilterDesc, "A Cookie Filter", SF_MAX_FILTER_DESC_LEN ); /* Ask to be notified, when the headers have been processed */ pVer->dwFlags = SF_NOTIFY_PREPROC_HEADERS; return TRUE; }
void RandomBytes( char *buffer, int count ) { /* fill buffer with count random digits and terminate */ int i=0; for( i=0; i<count; i++ ) buffer[i] = '0' + (rand() % 10); buffer[i]=0; } DWORD WINAPI HttpFilterProc( PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, PHTTP_FILTER_PREPROC_HEADERS headers ) { char cookie[256], url[256]; int cookielen = 256, urllen=256; /* get header information */ headers->GetHeader( pfc, "Cookie:", cookie, &cookielen ); headers->GetHeader( pfc, "url:", url, &urllen ); if( cookielen > 1 ) { /* we have a cookie, log it */ int fd = open( "/tmp/cookie.log", O_WRONLY|O_CREAT|O_APPEND, 0755 ); if( fd != -1 ) { char outbuff[514]; sprintf( outbuff, "%s %s\n", cookie, url ); write( fd, outbuff, strlen( outbuff ) ); close( fd ); } } else { /* Set a cookie header with a random cookie */ char msg[256]; RandomBytes( cookie, 16 ); sprintf( msg, "Set-Cookie: %s\r\n", cookie ); pfc->AddResponseHeaders( pfc, msg, 0 ); } /* return, instructing the server to notify the next module */ return SF_STATUS_REQ_NEXT_NOTIFICATION; }
First we need a GetFilterVersion function. This time asking for notification on the authentication hook.
Now we need to write an HttpFilterProc to do the work. A Denied() function is given that actually sends the necessary HTTP headers back to the client on a failure along with an explanation as to why access was denied. On sending back a 401 response, the browser should pop up a dialog box asking the user to log in.#include <string.h> #include "httpfilt.h" BOOL WINAPI GetFilterVersion( HTTP_FILTER_VERSION *pVer ) { pVer->dwFilterVersion = HTTP_FILTER_REVISION; strncpy(pVer->lpszFilterDesc, "Basic auth filter", SF_MAX_FILTER_DESC_LEN); pVer->dwFlags = SF_NOTIFY_AUTHENTICATION; return 1; }
void Denied( PHTTP_FILTER_CONTEXT pfc, char *msg ) { int l = strlen( msg ); pfc->ServerSupportFunction( pfc, SF_REQ_SEND_RESPONSE_HEADER, (PVOID) "401 Permission Denied", (LPDWORD) "WWW-Authenticate: Basic realm=\"foo\"\r\n", 0); pfc->WriteClient( pfc, msg, &l, 0 ); } DWORD WINAPI HttpFilterProc( PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, PHTTP_FILTER_AUTHENT auth ) { if( auth->pszUser[0] == 0) { Denied( pfc, "No user/password given" ); return SF_STATUS_REQ_FINISHED; } if( strcmp( auth->pszUser, "fred" ) ) { Denied( pfc, "Unknown user" ); return SF_STATUS_REQ_FINISHED; } if( strcmp( auth->pszPassword,"bloggs") ) { Denied( pfc, "Wrong Password" ); return SF_STATUS_REQ_FINISHED; } return SF_STATUS_REQ_NEXT_NOTIFICATION; }
Which tells the server, English if possible, if not French, or as a last resort German. Here is a sample ISAPI application that when asked for (for example) fred.html will try and load fred.fr.html if the favourite language is French. For simplicity, we will cheat slightly, and only take the first language in the Accept Language referenced header. As always we need to write a GetFilterVersion function. This time we'll use the SF_NOTIFY_URL_MAP hook.Accept Language: en=0.9, fr=0.4, de=0.1
Now the HttpFilterProc will receive a PHTTP_FILTER_URL_MAP structure as its third argument.#include "httpfilt.h" #include#include BOOL WINAPI GetFilterVersion( HTTP_FILTER_VERSION *pVer ) { pVer->dwFilterVersion = HTTP_FILTER_REVISION; strncpy(pVer->lpszFilterDesc, "Language Negotiation Filter", SF_MAX_FILTER_DESC_LEN); pVer->dwFlags = SF_NOTIFY_URL_MAP; return 1; }
DWORD WINAPI HttpFilterProc( PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, PHTTP_FILTER_URL_MAP map ) { /* Get "Accept-Language" header using ALL_HTTP */ char *lang; char buffer[4096]; int size = sizeof(buffer); pfc->GetServerVariable(pfc, "ALL_HTTP", buffer, &size); /* rip out the language here */ lang = strstr(buffer, "HTTP_ACCEPT_LANGUAGE"); if( lang ) { char *p; lang = lang+22; /* skip "Accept Language: " */ for(p=lang; isalpha(*p); p++); *p=0; /* terminate after first language */ { /* now look for the file with that language in the name */ struct stat st; char *nfile = pfc->AllocMem(pfc, 256, 0); p = strchr( map->pszPhysicalPath, '.' ); if( p ) { /* build up the new filename */ int c = p - map->pszPhysicalPath + 1; memcpy( nfile, map->pszPhysicalPath, c); memcpy( nfile + c, lang, strlen( lang ) ); c += strlen( lang ); memcpy( nfile + c, p, strlen( p ) + 1 ); /* if it exists, use it! */ if( !stat( nfile, &st ) ) map->pszPhysicalPath=nfile; } } } return SF_STATUS_REQ_NEXT_NOTIFICATION; }