Extending an Internet Server with ISAPI – Part 01

 

Overview

This chapter examines the ISAPI and CGI technologies. You can use CGI to create applications that extend a Web server, and you can use ISAPI to create DLLs that extend a Web server. In particular, ISAPI allows you to write scripts and filters and to interact dynamically with a user of your browser.

ISAPI technology is specific to the Internet Information Server that ships with Windows NT and to the Personal Web Server that ships with Microsoft FrontPage. It is, however, merely a specification, and other servers could conform to it if they wish. Several different vendors, including Borland, have created technology that allows ISAPI-based technology to be used in conjunction with NSAPI, which is a similar technology to ISAPI. (The NSAPI/ISAPI bridge is available in Delphi 3.0, for example.)

ISAPI programming is very similar to CGI programming. The only major difference is that you're creating a DLL instead of an executable. DLLs are advantageous because they can be loaded into the address space of the Web server. This capability gives them a leg up over CGI when you're considering performance. CGI, on the other hand, is a very simple specification to use, and it adapts itself easily to database applications.

ISAPI's reliance on the Windows platform might be a serious limitation in some other context, but because C++Builder also relies on Windows, discussing the topic at length in this book makes sense. Another feature to recommend ISAPI is its extreme simplicity. Friendly, powerful, easy-to-use APIs are the bread and butter of this book, and ISAPI fits the bill beautifully. Writing BCB database applications with ISAPI is, however, a bit tricky.

The first half of this chapter deals with ISAPI, and the second half deals with CGI. I will show how to retrieve data from both ISAPI DLLs and CGI applications, though my treatment of the subject is more complete in the section on CGI. If you are interested primarily in ISAPI, you should also take the time to read about the CGI database applications in the second half of this chapter.

The C++ code in this chapter relies on the presence of several HTML files that are quoted in full in this chapter and that are also available on the CD that ships with this book. However, the C++ code will not function properly unless the HTML files quoted in this chapter are in the correct location on your hard drive. See the README.TXT file that comes with the CD for additional information on setting up your system correctly to run this code. Some of these files are located in the root of the Chap26 directory on the CD. You will also need to use several databases' aliases described in the readme file.

Hardware and Software Requirements

As I implied in the "Overview" section for this chapter, the code discussed here requires one of the following:

  • A copy of Microsoft Windows NT 3.51 Server or NT 4.0 or later server with the Internet Information Server loaded

 

  • Windows 95 and a copy of the Personal Web Server that ships with FrontPage

At the time of this writing, it is not clear whether the Personal Web Server (PWS) will be available from other sources besides FrontPage. I downloaded the beta copy I used while writing this book directly from Microsoft's Web site. Checking to see if this copy is still available in that form is probably worthwhile.

You should also check to see if the Netscape or WebSite servers are now supporting the ISAPI API. An unfortunate and rather unseemly economic battle between Microsoft and its various competitors may slow down or even halt the spread of ISAPI as a standard, but checking to see if the battle has cooled somewhat is still worthwhile.

At any rate, the PWS is a useful piece of software for home users to explore. It will turn any Windows 95 machine into a Web server. I'm not sure how robust it will be under the strain of more than a few contiguous users. However, it is ideal if you want to set up a Web server in your home or in a small office. WebSite, from O'Reilly, is another fine product to turn to, particularly if you want to select a well-tested, robust server that can carry a heavy load.

You should also have a second computer equipped with a Web browser. This second computer can be running any operating system and can use virtually any software that supports Web browsing. I can think of no reason why you can't test most of this code on a single machine running a server, of course, but you will hardly get into the spirit of this enterprise if you're limited to that kind of setup.

I assume that most readers working in a business setting will have an intranet setup that will allow them to experiment with this technology. If you're working at home, I cannot stress too often the incredible value of setting up a network in your house. Network cards are very inexpensive these days. One of the ones I use in my home cost about $30 new. Network cable is also inexpensive, and both Windows 95 and Windows NT come equipped with all the software you need to set up a network that supports both file browsing with Windows Explorer and also TCP/IP.

When I first set up a network in my home, I thought I was pushing the extreme edge of modern technology. Now I simply take it for granted and can't understand how in the world I ever got along without it. Old computers don't have to die; they can just become Web servers. Small hard drives are extended easily by sharing storage space across multiple machines. After all, you don't need a separate copy of every application or every file on each machine. You can just share drives back and forth between machines, thereby saving a tremendous amount of space.

Most importantly, you can study and experiment with your network at your leisure and then apply that knowledge at work. A home network is an ideal place to educate yourself regarding this valuable technology.

Getting More Information on ISAPI

The best place to go for information on ISAPI is the Microsoft MSDN or the Microsoft Internet SDK. These two sources provide most of the information you need that can't be found in this book.

Here's a place you can go on the Web if you want to find out more about the ISAPI specification:

http://www.microsoft.com/win32dev/apiext/isalegal.htm
 

Of course, I can't guarantee that this Web page will still be in existence when you read this book. However, two relatively stable sites on the Web that should serve as links to this spot are

http://www.microsoft.com/intdev/
http://www.microsoft.com/win32dev/
 

Check in at both these sites on fairly regular intervals to get updates on Win32 and Internet technology.

ISAPI

As stated in the Microsoft documentation, ISAPI allows you to "Write server-side scripts and filters to extend the capabilities of Microsoft Internet Information Server and other ISAPI Web servers."

ISAPI is a very easy-to-use yet extremely powerful technology that allows you to extend the reach of the Internet Information Server or the Personal Web Server. This tool allows you to make your Web site do pretty much whatever you want it to do. For example, it provides a means for you to

  • Set up interactive responses to user input

 

  • Provide database browsing and updating

 

  • Filter input to your browser to track who signs on and where he or she goes

In the past, the best way to extend a Web server was to create CGI applications. These powerful tools were limited by their executable format. When you sent in a CGI-based request from a browser to a server, the CGI application in question usually had to be loaded into memory, which took a long time. Also, the CGI technology could be a bit awkward to use under some circumstances.

ISAPI is a method of writing DLLs that replace CGI applications. You can also write filters with ISAPI, though this subject is not covered in this book. ISAPI has the advantage of being easier to use than CGI, plus it can be much faster and make much better use of system resources. In particular, the following points help explain why ISAPI DLLs are better than CGI applications:

  • ISAPI DLLs live in the same address space as the HTTP server. They can therefore directly access the HTTP services available from the server. They load into memory more quickly and have much less overhead when it comes to making a call from the server. These capabilities can be particularly helpful if you're working under a heavy load.

 

  • You can control when the DLL is loaded or unloaded. For example, you can preload DLLs for fast access on the first try or unload the ISAPI applications DLLs that are not being used to free system resources. You can do the same thing with the CGI executable, but the executable format was not really designed for this kind of manipulation, although this is part of the native capability of DLLs.

