Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Developing a Server Extension Application

To get things started, let's look at how to create an Internet Server Extension Application (ISA), which is not really a standalone application but a DLL that can be loaded by the Web server to process requests more efficiently than CGI scripts. You will see the entry points that your DLL must export in order to be an ISA, as well as how your extensions communicate with the client and the server.

To install an ISA on your server, you need only copy the ISA DLL to a directory that is accessible by the HTTP server. The functions of this extension then can be accessed by a client with a request something like this:

http://scripts/myISA.dll?SendList?param1=3

ISA Entry Points

The DLLs that you develop with ISAPI will interact with the HTTP server by way of three entry points that your DLL exports. The first of these, GetExtensionVersion(), is called by the server when it first loads the extension DLL, and it is used to report the version of ISAPI supported by the ISA. You may also choose to implement and export a TerminateExtension() function, which will be called before the extension DLL is unloaded, although this is not required. The final entry point that you must implement is HttpExtensionProc(), which will be called by the server to process requests to the ISA.

GetExtensionVersion()

Your extension DLL must export a GetExtensionVersion() function. This will be called by the server when it first loads your DLL, and it is intended to be used to report version information about your DLL to the server; however, you may also choose to do any other initialization that your DLL requires in this function.

Other than any initialization specific to your extension, the code for GetExtensionVersion() is fairly generic. You can pass a description of your ISA and the version of ISAPI that it supports back to the server. Most implementations will look something like this:

BOOL WINAPI GetExtensionVersion( HSE_VERSION_INFO *pVer)
{
    pVer->dwExtensionVersion = MAKELONG( HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
    strncpy(pVer->lpszExtensionDesc, "A Description of Your ISA Here",
                HSE_MAX_EXT_DLL_NAME_LEN);
    return TRUE;
}

If something goes wrong in any additional initialization that you perform in GetExtensionVersion(), you can return FALSE, which will abort the loading of your extension.

TerminateExtension()

Your ISA also may export an implementation of the TerminateExtension() function, although this is not required. This function is called whenever the server wants to unload the extension DLL and may be useful for cleaning up any dynamic structures or extra threads that you started in GetExtensionVersion(). Here is the prototype for TerminateExtension():

BOOL WINAPI TerminateExtension(DWORD dwFlags);

The dwFlags parameter will receive one of two values: When dwFlags is HSE_TERM_ADVISORY_UNLOAD, the server would like to unload your DLL to tidy up a bit. If you don't want to let the server unload your DLL, you can return FALSE. Otherwise, returning TRUE will allow the server to unload the DLL. On the other hand, if dwFlags is HSE_TERM_MUST_UNLOAD, your DLL is about to be unloaded whether or not you like it, so if you need to clean up, you'd better do it now.

HttpExtensionProc()

In most cases, the bulk of the code for your ISA will be in the HttpExtensionProc() function, which is called by the server for each request to the ISA. The following is the prototype for this function:

DWORD HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK *lpEcb);

At first glance, the prototype for HttpExtensionProc() looks quite simple, although the EXTENSION_CONTROL_BLOCK (ECB) structure is a bit complicated—it provides many different fields that are passed into your DLL, as well as several different fields that are returned to the server.

In addition, the ECB contains pointers to several different utility functions, provided by the server, that your DLL can use in processing the request. It is these functions that allow your ISA to receive additional information from the client and to send information back to the client.

You will be looking at the ECB in greater detail in just a bit, but for now, let's take a look at a simple example to see how the basics of the HttpExtensionProc() entry point work. The following implementation will return a very simple HTML page to the client for any requests made to the ISA:

