|
|||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionUnless you have been a caveman, you have seen a number of web sites that whenever is browsed, the navigation ends up to a DLL file residing in a scripting directory of that dominium! Something like the following pseudo URL: http://www.mydomain.com/script/example.dll?ID=p05874&Tx=870250AZT6
What does this DLL suppose to do and what does it have to do with today's paper? These DLLs are created using the Internet Server API, or ISAPI for short. ISAPI is developed to provide a benefit or two, over the shortcomings of Common Gateway Interface, CGI. Although we surprisingly experience new web sites developed exclusively by CGI scripts nowadays, however, ISAPI DLLs have got something to offer that CGI could never bring us this way or that way. I am going to start off this paper by describing the underlying details that any ISAPI programmer has to know, to be able to develop a better ISAPI extension. From then on, I will go through a development of a useful ISAPI extension step by step. The extension is supposed to validate a given credit card. Yeah! This is also my answer to those people who asked me the algorithm involved validating a credit card over and over again. Gotta go! What is ISAPI?Internet Server Application Programming Interface (ISAPI), is an API developed to provide the application developers with a powerful way to extend the functionality of Internet Information Server (IIS). Although ISAPI extensions by no means are limited to IIS, they are extensively used in conjunction with MS-IIS. CGI vs. ISAPIDeveloping a CGI program involves creating an EXE with C, C++, and/or Perl programming languages. This EXE file will be executed and terminated for every request received, causing an excessive memory usage, whenever users hit the same page over and over again! This excessive memory usage that could bring the server completely down, has been solved under ISAPI extensions. An ISAPI extension is a regular DLL file that exposes 3 special functions that is called by the calling process (i.e., IIS) and therefore, will be loaded to memory once, no matter how many clients are going to use it at the same time. (It would be a good idea if you could take a look at a reference, to see how memory management is done under Windows 2000. The Visual C++ 6.0 Bible, Chapter 18, The Memory Management, describes it well!) ISAPI fundamentalsSince the ISAPI extension and the calling process (IIS) live at the same address space, they could contact each other, directly. This means a great potential to bring the whole IIS down, and in some cases, the entire web server! Take a look at the following figure:
You see whatever problem your extension encounters, it could affect the entire web server process, if it's not handled properly. As illustrated above, communicating between the extension and IIS is done via a pointer to a structure of type ECB, or Extension Control Block that is declared as follows: typedef struct _EXTENSION_CONTROL_BLOCK { DWORD cbSize; // size of this struct. DWORD dwVersion; // version info of this spec HCONN ConnID; // Context number not to be modified! DWORD dwHttpStatusCode; // HTTP Status code CHAR lpszLogData[HSE_LOG_BUFFER_LEN];// null terminated log info LPSTR lpszMethod; // REQUEST_METHOD LPSTR lpszQueryString; // QUERY_STRING LPSTR lpszPathInfo; // PATH_INFO LPSTR lpszPathTranslated; // PATH_TRANSLATED DWORD cbTotalBytes; // Total bytes indicated from client DWORD cbAvailable; // Available number of bytes LPBYTE lpbData; // pointer to cbAvailable bytes LPSTR lpszContentType; // Content type of client data BOOL (WINAPI * GetServerVariable) (HCONN hConn, LPSTR lpszVariableName, LPVOID lpvBuffer, LPDWORD lpdwSize ); BOOL (WINAPI * WriteClient) (HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwReserved ); BOOL (WINAPI * ReadClient) (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize ); BOOL (WINAPI * ServerSupportFunction)( HCONN hConn, DWORD dwHSERequest, LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType ); }EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK; Whatever information either the calling process or the extension wants to pass to the other, is done through this control block. We will shortly have a look at this ECB structure. For now, let's see how IIS works in conjunction with your extension, to serve the visitor of your web site. Whenever an extension is accessed (e.g., http://www.mydomain.com/script/example.dll?ID=p05874 & Tx=870250AZT6), IIS checks to see whether the example.dll is loaded into memory. If it is not, then it initiates the loading process. Once the DLL is loaded into memory, a worker thread starts running to manage our extension, and thereafter the entry point (
The server then calls the The third and the last entry point in an ISAPI extension DLL is the In brief, an ISAPI extension is a regular DLL that exposes 3 functions to interact with the server:
Having this information in hand, let's start with the DLLMain, the entry pointAs indicated by Microsoft, "the BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwCallReason,
LPVOID lpReserved);
If you provide your extension with this function, it will be called upon the initialization and the termination process of your extension. The state is indicated by the
Describing each of these parameters in detail is beyond the scope of this paper, so I simply refer you to Microsoft's Developer Network to read more about this function. Anyhow, we could save the GetExtensionVersion, the actual entry pointThis function is actually the first entry point that is called by IIS to determine the information about the extension. To understand this better, let's have a look at it's prototype: BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer); Upon the activation of this function, we are supposed to fill out the extension information within the typedef struct _HSE_VERSION_INFO { DWORD dwExtensionVersion; CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; }HSE_VERSION_INFO, *LPHSE_VERSION_INFO; where HttpExtensionProc, the main entry pointThe amazing part of any ISAPI extension starts when the extension procedure ( DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB); where Do you recall the members of the ECB's structure? ECB contains a method, prototyped as follows: BOOL WriteClient(HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes,
DWORD dwSync);
Using this member function, you can send the data present in the given char szBigRedRos[] = "<font color='#FF0000' size='3'><b>A BIG RED ROSE</b></font>"; DWORD dwSize = strlen(szBigRedRos); pECB->WriteClient(pECB->ConnID, szBigRedRose, dwSize, 0); Neat, huh? I think you already have got the minimum underlying knowledge to develop your first ISAPI extension. So, let's start... Project requirements
GoalsWe are going to develop a non-MFC ISAPI extension, using plain Win32 API calls that is supposed to validate a given Master Card. We name the extension validate.dll and it simply writes the client a string telling the client, whether the given credit card number is valid. Of course, we should examine what happens if a user wants to browse the page through the following URL: http://mydomain/script/validate.dll?some%20string
or http://mydomain/script/validate.dll?
or some sort of such invalid URLs, of course, from the purpose of our extension's view (these URLs are valid for your browser, though!). In such circumstances, we simply echo the following text to the client: What you have entered is an invalid Master Card #. And that's all what our extension does! Why not MFC?Although MFC amazingly simplifies the parsing process of query strings, it will dramatically increase the file size of our extension! On the other hand, if you know the subtle nuance of a non-MFC extension, you are a step further towards creating a better MFC extension. So, I decided to avoid using MFC for our first ISAPI extension. Luhn algorithmNow, the question is how can we check whether a given credit card number is valid. The algorithm involved in this checking is called Luhn algorithm (AKA Sum10 algorithm, AFAIK). Consider a number, say, 5168254236021548. To understand if this is a valid cc#, you should follow 4 easy guidelines:
So 5168254236021548 is an invalid cc# (since 69 % 10 is 9 not 0). Further checkingHaving completed the Luhn checking, we could go a step forward by checking the type of the CC, according to the following table:
However, for our example, we just assume that the given cc# is a Master card, so we check the card against the conditions for Master Cards, i.e., we could simply check if the given number has got 16 digits. If it's not, we simply echo that the give number is not a valid Master card number. You could extend the functionality, if you want. How to implement Luhn checking in C?Here, I've tried to program a procedure so that it simply returns 0 if the given string is a valid Master Card#. Otherwise, it will return a non-zero number indicating the error code: #define ERR_WRONG_NUMBER_OF_DIGITS 1 #define ERR_NOT_A_MASTERCARD 2 #define ERR_INVALID_CC 3 #define ERR_INVALID_INPUT 4 BYTE CheckCC(const char *pszNumber) { if(strlen(pszNumber) != 16) return ERR_WRONG_NUMBER_OF_DIGITS; for(int i = 0; i < 16; i++) if(!isdigit(pszNumber[i])) return ERR_INVALID_INPUT; if(pszNumber[0] != '5' || pszNumber[1] < '1' || pszNumber[1] > '5') return ERR_NOT_A_MASTERCARD; int nSum; for(i = 0, nSum = 0; i < 16; i += 2) { int nDigit = (pszNumber[i] - 48) * 2; nSum += (nDigit < 10 ? nDigit : nDigit / 10 + nDigit % 10) + (pszNumber[i + 1] - 48); } if(nSum % 10) return ERR_INVALID_CC; return 0; } And therefore, checking a master card is simply calling the following function: BYTE byRet = CheckCC("1269875230210254"); if(!byRet) { //this is a valid master card # } else { //An invalid master card#, byRet shows the error code! } Starting our extensionWith this information in hand, it's now time to develop our ISAPI extension. Launch your VC++ compiler. From the File menu, select the New command. Having the Projects tab selected, left-click on Win32 Dynamic-Link Library. Within the Project Name space, type validate and click ok to continue. Within the Win32 Dynamic-Link Library box, select the second option, A simple DLL project and press the Finish button. At this point, we've got a DLL project ready to be implemented. Since we are not interested either in BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call, LPVOID lpReserved)
{
return TRUE;
}
Now, let's gear from BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
{
pVer->dwExtensionVersion = HSE_VERSION;
strncpy(pVer->lpszExtensionDesc,
"Validate ISAPI Extension", HSE_MAX_EXT_DLL_NAME_LEN);
return TRUE;
}
where #define HSE_VERSION MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR)
So add the mentioned header file to the top of the validate.cpp file. Pay a close attention, please! We return What's next, then? It's now time to find a way to get the query string back from IIS. But how? You probably remember that IIS can communicate with our DLL through the ECB's pointer, passed as the only parameter to the DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB); where Another thing we should overcome before going on, is to learn how we could use BOOL (WINAPI *WriteClient)(HCONN ConnID, LPVOID Buffer,
LPDWORD lpdwBytes, DWORD dwReserved);
where With this information in hand, let's implement our DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
{
StartContext(pECB);
BYTE byRet = CheckCC(pECB->lpszQueryString);
if(!byRet)
{
//this is a valid master card, echo a suitable string to the client
WriteContext(pECB,
"<p><b><font face='Verdana'
color='#008000'>Congratulations!</font>");
WriteContext(pECB,
"</b></p><p><font size='2' face='Verdana'>");
WriteContext(pECB,
"%s is a valid master card #</font></p>\r\n",
pECB->lpszQueryString);
}
else
{
//this is an invalid master card, echo a proper string to the client!
WriteContext(pECB,
"<p><b><font face='Verdana'
color='#800000'>Sorry!</font></b></p>");
WriteContext(pECB,
"<p><font size='2' face='Verdana'>What you have entered is an ");
WriteContext(pECB,
"invalid master card#</font></p>\r\n");
}
EndContext(pECB);
return HSE_STATUS_SUCCESS;
}
And what about void WriteContext(EXTENSION_CONTROL_BLOCK *pECB, char *pszFormat, ...) { char szBuffer[1024]; va_list arg_ptr; va_start(arg_ptr, pszFormat); vsprintf(szBuffer, pszFormat, arg_ptr); va_end(arg_ptr); DWORD dwSize = strlen(szBuffer); pECB->WriteClient(pECB->ConnID, szBuffer, &dwSize, 0); } This way, we don't need to pass the length of the string or other non-useful information over and over again, while echoing a string. Moreover, we could use it like the MFC's WriteContext(pECB, "5 + 6 = %d", 5 + 6); that echoes the 5 + 6 = 11 string to the client. On the other hand void StartContext(EXTENSION_CONTROL_BLOCK *pECB) { WriteContext(pECB, "<html>\r\n<body>\r\n"); } void EndContext(EXTENSION_CONTROL_BLOCK *pECB) { WriteContext(pECB, "</body>\r\n</html>"); } Now set the active project configuration to Release mode and compile the program. It is now time to see how to install and use our extension. Assuming you are running Windows 2000, select the Internet Services Manager from the Administrative Tools of the Programs menu to bring the Internet Information Services snap-in to life. From the left panel, navigate your way through the available leaves of the web sites to the proper directory, where you plan to use the already created extension. This is usually done in scripts directory of that dominium. For this purpose, I've created a scripts directory under the articles folder:
Right click on the scripts directory and select properties to open up the following box:
Make the necessary changes to the box, so that it looks like the above picture and then apply the changes. Now, copy the validate.dll file from the release directory to the scripts directory you already configured. Launch your favorite web browser, and try to access the DLL, passing a number as its query string: http://myth/articles/scripts/validate.dll?1234567890125436
Please pay attention that you have to change the above-mentioned URL to match the specific needs of your dominium. Doing so, you'll see a damn familiar web page:
Oooooops! What went wrong, causing this damn page to appear?! If you think a little bit, you would probably remember that we have to expose those 3 (in our case 2) entry points we have used in our program, While in VC++ environment, create a blank file named validate.def and implement it this way: ; Validate.def : Declares the module parameters for the DLL. LIBRARY "Validate" DESCRIPTION 'Validate ISAPI Extension' EXPORTS ; Explicit exports can go here HttpExtensionProc @1 GetExtensionVersion @2 Then, from the project menu, select add to project, and then select the files item. Add the already created def file to the project. Rebuild the DLL. Let's copy the file from the release directory to the scripts directory again and browse the page again. This time, you will get the following page:
Now try to access the page using a valid master card number, and you'll get the congratulations page. Then try to delete the validate.dll located in the scripts directory! What happens? Yep! You'll get an error indicating that the DLL is already in use and cannot be deleted. So what we have to do, if we want to update our DLL? Shall we restart the computer, close the client, or ...? Nope! Not at all! All you have to do is to stop IIS. This is done through the Internet Information Services snap-in! Launch it, and right click on the server's name. From the context menu, select restart IIS. In the "what do you want IIS to do?" combo box, select "stop Internet services on server" and press the ok button. Wait for seconds, and it's done! Now you can delete and/or modify your extension! Neat, huh? The final wordThis is what ISAPI extensions are supposed to do. They extend the functionality of IIS. Through this article, I repeated the word extension over and over again. It's now time to say that ISAPI programs are divided into two categories: ISAPI extensions and ISAPI filters. ISAPI filters, unlike the ISAPI extensions would be called for any hit made to the web server! In other words, they magnificently slow down the process, since they are called over and over again. However, they could be absolutely useful when creating a logging service, or doing some specific jobs. Since describing ISAPI filters in detail deserves another paper, let me leave it here to you to understand the subtle nuances of how they work. Anyhow, this was the simplest ISAPI extension that we developed today, just to show you what the heck an extension is and how it works. It was a synchronous, single-threaded DLL that is the most easiest DLL to develop! Real-world applications are not this easy to implement though, since you have to face the multi-threaded issues, as well as connection pools and other advanced topics. It's all up to you to learn how to play them magnificently, though, and this paper is just a starting point! That's all, folks. Aloha! How to contact the authorI, as ever before, would love to hear your comments, questions and/or suggestions. So, please do not hesitate to send them to me, mehdi_mousavi@hotmail.com. References | ||||||||||||||||||||||||||||||||