In this chapter, I will concentrate on writing DLLs that return datasets or that simply communicate with the user who is running a browser. I will not explore filters at all. For information on filters, you should go to the Microsoft Web site or browse the MSDN.

ISAPI Basics

The file Httpext.h contains the key declarations used with ISAPI. This file should ship with C++Builder and is available with versions of the Microsoft SDK dated later than July 1996. It should also appear in the \include\vcl directory as ISAPI.HPP. Because it is a Windows 95- or Windows NT-based technology, you must be using a 32-bit compiler to access this technology. You can't use it from a 16-bit compiler, nor is it available on Windows 3.1.

Httpext.h contains the interface to the ISAPI technology created by Microsoft. At the time of this writing, C++Builder has no custom interface for ISAPI, and I will describe only how to use Microsoft's existing technology. However, ISAPI is extremely easy to use, and the addition of a custom object is not necessary for most users.

Three functions can serve as entry points to ISAPI DLLs. The first two listed here are mandatory, whereas the third is optional:

  • GetExtensionVersion: This function just does minimal version checking.

 

  • HttpExtensionProc: This function is the entry point of the DLL, like the main begin..end block in a Delphi application.

 

  • TerminateExtension: This optional routine can be used to clean up threads of other memory allocations.

When you're creating an ISAPI DLL, you must export the first two of the three preceding functions. Implementing these two functions is the key to all ISAPI programming.

In C++Builder, DEF files are frowned upon. You should therefore make sure that Httpext.h has been modified to export both of these functions with __declspec(dllexport):

BOOL WINAPI __declspec(dllexport) GetExtensionVersion(HSE_VERSION_INFO *pVer);
 
DWORD
 
WINAPI
 
__declspec(dllexport) HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB);
 

TerminateExtension is new in ISAPI 2.0. Its declaration looks like this:

BOOL  WINAPI   TerminateExtension( DWORD dwFlags );
 

TerminateExtension is called just before a connection is broken. It provides a place for you to deallocate memory allocated inside your DLL.

These three routines all contain the word Extension. This term is used because ISAPI DLLs extend the Internet Information Server or the Personal Web Server. (Remember, the Internet Information Server is Microsoft's Web server. If you want to turn an NT Server into a Web server, you use this tool. It ships with NT 4.0 and is installed automatically during the setup of that operating system.)

Using GetExtensionVersion

The GetExtensionVersion function must be exported from your DLL; otherwise, the server will not load your DLL. The only job of this function is to report the version of ISAPI that you expect to support.

You can always just cut and paste the GetExtensionVersion code into your DLLs. You need to change the function only slightly when you want to change the description passed in the lpszExtensionDesc field of the HSE_VERSION_INFO struct:

BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
 
{
 
  pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
 
  strcpy(pVer->lpszExtensionDesc, "C++ Builder ISAPI DLL");
 
  return (TRUE);
 
};
 

The parameter passed to this function is declared in Httpext.h as follows:

typedef struct   _HSE_VERSION_INFO {
 
    DWORD  dwExtensionVersion;                           // Version info
 
    CHAR   lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN];  // Description
 
} HSE_VERSION_INFO, *LPHSE_VERSION_INFO;
 

The two fields of the record are self-explanatory, with the first containing the ISAPI version number and the second holding a user-defined string describing the purpose of the DLL. The following are some constants declared in the DLL that are used in the preceding code:

#define   HSE_MAX_EXT_DLL_NAME_LEN  256
 
#define   HSE_VERSION_MAJOR           1      // major version of this spec
 
#define   HSE_VERSION_MINOR           0      // minor version of this spec
 

That's all you need to do to set up the first of the two mandatory functions in an ISAPI DLL. The next step, using HttpExtensionProc, is a bit more complex, so I will treat it in its own section.

Working with the HttpExtensionProc

The HttpExtensionProc routine is the entry point for the DLL. It serves the same purpose that the main() routine does in a C program, or that the main begin..end pair does in a Delphi program.

Here is a very simple example of an HttpExtensionProc routine:

DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
 
{
 
  char ResultString[500];
 
  DWORD resultLen;
 
  char *IsapiLogText = "ISAPI1 - Simple ISAPI Extension DLL";
 
  strcpy(pECB->lpszLogData, IsapiLogText);
 
  pECB->dwHttpStatusCode = 200;
 
  char *HtmlInfo =
 
    "<HTML>"
 
    "<HEAD><TITLE>C++ Builder ISAPI DLL </TITLE></HEAD>"
 
    "<H1>ISAPI1 Test Results</H1>"
 
    "<BODY bgcolor=\"#0000FF\" text=\"#00FFFF\">"
 
    "Hello from a C++ Builder ISAPI DLL!<BR></BODY>"
 
    "</HTML>";
 
  sprintf(ResultString,
 
    "HTTP/1.0 200 OK\nContent-Type: text/html\n"
 
    "Content-Length: %d\nContent:\n\n %s", 500, HtmlInfo);
 
  resultLen = lstrlen(ResultString);
 
  fprintf(out, ResultString);
 
  pECB->WriteClient(pECB->ConnID, ResultString, &resultLen, 0);
 
  return (HSE_STATUS_SUCCESS);
 
}
 

If you queried a DLL containing this function from a browser, you would get a page back with this message:

ISAPI1 Test Results
 
Hello from a C++ Builder ISAPI DLL!
 

In the next few paragraphs, I will describe the key points of the code shown here. However, I'll help you develop a complete understanding of this routine slowly in the next few sections of the chapter.

The HTML code for querying the DLL might look something like this:

<a href="/scripts/isapi1.dll">ISAPI1 Example</a>
 

Most of the body of the function is taken up with simple HTML code that provides basic information to the user:

char *HtmlInfo =
 
    "<HTML>"
 
    "<HEAD><TITLE>C++ Builder ISAPI DLL </TITLE></HEAD>"
 
    "<H1>ISAPI1 Test Results</H1>"
 
    "<BODY bgcolor=\"#0000FF\" text=\"#00FFFF\">"
 
    "Hello from a C++ Builder ISAPI DLL!<BR></BODY>"
 
    "</HTML>";
 

You also need to fill in a few fields of the EXTENSION_CONTROL_BLOCK:

char *IsapiLogText = "ISAPI1 - Simple ISAPI Extension DLL";
 
strcpy(pECB->lpszLogData, IsapiLogText);
 
pECB->dwHttpStatusCode = 200;
 

The lpszLogData field contains the string that will be written to the log on your server. With the Personal Web Server, this log is kept by default in the Windows directory, though you can change this in the Administration section of the server applet found in the Control Panel.