#include <httpext.h>
#include <stdio.h>
#include <wininet.h>
DWORD WINAPI HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB)
{
    BOOL bRc;
    char strStatus[100] = "200 OK";
    DWORD dwMsgLen;
    DWORD dwError;
    char retBuf[1000];

    sprintf(retBuf, "<FONT COLOR=BLUE SIZE=4>Hi Mom!</FONT>\r\n");
    // Send HTTP headers back to the client
    bRc = pECB->ServerSupportFunction( pECB->ConnID,
                                    HSE_REQ_SEND_RESPONSE_HEADER,
                                    strStatus,
                                    NULL,
                                    (LPDWORD) "Content-Type: text/html\r\n\r\n");
    if(!bRc)
    {
        // An error occurred
        dwError = GetLastError();
        pECB->dwHttpStatusCode = HTTP_STATUS_SERVER_ERROR;
        return(HSE_STATUS_ERROR);
    }

    // Send response data to client
    dwMsgLen = strlen(retBuf);
    bRc = pECB->WriteClient(pECB->ConnID, retBuf, &dwMsgLen, HSE_IO_SYNC);
    if(bRc == FALSE)
    {
        dwError = GetLastError();
        // Do something with the error code...
        pECB->dwHttpStatusCode = HTTP_STATUS_SERVER_ERROR;
        return(HSE_STATUS_ERROR);
    }

    return(HSE_STATUS_SUCCESS);
} // end HttpExtensionProc
						

In this example, you use two of the callback functions provided by the Web server via pointers in the ECB. You use ServerSupportFunction() to send the HTTP headers for the response to the client, and then use WriteClient() to send the body of an HTML page that is returned to the client. You will look at both these functions in greater detail later in this chapter.

The return values for HttpExtensionProc() are used to tell the server about the status of the request and whether it can free up its resources for the session:

You also can specify additional information about the status of a request handled by your ISA by setting the value of the dwHttpStatusCode field of the ECB to one of the following values (defined in WinInet.h) before returning from HttpExtensionProc():

The Extension Control Block

The HTTP server passes requests to your extension DLL by passing a pointer to an EXTENSION_CONTROL_BLOCK (ECB) structure, which contains information about the request to your ISA. This structure also allows you to pass certain information back to the server and provides pointers to a set of helper functions that can be used to communicate with the client. Here is the structure:

typedef struct _EXTENSION_CONTROL_BLOCK {
    DWORD     cbSize;
    DWORD     dwVersion;
    HCONN     ConnID;
    DWORD     dwHttpStatusCode;
    CHAR      lpszLogData[HSE_LOG_BUFFER_LEN];
    LPSTR     lpszMethod;
    LPSTR     lpszQueryString;
    LPSTR     lpszPathInfo;
    LPSTR     lpszPathTranslated;
    DWORD     cbTotalBytes;
    DWORD     cbAvailable;
    LPBYTE    lpbData;
    LPSTR     lpszContentType;
    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;
						

General Request Parameters

The first group of fields in the ECB is used to send information from the server about the request. The cbSize field is used to give the total size of the structure, and dwVersion will hold the version of ISAPI that is being used. In addition, the server will pass a value in connID that can be used to identify the connection to the client making the request. connID should not be modified by your DLL but will be used in calls to the helper functions, as you will soon see.

Query Information

The ECB also passes pointers to four null-terminated strings that give information specific to the client request. The string at lpszMethod gives the HTTP method that was requested, such as GET or PUT. Any additional information passed for a query is provided in lpszQueryString. If a path was specified in the client request, it will be passed via lpszPathInfo. In addition, the HTTP server will translate the path to a directory on the local server, which is passed in lpszPathTranslated.

GetServerVariable()

Many other variables are available from the server via the GetServerVariable() function pointer passed in the ECB. Here is the prototype for GetServerVariable():

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

This function takes the connection ID (as passed in the ConnID field of the ECB) and one of the following constants used to select a value to retrieve (several of these values also are available directly from the ECB):

If the call to GetServerVariable() is successful, it will return TRUE and the requested data will be copied to lpvBuffer, with the length returned at lpdwSizeofBuffer. If the buffer passed in lpdwSizeofBuffer is too small for the returned value, the function will return FALSE, and a subsequent call to GetLastError() will return ERROR_INSUFFICIENT_BUFFER. The following example shows the use of GetServerVariable() to retrieve the server name:

    lSize = sizeof(strServerName);
    bRc = pECB->GetServerVariable(pECB->ConnID, "SERVER_NAME",
                strServerName, &lSize);

    if(!bRc)
        dwError = GetLastError();

Additional Request Data

A client request may include a block of data. If additional data is sent, the total amount of data sent will be passed to HttpExtensionProc() in the cbTotalBytes field. The first block of the actual data also is passed to your DLL in a buffer at lpbData. The length of the data available at lpbData is passed in the cbAvailable field of the ECB.

If more data has been sent by the client (that is, if cbTotalBytes is greater than cbAvailable), the rest of the data may be retrieved from the client by using the ReadClient() callback function, which is accessed via a pointer passed in the ECB. Here is the prototype for ReadClient():

    BOOL (WINAPI * ReadClient)  ( HCONN      ConnID,
                                  LPVOID     lpvBuffer,
                                  LPDWORD    lpdwSize );

When calling ReadClient(), you should pass the connection ID (from the ECB ConnID field) in hConn, a pointer to your receive buffer in lpvBuffer, and a pointer to the size of your buffer in lpdwSize. If the data is read successfully, ReadClient() will return TRUE, the data will be copied to lpvBuffer, and the actual size of the data read will be returned at lpdwSize. If an error occurs, ReadClient() will return FALSE, and you should call GetLastError() for a specific error code.

ServerSupportFunction()

The next callback function you will look at, ServerSupportFunction(), gives your ISA access to many different functions that the server can perform for you, ranging from sending HTTP headers, to setting up asynchronous I/O, to transmitting files. Here is the prototype for ServerSupportFunction():

BOOL (WINAPI * ServerSupportFunction)( HCONN hConn, DWORD dwHSERequest,
    LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType);

The hConn parameter should be passed the connection ID from the ConnID field of the ECB. The actual function that is performed by ServerSupportFunction() and how the remaining parameters are used are determined by the value of dwHSERequest, which can have one of the values in the following sections.

HSE_REQ_REFRESH_ISAPI_ACL

HSE_REQ_REFRESH_ISAPI_ACL causes the IIS to reprocess the discretionary access control list (DACL) for a particular ISAPI extension's DLL. This function can also be used to reprocess another extension's DACL, if security permissions allow.

HSE_REQ_IS_KEEP_CONN

HSE_REQ_IS_KEEP_CONN can determine the Keep-Alive status of the active connection.

HSE_REQ_GET_IMPERSONATION_TOKEN

HSE_REQ_GET_IMPERSONATION_TOKEN allows the retrieval of a handle to the impersonation token that the request is implementing.

HSE_REQ_ABORTIVE_CLOSE

HSE_REQ_ABORTIVE_CLOSE will request to the IIS to use an abortive shutdown sequence when closing the TCP/IP connection socket. It generally performs a cleaner and elegant close.

HSE_REQ_GET_CERT_INFO_EX

HSE_REQ_GET_CERT_INFO_EX will specify the certificate context for the first certificate in the certificate chain of the client.

HSE_REQ_SEND_URL_REDIRECT_RESP

A URL redirect (302) message is sent to the client. You should pass the new URL string in lpvBuffer, and its size should be passed in lpdwSize.

HSE_REQ_SEND_URL

HSE_REQ_SEND_URL sends data to the client, based on a URL that specifies data local to the server. The null-terminated URL string is passed by lpvBuffer, and a pointer to its length should be passed in lpdwSize.

HSE_REQ_SEND_RESPONSE_HEADER

HSE_REQ_SEND_RESPONSE_HEADER sends an HTTP server response to the client. lpvBuffer should point to an HTTP status string—for example, 200 OK. You also may append additional HTTP headers to the response by passing a pointer to a null- terminated string in the lpdwDataType parameter.

This function is being deprecated for a future version of IIS. The preferred function to use is HSE_REQ_SEND_RESPONSE_HEADER_EX.

HSE_REQ_SEND_RESPONSE_HEADER_EX

This is the same as HST_REQ_SEND_RESPONSE_HEADER but more advanced. You can use this function to send a complete HTTP response header to the client browser, including the server version, message time, HTTP status, and MIME version. Also, when used with HST_SEND_HEADER_EX, this support function can set the lengths of the header and status strings, as well as specify that the connection should be kept open.

HSE_REQ_MAP_URL_TO_PATH

HSE_REQ_MAP_URL_TO_PATH translates a logical path to a physical path on the server. The logical path is passed in a buffer at lpvBuffer, and a pointer to the length of the buffer is passed in lpdwSize. Upon return, the translated path is written at lpvBuffer, and the DWORD at lpdwSize is updated to hold the size of the string returned.

This function is being deprecated for a future version of IIS. The preferred function to use is HSE_REQ_MAP_URL_TO_PATH_EX.

HSE_REQ_MAP_URL_TO_PATH_EX

HSE_REQ_MAP_URL_TO_PATH_EX is an improved version of HSE_REQ_MAP_URL_TO_PATH. This function enables you to map a logical URL path to a physical one. This permits the ability to collect different types of attributes related to the physical path.

HSE_REQ_DONE_WITH_SESSION

HSE_REQ_DONE_WITH_SESSION is used to tell the server when the ISA is finished with a session. You will look at how this is used when you look at asynchronous operations.

HSE_REQ_IO_COMPLETION

HSE_REQ_IO_COMPLETION sets up a callback function for the completion of asynchronous operations. You will look at this in more detail in just a bit.

HSE_REQ_TRANSMIT_FILE

HSE_REQ_TRANSMIT_FILE tells the server to transmit a file to the client. lpvBuffer points to an HSE_TF_INFO structure. (See the section "Sending Files," later in this chapter.)

HSE_REQ_GET_SSPI_INFO

HSE_REQ_GET_SSPI_INFO retrieves information about a secure connection. lpvBuffer receives the context handle, and *lpdwDataType receives the credential handle.

HSE_APPEND_LOG_PARAMETER

HSE_APPEND_LOG_PARAMETER can be used to write custom log strings to the log record. The log string will be obtained from lpvBuffer and be appended to the log.

HSE_REQ_ASYNC_READ_CLIENT

Use HSE_REQ_ASYNC_READ_CLIENT to attempt to read from the client asynchronously. When the read has completed, the ISA will call a specified callback function that you must set using the ServerSupportFunction request HSE_REQ_IO_COMPLETION.

HSE_REQ_CLOSE_CONNECTION

HSE_REQ_CLOSE_CONNECTION sends a request to close the current client socket connection. It will perform this action even if there is an asynchronous read pending. Upon using this function, you have to wait for the ISA to call the asynchronous I/O function before ending the session with HSE_REQ_DONE_WITH_SESSION. HSE_REQ_CLOSE_ CONNECTION will close the client socket connection immediately, but because ISA has to deal with threads in the thread pool, it takes a small amount of time for the connection to be removed completely.

Important: After you use the HSE_REQ_CLOSE_CONNECTION server support function to close a connection, you must wait for IIS to call the asynchronous I/O function (specified by HSE_REQ_IO_COMPLETION) before you end the session with HSE_REQ_DONE_WITH_ SESSION. HSE_REQ_CLOSE_CONNECTION closes the client socket connection immediately, but IIS takes a small amount of time to handle the threads in the thread pool before the connection can be removed completely.

WriteClient()

The WriteClient() function pointer provided in the ECB is used to send a block of data to the client that made the request to your ISA. In many cases, this will be HTML data, although it could be any other data you want to send to the client. The following is the prototype for WriteClient():

BOOL WriteClient( HCONN ConnID, LPVOID Buffer,
    LPDWORD lpdwBytes, DWORD dwReserved);

You should pass the value from the ConnID field of the ECB for the ConnID parameter. The value passed in Buffer should point to your data, and lpdwBytes should point to a DWORD with the length of the data to send. The dwReserved field can be used to specify how the call should complete. If dwReserved is HSE_IO_SYNC, the call will complete synchronously, blocking until it is finished. If dwReserved is set to HSE_IO_ASYNC, the call will return immediately, and the operation will complete asynchronously. You will look at asynchronous operations in the next section.

If WriteClient() completes successfully, it will return TRUE, and the DWORD at lpdwBytes will be updated to reflect the actual number of bytes sent. If an error occurs, WriteClient() will return FALSE, and you should call GetLastError() for a specific error code. The following example shows how you can send data (in this case, a null- terminated string of HTML) to the client:

    // Send response data to client
    dwMsgLen = strlen(retBuf);
    bRc = pECB->WriteClient(pECB->ConnID, retBuf, &dwMsgLen, HSE_IO_SYNC);
    if(bRc == FALSE)
    {
        dwError = GetLastError();
        // Do something with the error code...
        pECB->dwHttpStatusCode = HTTP_STATUS_SERVER_ERROR;
        return(HSE_STATUS_ERROR);
    }
						

Sending Files

In addition to the WriteClient() callback function, you can send a file to the client by calling ServerSupportFunction(), with dwHSERequest set to HSE_REQ_TRANSMIT_FILE. This will use the WinSock TransmitFile() function to send a file more quickly than WriteClient().

When calling ServerSupportFunction(), you should set dwHSERequest to HSE_REQ_TRANSMIT_FILE, and lpvBuffer should point to an HSE_TF_INFO structure, shown in the following:

typedef struct _HSE_TF_INFO  {
    PFN_HSE_IO_COMPLETION   pfnHseIO;
    PVOID  pContext;
    HANDLE hFile;
    LPCSTR pszStatusCode;
    DWORD  BytesToWrite;
    DWORD  Offset;
    PVOID  pHead;
    DWORD  HeadLength;
    PVOID  pTail;
    DWORD  TailLength;
    DWORD  dwFlags;
} HSE_TF_INFO, * LPHSE_TF_INFO;

The pfnHseIO and pContext fields are used to pass a callback function and context value to be used when the operation completes. If these values are not specified, the values set by a call to ServerSupportFunction(), using HSE_REQ_IO_COMPLETION, will be used. However, if values are passed in the pfnHseIO and pContext fields, these will override those set using HSE_REQ_IO_COMPLETION for the duration of this operation. You will see exactly how these are used in the next section.

The file handle passed in hFile must have been previously opened with CreateFile(), using the FILE_FLAG_SEQUENTIAL_SCAN and FILE_FLAG_OVERLAPPED flags.

You should specify the number of bytes to send in the BytesToWrite field (0 will send the whole file) and also may specify a beginning offset from the start of the file in Offset.

Optionally, you can have the server attach HTTP headers to your file by specifying the HSE_IO_SEND_HEADER flag in the dwFlags field. This will build an HTTP header for the status code string specified in the pszStatusCode field—for example, 200 OK. If you use this option, you should not also use the HSE_REQ_SEND_HEADERS operation of ServerSupportFunction() to send headers.

You also can specify additional data blocks to be sent before and after the data from the file by setting pHead and pTail to point to appropriate buffers and specifying the lengths of the data in HeadLength and TailLength.

You also may include HSE_IO_DISCONNECT_AFTER_SEND in the dwFlags field, which tells the server to disconnect the client connection when the file transfer is complete. If you do not specify this flag, you need to notify the server when you are finished with the session by calling ServerSupportFunction() with HSE_REQ_DONE_WITH_SESSION.

Asynchronous Operations

ISAPI supports asynchronous completion for the WriteClient() function and the HSE_REQ_TRANSMIT_FILE operation provided by ServerSupportFunction(). Both of these functions can complete asynchronously by calling a callback function that you specify by calling ServerSupportFunction() for the HSE_REQ_IO_COMPLETION operation. This enables you to specify a callback function in the lpvBuffer parameter and a context value in lpdwDataType to be used in subsequent asynchronous operations. The code to set up a callback function would look something like this:

BOOL bRc;
bRc = pECB->ServerSupportFunction(pECB->ConnID,HSE_REQ_IO_COMPLETION,
                                    MyCompletionFunc, NULL, (DWORD *)0x123);

Your ISA now can begin asynchronous operations with either the WriteClient() function or ServerSupportFunction(), with a dwHSERequest value of HSE_REQ_TRANSMIT_FILE.

After you have initiated an asynchronous operation from within your HttpExtensionProc() callback, you can return a status of HSE_STATUS_PENDING from your HttpExtensionProc() function. After the operation completes, you should call ServerSupportFunction() with a dwHSERequest of HSE_DONE_WITH_SESSION to notify the server that you are finished with the session.

The I/O Completion Callback

The callback function that is installed with the previous code should have the following prototype:

VOID WINAPI MyIoCompletionFunc(EXTENSION_CONTROL_BLOCK * pECB,
    PVOID pContext, DWORD cbIO, DWORD dwError );

Whenever an asynchronous operation completes, the server will make a call to the callback function that is installed. The server will pass a pointer to the ECB (pECB) for the session that started the I/O operation, the context value (pContext) that was passed with HSE_REQ_IO_COMPLETION (or the HSE_TF_INFO structure), the number of bytes transferred (cbIO), and an error code (dwError).

Your callback function then can do any additional processing that is necessary. You also can initiate an additional asynchronous operation. When you are finished with all the processing for the session, you should call ServerSupportFunction() with a dwHSERequest value of HSE_DONE_WITH_SESSION so that the server can close the connection and free the resources it has allocated for the session.

The following example shows a callback function that simply tells the server that you are finished with a session when the asynchronous operation completes:

VOID WINAPI MyCompletionFunc(EXTENSION_CONTROL_BLOCK *pECB,
                             PVOID pContext, DWORD cbIO, DWORD dwError)
{
    pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_DONE_WITH_SESSION,
                                NULL, NULL, NULL);
} // end MyCompletionFunc
							