The status code in this example is set to 200, which means "OK." Other possible values include the following:

HTTP_STATUS_BAD_REQUEST
 
HTTP_STATUS_AUTH_REQUIRED
 
HTTP_STATUS_FORBIDDEN
 
HTTP_STATUS_NOT_FOUND
 
HTTP_STATUS_SERVER_ERROR
 
HTTP_STATUS_NOT_IMPLEMENTED
 

More information on the EXTENSION_CONTROL_BLOCK is provided in the section called "Working with the EXTENSION_CONTROL_BLOCK."

Notice the function pointer called WriteClient in the struct. You can call this function to send information back to the browser. When calling this function, you use the value in the ConnID field of the EXTENSION_CONTROL_BLOCK struct. ConnID is filled in for you automatically when the HttpExtensionProc function is called.

Before you look at the EXTENSION_CONTROL_BLOCK struct, let me show you a complete ISAPI DLL that uses the HttpExtensionProc function shown in this section.

A Stripped-Down ISAPI Example

The source code in Listing 26.1 shows how to create the simplest possible ISAPI DLL. The goal is to remove all the complications from the code, and just include enough information to make sure everything is working correctly.

Listing 26.1. The ISAPI1 example.

///////////////////////////////////////
 
// FILE: ISAPI1.CPP
 
// PROJECT: ISAPI1.DLL
 
// copyright (3) 1996 by Charlie Calvert
 
//
 
// This example shows how to use ISAPI, which is similar to creating a
 
// CGI application. The code should return a simple string to an HTML
 
// browser such as the Internet Explorer.
 
//
 
// Here is the HTML you output in a browser to call this ISAPI DLL:
 
//
 
// <HTML>
 
// <HEAD>
 
// <TITLE>CharlieC Home Page</TITLE>
 
// </HEAD>
 
// <BODY>
 
// <H1>My Home Page </H1>
 
// <P>
 
// This is the home page for my home computer.
 
// <P>
 
// <A HREF="/scripts/isapi1.dll" >ISAPI One</A><BR>
 
// </BODY>
 
// </HTML>
 
#include <vcl\vcl.h>
 
#include <string.h>
 
#include <stdio.h>
 
#pragma hdrstop
 
#include "..\..\utils\Httpext.h"
 
USERES("Isapi1.res");
 
FILE *out;
 
// GetExtensionVersion callback definition
 
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
 
{
 
  fputs("Version", out);
 
  pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
 
  strcpy(pVer->lpszExtensionDesc, "C++ Builder ISAPI DLL");
 
  return (TRUE);
 
};
 
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
 
{
 
  AnsiString ResultString;
 
  DWORD resultLen;
 
  AnsiString IsapiLogText = "ISAPI1 - Simple ISAPI Extension DLL";
 
  strcpy(pECB->lpszLogData, IsapiLogText.c_str());
 
  AnsiString HtmlInfo =
 
    "<HTML>"
 
    "<HEAD><TITLE>C++ Builder ISAPI DLL </TITLE></HEAD>"
 
    "<H1>ISAPI1 Test Results</H1>"
 
    "<BODY bgcolor=\"#0000FF\" text=\"#00FFFF\">"
 
    "You are talking to a C++Builder ISAPI DLL."
 
    "<BR></BODY>"
 
    "</HTML>";
 
  pECB->dwHttpStatusCode = 200;
 
  ResultString = Format(
 
    "HTTP/1.0 200 OK\nContent-Type: text/html\n"
 
    "Content-Length: %d\nContent:\n\n %s",
 
    OPENARRAY(TVarRec, (HtmlInfo.Length(), HtmlInfo)));
 
  resultLen = ResultString.Length();
 
  fprintf(out, ResultString.c_str());
 
  pECB->WriteClient(pECB->ConnID, ResultString.c_str(), &resultLen, 0);
 
  return (HSE_STATUS_SUCCESS);
 
}
 
#pragma argsused
 
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
 
{
 
  switch (reason)
 
  {
 
    case DLL_PROCESS_ATTACH:
 
      out = fopen("c:\\test.txt", "w+");
 
      fprintf(out,"hello");
 
      break;
 
    case DLL_PROCESS_DETACH:
 
      fprintf(out,"goodbye");
 
      fclose(out);
 
      break;
 
    default:
 
      break;
 
  }
 
  return (TRUE);
 
}
 

To use this DLL, you should copy it into a subdirectory of the scripts directory beneath the root for your Web. On my NT 4.0 machine, the subdirectory looks like this:

c:\winnt\system32\inetsrv\scripts\mystuff\isapi1.dll
 

In this case, I have created the directory called MYSTUFF, and it is used solely for storing ISAPI DLLs I have created. Your mileage may, of course, differ on your machine, depending on where you put the InetSrv directory and various other factors.

To call this DLL, you should add the following hyperlink to one of your HTML pages:

<A HREF="/scripts/mystuff/isapi1.dll" >ISAPI One</A><BR>
 

For example, here is a complete sample page:

<HTML>
 
<HEAD><TITLE>An ISAPI Page</TITLE></HEAD>
 
<BODY>
 
<H1>My ISAPI Page</H1>
 
<P>This is the home page for ISAPI on my computer.<P>
 
<A HREF="/scripts/mystuff/isapi1.dll" >ISAPI One</A><BR>
 
</BODY>
 
</HTML>
 

When the user clicks the hyperlink, the ISAPI1 DLL will be called and the string "Hello from C++ Builder" will appear in the user's browser. If you did not put the ISAPI1.DLL in the MYSTUFF directory, then you should change the preceding HTML code to reflect that fact. Notice that the path you assign is relative to the InetSrv directory and does not, and should not, contain the entire path to your DLL.

Note that if you copy the ISAPI1.DLL into the MYSTUFF directory multiple times, you will need to shut down the WWW portion of the Internet server before each copy. The rule is that you can copy the DLL the first time for free, but after you have used it, it belongs to the server, and you need to shut down the WWW services on the server before you can copy an updated version of the file over the first copy. You can use the Internet Service Manager application to shut down the WWW services on the NT Server. This application should be in the Microsoft Internet Server group created in Windows Explorer or Program Manager (NT 3.51) at the time of the installation of the Internet Information Server. You can use the PWS applet in the Control Panel if you're using the Personal Web Server on Windows 95 or on the Windows NT Workstation.

Working with the EXTENSION_CONTROL_BLOCK

By this point in the chapter, you should be able to create your first ISAPI DLL and call it from a Web browser on a second machine. The rest of this chapter explores ISAPI in more depth.

The following fairly complex record is passed as the sole parameter to HttpExtensionProc:

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];// 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      dwHSERRequest,
 
                                         LPVOID     lpvBuffer,
 
                                         LPDWORD    lpdwSize,
 
                                         LPDWORD    lpdwDataType );
 
} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;
 

Notice that this record contains the ConnID field referenced previously and passed as the first parameter to WriteClient.

The first parameter of this record is used for version control. It should be set to the size of the EXTENSION_CONTROL_BLOCK. If Microsoft changes this structure, then they can tell which version of the structure they are dealing with by checking the size of the record as recorded in this field. You should never change any of the first three fields of this record; these fields are filled out ahead of time by ISAPI and can only be referenced, not changed, by your program.

The most important field of this record is probably the lpszQueryString, which contains information about the query passed in from the server. For example, suppose you have created a DLL called ISAPI1.DLL. To call this DLL, you would create an HREF that looks like this in one of your browser pages:

<A HREF="/scripts/mystuff/test1.dll">Test One</A>
 

If you want to query the DLL, you would edit the preceding line so that it looks like this:

<A HREF="/scripts/mystuff/test1.dll?MyQuery">Test One</A>
 

Given the second of the two HTML fragments listed here, your DLL would get called with the string "MyQuery" in the lpszQueryString parameter. Notice in particular the use of the question mark, followed by the query string itself.

You could, of course, change the query string at will. For example, you could write

<A HREF="/scripts/mystuff/test1.dll?ServerName">Test One</A>
 

To this query, the DLL might reply with the name of the server. You have no limits on what you can pass in this parameter, but the string after the question mark cannot have any spaces in it. If you need to use spaces, replace them with a plus sign: Instead of "Server Name", write "Server+Name". The string can be anything you want, and it is up to you to parse the information from inside the DLL as you like.

When you return information from the server back to the browser, you use the WriteClient function pointer that is part of this record.

Writers of CGI applications will notice that the syntax for sending query strings is familiar. Indeed, ISAPI follows many of the conventions of CGI, and most of the fields in the EXTENSION_CONTROL_BLOCK are simply borrowed directly to initialize this pointer; it is passed to you gratis by the Internet Information Server.

Another key field in the EXTENSION_CONTROL_BLOCK is the lpbData field, which contains any additional information sent to you by the browser. In particular, it is used to pass information associated with a Submit button. For example, if you have an HTML form with a number of fields in it, the information from these fields will be sent in the pointer called lpbData after the Submit button is clicked. The section of this chapter called "Getting Information from a Submit Button" focuses on how to handle this situation.

So far I have zeroed in on three key fields of the EXTENSION_CONTROL_BLOCK:

  • WriteClient: A pointer to a function that allows you to send formatted HTML data back to the browser. This function uses the ConnID field of EXTENSION_CONTROL_BLOCK.

 

  • lpszQueryString: The query passed to you from the browser.

 

  • lpbData: Any additional data being passed to you from the browser. This data is usually the contents of any fields on an HTML form. I discuss this field further in the section on the Submit button.

Mirroring the Fields of the EXTENSION_CONTROL_BLOCK

The best way to get a feeling for how the rest of the fields of EXTENSION_CONTROL_BLOCK work is simply to mirror them back to yourself in a browser. In other words, you can create an HTML page that allows the user to call a custom ISAPI DLL. The purpose of this ISAPI DLL is simply to snag the contents of each field of the EXTENSION_CONTROL_BLOCK, format them in HTML, and send them back to the browser. This will turn your browser into a rather jazzy debugger that shows each of the fields in the EXTENSION_CONTROL_BLOCK. Listing 26.2 contains the source to a DLL called IsapiVars that performs this task.

Listing 26.2. The IsapiVars code that mirrors back the parameters sent in the EXTENSION_CONTROL_BLOCK.

///////////////////////////////////////
 
// IsapiVars.cpp
 
// Mirror back the information sent to an ISAPI DLL by the server
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#include <vcl\vcl.h>
 
#pragma hdrstop
 
#include "..\..\utils\Httpext.h"
 
 
 
USERES("IsapiVars.res");
 
 
 
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
 
{
 
  pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
 
  strcpy(pVer->lpszExtensionDesc, "ISAPI Variables DLL");
 
  return (TRUE);
 
};
 
 
 
#define SIZE 2048
 
 
 
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
 
{
 
  char ResultString[SIZE * 2];
 
  char HtmlInfo[SIZE];
 
  char Buffer[SIZE];
 
  DWORD StrSize;
 
  DWORD resultLen;
 
 
 
  char *IsapiLogText = "ISAPIVars from C++ Builder";
 
  strcpy(pECB->lpszLogData, IsapiLogText);
 
  pECB->dwHttpStatusCode = 200;
 
 
 
  sprintf(HtmlInfo,
 
    "<HTML><TITLE>Fields of EXTENSION_CONTROL_BLOCK</TITLE>"
 
    "<H1>Test server results</H1><BODY>"
 
    "Size = %d<BR>"
 
    "Version = %.8x<BR>"
 
    "ConnID = %.8x<BR>"
 
    "Method = %s<BR>"
 
    "Query = %s<BR>"
 
    "PathInfo = %s<BR>"
 
    "PathTranslated = %s<BR>"
 
    "TotalBytes = %d<BR>"
 
    "AvailableBytes = %d<BR>"
 
    "ContentType = %s<BR><BR>"
 
    "<H1>Calls to GetServerVariable</H1>",
 
    pECB->cbSize, pECB->dwVersion, pECB->ConnID,
 
    pECB->lpszMethod, pECB->lpszQueryString,
 
    pECB->lpszPathInfo, pECB->lpszPathTranslated,
 
    pECB->cbTotalBytes, pECB->cbAvailable,
 
    pECB->lpszContentType);
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "REMOTE_ADDR", &Buffer, &StrSize);
 
  AnsiString VarString("REMOTE_ADDR = " + AnsiString(Buffer) + "<BR>"); 
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "REMOTE_HOST", &Buffer, &StrSize);
 
  VarString += "REMOTE_HOST = " + AnsiString(Buffer) + "<BR>";
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "REMOTE_USER", &Buffer, &StrSize);
 
  VarString += "REMOTE_USER = " + AnsiString(Buffer) + "<BR>";
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "SERVER_NAME", &Buffer, &StrSize);
 
  VarString += "SERVER_NAME = " + AnsiString(Buffer) + "<BR>";
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "SERVER_PORT", &Buffer, &StrSize);
 
  VarString += "SERVER_PORT = " + AnsiString(Buffer) + "<BR>";
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "SERVER_PROTOCOL", &Buffer, &StrSize);
 
  VarString += "SERVER_PROTOCOL = " + AnsiString(Buffer) + "<BR>";
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "SERVER_SOFTWARE", &Buffer, &StrSize);
 
  VarString += "SERVER_SOFTWARE = " + AnsiString(Buffer) + "<BR>";
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "HTTP_ACCEPT", &Buffer, &StrSize);
 
  VarString += "HTTP_ACCEPT = " + AnsiString(Buffer) + "<BR>";
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "URL", &Buffer, &StrSize);
 
  VarString += "URL = " + AnsiString(Buffer) + "<BR><BR><BR>";
 
 
 
  StrSize = sizeof(Buffer);
 
  pECB->GetServerVariable(pECB->ConnID, "ALL_HTTP", &Buffer, &StrSize);
 
  VarString += "ALL_HTTP = " + AnsiString(Buffer) + "<BR>";
 
 
 
 
 
  strcat(HtmlInfo, VarString.c_str());
 
 
 
  sprintf(ResultString,
 
    "HTTP/1.0 200 OK\nContent-Type: text/html\n"
 
    "Content-Length: %d\nContent:\n\n %s </HTML>",
 
    SIZE, HtmlInfo);
 
 
 
 
 
  StrSize = strlen(ResultString);
 
  pECB->WriteClient(pECB->ConnID, ResultString, &StrSize, 0);
 
  return (HSE_STATUS_SUCCESS);
 
}
 
 
 
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
 