Writing to the Server Log

Your ISA can send data to the server's log file by writing a string to the buffer pointed to by the lpszLogData field of the ECB. lpszLogData points to a buffer of size HSE_LOG_BUFFER_LEN. If you write a null-terminated string to this buffer, the string will be written to the server's log file when you return from HttpExtensionProc(). The following example shows how you can use this to append your own string to the entry the server writes to the log for a request:

// Add Entry to Log File
strcpy(pECB->lpszLogData, "My Additional Log Text.");

Exception Handling in ISAs

ISAPI extension DLLs gain a significant performance advantage over CGI scripts, because they are part of the Web server's process and do not require additional process startup time. However, this also introduces the potential for additional problems. In many cases, if your ISA produces a GPF or crashes for some other reason, the whole server will crash.

Because of this, you should be careful to handle any exceptions that may arise from your ISA. You should enclose any potentially risky code with the __try/__except mechanism. It's not a bad idea to enclose the entire body of each of your callback functions in a __try block. This way, problems that arise in your ISA will not affect the rest of the Web server.

You should take the same precautions for ISAPI filter DLLs, which you will look at later.

Debugging Your ISA

Your Internet server extension can be debugged just like any other DLL. However, some extra setup is required to run the Web server as a standalone executable for debugging rather than as a service.