{
 
  return 1;
 
} 
 

To call this DLL, you should create an HTML script that contains the following line:

<A HREF="/scripts/mystuff/isapivars.dll">Test One</A> <BR>
 

Of course, the actual path shown in your code may be somewhat different from what I show here.

The HttpExtensionProc for this DLL is broken into two sections. The first retrieves all the main fields from the EXTENSION_CONTROL_BLOCK, and the second goes to town on one particular field, which is a function called GetServerVariable.

The code that parses the main fields of the EXTENSION_CONTROL_BLOCK is fairly straightforward:

sprintf(HtmlInfo,
 
    "<HTML><TITLE>Fields of EXTENSION_CONTROL_BLOCK</TITLE>"
 
    "<H1>Test server results</H1><BODY>"
 
    "Size = %d<BR>"
 
    "Version = %.8x<BR>"
 
    "ConnID = %.8x<BR>"
 
    "Method = %s<BR>"
 
    "Query = %s<BR>"
 
    "PathInfo = %s<BR>"
 
    "PathTranslated = %s<BR>"
 
    "TotalBytes = %d<BR>"
 
    "AvailableBytes = %d<BR>"
 
    "ContentType = %s<BR><BR>"
 
    "<H1>Calls to GetServerVariable</H1>",
 
    pECB->cbSize, pECB->dwVersion, pECB->ConnID,
 
    pECB->lpszMethod, pECB->lpszQueryString,
 
    pECB->lpszPathInfo, pECB->lpszPathTranslated,
 
    pECB->cbTotalBytes, pECB->cbAvailable,
 
    pECB->lpszContentType);
 

This code is nothing more than a simple call to sprintf. The goal is simply to take the fields of the EXTENSION_CONTROL_BLOCK and mirror them back to the user's browser. To do so, all you need to do is set up a legitimate HTML document and then add a human-readable version of the struct to the body of the form.

The output from this part of the code looks like the screen shot shown in Figure 26.1.

FIGURE 26.1. The main fields of the EXTENSION_CONTROL_BLOCK shown in a browser.

Working with GetServerVariable is a bit more complicated. As a result, I will give this description a whole section so that it can have plenty of room in which to knock about.

The GetServerVariable and ReadClient Routines

You can use GetServerVariable to retrieve information from a server just as you would request information inside a CGI application. Here is an example of calling the routine:

#define SIZE 2048;
 
...
 
char Buffer[SIZE];
 
...
 
StrSize = sizeof(Buffer);
 
pECB->GetServerVariable(pECB->ConnID, "REMOTE_ADDR", &Buffer, &StrSize);
 
AnsiString VarString("REMOTE_ADDR = " + AnsiString(Buffer) + "<BR>");
 

This function takes a connection ID in the first parameter, a constant in the second parameter, a buffer in the third parameter, and the length of the buffer in the fourth parameter:

BOOL WINAPI GetServerVariable(
 
 HCONN hConn,
 
 LPSTR lpszVariableName,
 
 LPVOID lpvBuffer,
 
 LPDWORD lpdwSizeofBuffer
 
);
 

As a rule, you will have to reset the fourth parameter before each call to this function because the function itself returns the length of the string found in the lpvBuffer parameter in the lpdwSizeOfBuffer parameter. You definitely don't want to raise any exceptions or cause any errors to occur in your DLL, so I suggest playing it safe when calling this function.

The preceding code first sets the length of the buffer that will hold the information retrieved from the server. It then calls the server and asks for information. In this case, it asks for the content length of the information sent by the server.

You can pass the following strings in the second parameter of GetServerVariable:

AUTH_TYPE

Type of authentication used.

CONTENT_LENGTH

Number of bytes you can expect to receive from the client.

CONTENT_TYPE

Type of information in the body of a POST request.

PATH_INFO

Trailing part of the URL after the script name.

PATH_TRANSLATED

PATH_INFO with any virtual pathname expanded.

QUERY_STRING

Info following the "?" in the URL.

REMOTE_ADDR

IP address of the client (could be a gateway or firewall).

REMOTE_HOST

Hostname of the client or agent of the client.

REMOTE_USER

Username supplied by the client and authenticated by the server.

UNMAPPED_REMOTE_USER

Username before ISAPI mapped to an NT user account.

REQUEST_METHOD

The HTTP request method.

SCRIPT_NAME

The name of the script program being executed.

SERVER_NAME

The server name.

SERVER_PORT

The TCP/IP port on which the request was received.

SERVER_PORT_SECURE

If the request is on the secure port, then this will be 1; otherwise, it is 0.

SERVER_PROTOCOL

Usually HTTP/1.0.

SERVER_SOFTWARE

The name of the server software.

ALL_HTTP

All headers not already parsed into one of the previous variables.

HTTP_ACCEPT

The special-case HTTP header.

URL

New for version 2.0. The base portion of the URL.



You can find additional information on these variable names in the Microsoft online help.

 

You can see examples of the type of information returned by calling GetServerVariable in Figure 26.2. Note that this screen shot simply shows the lower half of the window shown in Figure 26.1.

FIGURE 26.2. The results of making several repeated calls to GetServerVariable.

Note that many of the preceding pieces of information are automatically passed in the EXTENSION_CONTROL_BLOCK record. Therefore, you usually do not need to call GetServerVariable, but you can if you need to, particularly if you want to retrieve information with ReadClient and need to know how much information to read.

Most of the time, you don't need to call ReadClient. However, if the amount of data being sent by the browser is larger than 48KB, you will need to call ReadClient to get the rest of the data.

The DLLEntryPoint

Before completing the discussion of how this DLL works, I want to take a moment to get some housekeeping chores out of the way. In particular, I want to mention the DLLEntryPoint routine, which appears at the bottom of the DLL.

All DLLs have an entry point that is called automatically. You don't have to respond to this entry point, but doing so is usually a good idea. In this case, I simply open a text file for debugging purposes:

#pragma argsused
 
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
 
{
 
  switch (reason)
 
  {
 
    case DLL_PROCESS_ATTACH:
 
      out = fopen("c:\\test.txt", "w+");
 
      fprintf(out,"hello");
 
      break;
 
    case DLL_PROCESS_DETACH:
 
      fprintf(out,"goodbye");
 
      fclose(out);
 
      break;
 
    default:
 
      break;
 
  }
 
  return (TRUE);
 
}
 

Nothing about the code shown here is mandatory. You don't have to open a text file and write to it; the code serves no other purpose than to give you a simple means of debugging your DLL. In particular, it creates a text file on the server to leave a record of your DLL's behavior. This file is helpful, particularly if you're learning ISAPI and have problems simply creating a valid DLL that exports the key functions.

 

 


NOTE: You can debug an ISAPI DLL by using several different means. One rather fancy method is to load the entire server into the stand-alone debugger, load your ISAPI DLL, and then set a break point inside it.

Although effective, the technique described in the preceding paragraph can be overkill. My suggestion is not to be too proud about resorting to the old-fashioned technique of creating a text file and writing to it. You can produce very detailed reports in this fashion, and they can show you exactly what is going on in your DLL. The only flaw in this system is that entering all those
fprintf statements takes a bit of time.


The example shown here has a more valuable purpose then merely showing one rather simple-minded way to debug an ISAPI DLL. In particular, it reminds you of the proper way to handle the entry point of a DLL. In addition to the DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH notifications are two others called DLL_THREAD_ATTACH and DLL_THREAD_DETACH. Because multiple clients could be using your DLL at the same time, DLL_THREAD_ATTACH can be a very important entry point to use when you're debugging or constructing your DLL.

Getting Information from a Submit Button

Often you get information sent to you from an HTML form that has a Submit button on it. As long as this information is shorter than 49KB, you can assume that it will be available in the lpbData field of TExtensionControlBlock. Otherwise, you will need to call ReadClient. Here is how you would typically read the information from the pointer passed in this field:

AnsiString S;
 
  if (pECB->lpbData != NULL)
 
  {
 
    S = (char *)pECB->lpbData;
 
    S = Parse(S);
 
  }
 
  else
 
    S = "Error occurred on get from lpbData field";
 

This code first checks to see if lpbData is non-NULL. This type of conservative coding is a necessity in ISAPI, as you don't want errors to be occurring way over on the server side, where it is hard to see what is going on. The fragment shown here then typecasts lpbData so that you can place its contents in a variable of type AnsiString. It then passes the string to a user-defined function called Parse that handles the string passed by the server. If something goes wrong, the string variable is set equal to an error message and then returned to the user so he or she can view it in a browser.

If you want to see exactly what information is available in the lpbData field, you can use the following two functions to echo the data back to your Web browser:

AnsiString SetupHeader(AnsiString &ResultString, AnsiString S, int &Len)
 
{
 
  char *HeaderInfo = "HTTP/1.0 200 OK\nContent-Type: text/html\n"
 
                     "Content-Length: %d\nContent:\n\n %s </HTML>";
 
 
 
  ResultString.SetLength(S.Length() + strlen(HeaderInfo) + 1);
 
  Len = ResultString.Length();
 
  sprintf(ResultString.c_str(), HeaderInfo, Len, S);
 
  return ResultString;
 
}
 
//HttpExtensionProc callback definition
 
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
 
{
 
  AnsiString ResultString;
 
  int ResultLen;
 
  char *IsapiLogText = "Mirror lpbData";
 
  strcpy(pECB->lpszLogData,IsapiLogText);
 
  pECB->dwHttpStatusCode = 200;
 
  AnsiString S;
 
  if (pECB->lpbData != NULL)
 
  {
 
    S = (char *)pECB->lpbData;
 
  }
 
  else
 
    S = "Error occurred get lpbData field";
 
  SetupHeader(ResultString, S, ResultLen);
 
  pECB->WriteClient(pECB->ConnID, ResultString.c_str(), &(DWORD)ResultLen, 0);
 
  return (HSE_STATUS_SUCCESS);
 
}
 

The first function, called SetupHeader, is just a utility routine that automates the process of setting up a header. It forces you to pass in the variable that is sent in the third parameter of WriteClient. I do this simply to help remind myself that I have to initialize this variable before passing it to the server.

The second routine simply mirrors the lpbData field back to the user of the DLL. On the CD that accompanies this book, you will find a DLL called MirrorData and an HTML form called MirrorDataTest.htm, which looks like this:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
 
<html>
 
<head>
 
<meta http-equiv="Content-Type"
 
content="text/html; charset=iso-8859-1">
 
<meta name="GENERATOR" content="Microsoft FrontPage 2.0">
 
<title>MirrorDataTest</title>
 
</head>
 
<body bgcolor="#0000FF" text="#00FFFF">
 
<h1>View lpbData Test</h1>
 
<form action="/scripts/Books/BUnleash/MirrorData.dll"
 
method="POST">
 
    <p>Enter some information: </p>
 
    <p><textarea name="SendData" rows="16" cols="74"></textarea></p>
 
    <p>Enter More Information</p>
 
    <p><input type="text" size="76" name="SendData"></p>
 
    <p><input type="submit" name="SendData" value="Submit"></p>
 
</form>
 
</body>
 
</html>
 

You can use this code to test the HttpExtensionProc that mirrors back the value of lpbData. Note that the HTML form contains two fields for entering text, the first called SendData and the second called MoreData. If you have only one field and you type Fast into it, the DLL would mirror back the following to the user:

SendData=Fast&SendData=Submit
 

If you have two fields, as in the HTML code shown here, then the following would be mirrored back if the first field contains Fast and the second contains Loose:

SendData=Fast&MoreData=Loose&SendData=Submit
 

If you break down this text into three separate fields, they would look like this:

SendData=Fast
 
&MoreData=Loose
 
&SendData=Submit
 

This text says, in effect, the SendData field has the word Fast in it, the MoreData field has the word Loose in it, and the SendData button is a Submit button.

Responding to a Submit Button

Assume that you have an HTML form with the code shown in Listing 26.3.

Listing 26.3. The HTML code for a Web page that uses ISAPI to interact with the user.

<html>
 
<head>
 
<title>Talking ISAPI Test</title>
 
</head>
 
<body bgcolor="#0000FF" text="#00FFFF">
 
<h1>Talking ISAPI Test</h1>
 