First of all, you need to have access to a server for debugging—trying to do debugging on a production server usually doesn't go over very well.

Second, you need to stop all three of the IIS services. Even though you will be using only the HTTP service, you also must stop the Gopher and FTP services. You can do this from the Services applet in the Control Panel or with the Internet Service Manager that was installed with IIS. In Windows 2000, this can be found in the Administrative Tools folder under the Control Panel.

Next, you need to specify the executable for the debug session in the debug settings for your ISA project. This should be the full path to the IIS server, which generally is something like this:

c:\winnt\system32\inetsrv\inetinfo.exe

You also must specify the following in the program arguments:

-e W3Svc

Now, when you start debugging your ISA project, the Internet Information Server will be started, and any breakpoints that you have set for your ISA will halt execution and allow you to debug the code in your ISA DLL.

Converting from CGI to ISAPI

If you have existing CGI scripts that you want to convert to ISAPI extension DLLs, you can use the following basic steps.

First of all, you convert your CGI executable to an ISA DLL. For the most part, the main() function of your CGI executable can simply be pasted into the HttpExtensionProc() of the ISA project, with the additional changes listed later. You also need to add a GetExtensionVersion() entry point.

Extension DLLs must be thread-safe, whereas CGI scripts generally do not need to be. You should make sure that any critical sections or shared data are properly protected.

CGI scripts receive data from the client by reading from stdin. In an ISA, this should be changed to read data from the lpbData buffer passed in the ECB and by using the ReadClient() function.

Various information from the server is passed to CGI scripts through environment variables, which are read with getenv(). For an ISA, you should replace these calls with calls to GetServerVariable().

CGI scripts send data back to the client by writing to stdout. In most cases, this should be replaced with calls to WriteClient() in ISA applications, although there are some special cases.

When sending completion status, instead of writing Status: NNN ... to stdout, you should use the HSE_REQ_SEND_RESPONSE_HEADER operation of ServerSupportFunction() or WriteClient().

Also, when sending a redirect response, instead of writing either the Location: or URI: header to stdout, you should use ServerSupportFunction() with the HSE_REQ_SEND_URL operation for local URLs, or the HSE_REQ_SEND_URL_REDIRECT_RESP operation for remote (or unknown) URLs.

Share ThisShare This

Informit Network