<p>Press this button to see more of the simple ISAPI test:</p>
 
<form action="/scripts/Books/BUnleash/IsapiTalk.dll"
 
method="POST">
 
    <p>Enter your name: <input type="text" size="20"
 
    name="SendName"></p>
 
    <p><input type="submit" name="SendName" value="Submit"></p>
 
</form>
 
</body>
 
</html>
 

This code will produce a form that contains a text area where the user can enter his or her name and a button called Submit. Given this form, you can expect the lpbData field to contain the following string, assuming the user enters the word Sammy in the name field:

SendName=Sammy&SendName=Submit
 

To understand what is happening here, note the BODY of the HTML statement composed on the server as reflected in the following excerpt from the SetUpResString function shown previously:

`<BODY>lpbData = %s </BODY>' +
 

If you study the code in the HttpExtensionProc function, you will see that it uses the Format routine to substitute the value of ECB.lpbData for the %s variable in the preceding piece of code. (If you don't understand how Format works, see the BCB documentation, or my references to this method in Chapter 3, "C++Builder and the VCL.")

After you get the information from the form in the lpbData parameter, you can parse it and return information to the user. For example, you could extract the number 23 from the preceding example and then square it and return it to the user. Doing so would in effect allow you to get information from the user, namely a number, perform a mathematical action on the number, and then return the result to the user. This means you're creating dynamic, interactive Web pages on-the-fly, which is the current sine qua non of Internet programming!

Listing 26.4 shows the complete code for a program that will reply to a user who enters his or her name into a page of a Web browser and submits it to the DLL. The HTML that accompanies this program is shown in Listing 26.5.

Listing 26.4. The code for the ISAPITalk DLL.

///////////////////////////////////////
 
// IsapiTalk.cpp
 
// Mirror back the information sent to an ISAPI DLL by the server
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#include <vcl\vcl.h>
 
#include <fstream.h>
 
#include <dir.h>
 
#pragma hdrstop
 
#include "..\..\utils\Httpext.h"
 
 
 
USERES("IsapiTalk.res");
 
 
 
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
 
{
 
  pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
 
  strcpy(pVer->lpszExtensionDesc, "C++ Builder ISAPI DLL");
 
  return (TRUE);
 
};
 
 
 
// Remember: IOStream deals with OS paths, not relative path to server!
 
BOOL GetResultString(AnsiString &Result, AnsiString Path)
 
{
 
  fstream InFile;
 
  AnsiString FileName(Path + "\\BUnleash\\TalkTest\\TalkReply.htm");
 
  char ch;
 
  char S[500];
 
 
 
  InFile.open(FileName.c_str(), ios::in, filebuf::openprot);
 
  if (!InFile)
 
  {
 
    // If we couldn't get file, then what directory were we in?
 
    Result = "<H>Error reading stream!</H>";
 
    FileName = ExtractFilePath(FileName);
 
    if (chdir(FileName.c_str()) == 0)
 
    {
 
      getcurdir(0, S);
 
      Result += S;
 
    }
 
    else
 
      Result += "Could not get file, nor find current directory!";
 
    return FALSE;
 
  }
 
  while (InFile.get(ch))
 
    Result += ch;
 
  return TRUE;
 
}
 
 
 
AnsiString Parse(AnsiString &S)
 
{
 
  int i = S.Pos("&");
 
  S.SetLength(i - 1);
 
  S = strrev(S.c_str());
 
  i = S.Pos("=");
 
  S.SetLength(i - 1);
 
  S = strrev(S.c_str());
 
  return S;
 
}
 
 
 
//HttpExtensionProc callback definition
 
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
 
{
 
  AnsiString Header("HTTP/1.0 200 OK\nContent-Type: text/html\nContent-Length:%d\nContent:\n\n");
 
  AnsiString ResultString;
 
  int resultLen;
 
 
 
  char *IsapiLogText = "ISAPI Talk";
 
  strcpy(pECB->lpszLogData,IsapiLogText);
 
  pECB->dwHttpStatusCode = 200;
 
 
 
  // build the HTML result string
 
  AnsiString S;
 
  if (pECB->lpbData != NULL)
 
  {
 
    S = (char *)pECB->lpbData;
 
    S = Parse(S);
 
  }
 
  else
 
    S = "Error occurred get lpbData field";
 
    
 
  AnsiString Date(DateToStr(Now()));
 
  AnsiString Time(TimeToStr(Now()));
 
 
 
  GetResultString(ResultString, pECB->lpszPathTranslated);
 
  ResultString = Format(ResultString, OPENARRAY(TVarRec, (S, Date, Time)));
 
  resultLen = ResultString.Length();
 
  Header = Format(Header, OPENARRAY(TVarRec, (resultLen)));
 
 
 
  pECB->WriteClient(pECB->ConnID, ResultString.c_str(), &(DWORD)resultLen, 0);
 
 
 
  return (HSE_STATUS_SUCCESS);
 
}
 
 
 
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
 
{
 
  return 1;
 
} 
 

 

Listing 26.5. The HTML file, called TalkReply.htm, used in the reply generated by the ISAPITalk DLL.

<html>
 
<head>
 
<title>This is a simple htm file</title>
 
</head>
 
<body bgcolor="#0000FF" text="#00FFFF">
 
<h1>Talking HTML Reply</h1>
 
<p>&nbsp;</p>
 
<p>Welcome, %s, to the talking HTML file!</p>
 
<p>It's remarkable that you should drop by today, %s, at %s (PST), as
 
I was just thinking about you. </p>
 
</body>
 
</html>
 

This program allows an HTML browser to work interactively with a Web server. Screen shots of before and after a query is made are shown in Figure 26.3 and Figure 26.4, respectively.

When you're working with this program, note that I assume you have placed the TalkReply.htm program in the following location relative to the root of your Web:

\\BUnleash\\TalkTest\\TalkReply.htm
 

The ISAPITalk DLL might receive the following string from the user who clicks the Submit button, asking that a number be squared:

SendName=Sammy&SendName=Submit
 


FIGURE 26.3. Preparing to query the ISAPITalk DLL.

 

FIGURE 26.4. The results of querying the ISAPITalk DLL.

Given this input, the preceding code would return the following string to the user across the Internet:

Welcome, Sammy, to the talking HTML file!
 
It's remarkable that you should drop by today, 2/15/97,
 
at 10:39:29 PM (PST), as I was just thinking about you.
 

In short, the user enters the string "Sammy", and the ISAPI DLL mirrors this information back and even adds in the current date and time as it is reported on the server side. This process sounds trivial, but the key issue here is that this activity is taking place dynamically on the Internet.

The function that parses the data sent by the user looks like this:

AnsiString Parse(AnsiString &S)
 
{
 
  int i = S.Pos("&");
 
  S.SetLength(i - 1);
 
  S = strrev(S.c_str());
 
  i = S.Pos("=");
 
  S.SetLength(i - 1);
 
  S = strrev(S.c_str());
 
  return S.
 
}
 

The code uses the strrev function from the Standard C Library to expedite the task of parsing out all the unnecessary data sent by the server.

The ISAPITalk program does not imbed any HTML code inside the C++ code that makes the DLL run. Instead, it reads in an HTML file and uses the VCL Format function to fill in the blank fields of the form shown in Listing 26.5.

The code for reading in the HTML file goes to considerable lengths to properly handle any errors that occur:

if (!InFile)
 
{
 
  // If we couldn't get file, then what directory were we in?
 
  Result = "<H>Error reading stream!</H>";
 
  FileName = ExtractFilePath(FileName);
 
  if (chdir(FileName.c_str()) == 0)
 
  {
 
    getcurdir(0, S);
 
    Result += S;
 
  }
 
  else
 
    Result += "Could not get file, nor find current directory!";
 
  return FALSE;
 
 
 
}
 

The issue here is that the file you want to load might be missing or might be in another directory than the one you suppose. The code shown here attempts to find the directory in which the DLL expects the HTML file to reside and to mirror that information back to the user on the off chance that an error occurs. This information can help you fix any broken links in your program without too much fussing around.

 

 


NOTE: The reason the ISAPITalk program reads in the HTML for the reply form from a file is simply that I don't like embedding HTML code inside C++ code. No technical reason prevents me from embedding HTML inside C++, but doing so does tend to be confusing. In particular, embedding the code makes it difficult for you to generate an attractive form that has just the proper look you want to produce. If you separate the HTML from your C++ code, you can use your favorite HTML editor to produce just the look you want.


The discussion of the ISAPITalk program has one final leg that needs to be completed before the race is done. This last portion of the journey involves getting the proper path to the HTML file that you read in while generating the reply form.

The Server Path Versus the OS Path

When you're writing ISAPI applications, you need to distinguish between the relative path you use when talking to the Web server and the absolute OS path you use when executing functions inside your DLL. Server paths should always be relative to the root of your Web, but OS paths are concerned with the current drive and directory.

If you embed the name of an HTML file in a hyperlink that is part of a Web path, you're dealing with relative server paths. If you call the C library chdir or getcurdir functions, then you're working with an OS path. For example, I need to pass an OS-based path to the function in the ISAPITalk program that reads an HTML file from disk.

Finding out the current OS path is relatively easy because ISAPI passes the absolute path to the root of the Web in the lpszPathTranslated field of the EXTENSION_CONTROL_BLOCK. For example, if the root of your Web is C:\WEBSHARE\WWWROOT, that path is passed to you in the lpszPathTranslated field.

The relative path you use in your Web pages is something that you usually determine while laying out the Web itself. Sometimes you have to refer to this path in your code. In particular, you will often reference this path if you're creating HTML on-the-fly. Most of the time, you simply have to determine the correct path by looking at the position of your files relative to the root of your Web.

Here is an example of a relative path used in an HTML file:

\\BUnleash\\TalkTest\\TalkReply.htm
 

Here is the OS path to the same file:

C:\WEBSHARE\WWWROOT\\BUnleash\\TalkTest\\TalkReply.htm
 

Whatever you do, don't ever embed full OS pathnames in either your HTML or your ISAPI DLLs. If you do, then you will have to edit your code and your HTML whenever you change the location of your Web site. That is much too much work. Furthermore, from a security point of view, telling the users of your Web any more than they have to know about the actual layout of your hard drive is probably not a good idea.

To find out more about this subject, you can examine the ISAPITalk DLL on the CD that accompanies this book. This program provides an example of finding out the exact path to a file on the server so that you can load it into your DLL.

That's most of what I want to say about ISAPI in this chapter. This information should be enough to get you up and running and having some fun with this great technology.

Retrieving Data with an ISAPI DLL

In this section you will see a simple example for retrieving data from an ISAPI DLL. The CGI examples featured later in this book go into more depth on database techniques to use in this type of program. You can easily convert the CGI code into code that can be used with ISAPI.

The sample DLL shown in Listing 26.6 retrieves all the data from the Country table in the BCDEMOS database and shows it to the user in an HTML table.

Listing 26.6. The IsapiData program shows how to retrieve data from a database and display it in a browser.

///////////////////////////////////////
 
// IsapiData
 
// Return Database rows from an ISAPI DLL
 
// Copyright (c) 1997 by Charlie Calvert
 
// Thanks to David Intersimone and Roland Fernandez
 
//
 
#include <vcl\vcl.h>
 
#include "..\..\utils\Httpext.h"
 
#pragma hdrstop
 
 
 
AnsiString TableToHtml(TDataSet *Table)
 
{
 
  int i;
 
  AnsiString Result;
 
 
 
  Result = "<TABLE BORDER>\n";
 
  for (i = 0; i < Table->FieldCount; i++)
 
  {
 
    Result = Result + Format("<TH>%s</TH>",
 
      OPENARRAY(TVarRec, (Table->Fields[i]->FieldName.c_str())));
 
  }
 
  
 
  while (!Table->Eof)
 
  {
 
    Result = Result + "<TR>";
 
    for (i = 0; i < Table->FieldCount; i++)
 
    {
 
      Result = Result + Format("<TD>%s</TD>",
 
        OPENARRAY(TVarRec, (Table->Fields[i]->AsString.c_str())));
 
    }
 
    Result = Result + "</TR>\n";
 
    Table->Next();
 
  }
 
  
 
  return Result + "</TABLE>\n";
 
}
 
 
 
 
 
AnsiString _stdcall _export GetData()
 
{
 
  TTable *Table;
 
  AnsiString Result;
 
 
 
  Table = new TTable(Application);
 
  Table->DatabaseName = "DBDEMOS";
 
  Table->TableName = "Country";
 
  Table->Open();
 
  Result = TableToHtml(Table); 
 
  Table->Close();
 
  delete Table;
 
 
 
  return Result;
 
}
 
 
 
BOOL WINAPI _export GetExtensionVersion(HSE_VERSION_INFO *pVer)
 
{
  pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
  strcpy(pVer->lpszExtensionDesc, "ISAPI Variables DLL");
  return (TRUE);
 
};
 
 
void TextWrite(AnsiString S)
 
{
 
  FILE *F;
  S = S + "\n";
  F = fopen("c:\\foo.txt", "w+");
  fprintf(F, S.c_str());


Next (Part 02) >>

 

 

VMS Desenvolvimentos

Diversas Dicas, Apostilas, Arquivos Fontes, Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++ Builder.

Voltar ao Site  

Voltar ao Index