ExposureRoom Home
  Log in Sign Up
Shiv's Website
Shiv Kumar
United States
Friends: 77
Focused on : 2
 
       
                                                             

Writing ISAPI DLLs "By Hand"

Not Rated YetNot Rated YetNot Rated YetNot Rated YetNot Rated Yet0votes
January 07, 2008 12:05 PM  Views:256   Favorited:0 Comments:0
Filed Under:  Programming
Tags:  Delphi, ISAPI
 
Since an ISAPI application is a DLL have you ever wondered what it would be like to build an ISAPI DLL just like any other DLL ? Can be do it ?

Sure we can ! This is Delphi remember ? A DLL is one of the basic things Delphi does. All we really need to know is what functions we need to export from the DLL. Once we implement these functions we have a DLL that IIS can load into its process space and call the required functions.

Getting Started

At the bare minimum there are two functions we need to export:
  1. GetExtensionVersion
  2. HttpExtensionProc
The DLL project source will look like this:
library ByHand;
uses
  SysUtils,
  Classes,
  ISAPI2;
{$R *.RES}
const
  crlf = #13#10;
function GetExtensionVersion(var Ver : THSE_VERSION_INFO): Boolean; stdcall;
begin
  Ver.dwExtensionVersion := 1;
  Ver.lpszExtensionDesc := 'ISAPI By Hand Example 1.0';
  Result := True;
end;
function HttpExtensionProc(var ECB : TEXTENSION_CONTROL_BLOCK): LongInt; stdcall;
var
  WriteClient   : TWriteClientProc;
  Content       : string;
  ContentLength : Cardinal;
begin
    { Get the callback function }
    @WriteClient := @ECB.WriteClient;
    Content :=
      'HTTP/1.0 200 OK'   crlf   crlf  
      '<html>'   crlf  
      '<head>'   crlf  
      '<title>ISAPI By Hand</title>'   crlf  
      '<body>'   crlf  
      '<h2>ISAPI by "Hand"</h2>'   crlf  
      '<h3>'  FormatDateTime('dddd dd mmm yyyy hh:nn:ss' , Now)   '</h3>'   crlf  
      '</body>'   crlf  
      '</head>'   crlf  
      '</html>'   crlf;
    ContentLength := Length(Content);
    { Send To Client }
    WriteClient(ECB.ConnID, PChar(Content), ContentLength, 0);
end;
exports
  GetExtensionVersion,
  HttpExtensionProc;
begin
end.
Isn't that simple! Well, after all it's just a regular DLL. How complex did you expect it to be ? The truth is that this is a Very simple ISAPI. It has none of the functionality that the Web Broker Technology provides us with. So why would we want to do it "By Hand" then ? There is one advantage to doing it by hand. Size ! This simple ISAPI DLL weighs in at 59K ! Compare this with an ISAPI built using the Web Broker suite starting out at 322K. Of course, both DLLs are standalone and need no supporting files.

This is not to say that we should go this route. There is a lot of built in functionality that the Web Broker technology provides us with that would be hell for even for an expert Delphi programmers to code. This (by Hand) by the way is how a VC programmer would go about building an ISAPI DLL. The point of this exercise is just to know, how we would do it if we had to. Besides, sometimes we would like some basic/simple functionality built into an ISAPI without the overhead of the support that the Web Broker suite provides. At 59K there could be lots of little ISAPI DLLs floating around that could be doing some really simple tasks.

Notice however that we've added the ISAPI2 interface unit to our uses clause. In the good old Delphi 1 and 2 days, we would had to translate the httpext.h C header file to Pascal by ourselves to be able to write ISAPI DLLs to begin with. Borland R&D has done this for us already (since Delphi 3) and so we just need to include it in the uses clause.

Some Theory

ISAPI extensions can accomplish a wide variety of tasks, but in order to be used by IIS, they must provide a standard interface. Each extension must implement, and export from the DLL, two primary functions:
  1. GetExtensionVersion
  2. HttpExtensionProc
A third function, TerminateExtension, is considered optional.

Request Processing Sequence ISAPI extension request processing is fairly straightforward. The following events occur when IIS receives a request that IIS maps to an ISAPI extension:

  1. IIS loads the DLL, if it is not already in memory. When the DLL is loaded into memory, the optional DLL entry/exit function (usually DllMain) will be called automatically, by Windows. Next, IIS calls the extension's GetExtensionVersion entry-point function.
  2. IIS performs minor preprocessing on the incoming request.
  3. IIS creates and populates an EXTENSION_CONTROL_BLOCK, which IIS will use to pass request data and callback function pointers to the extension.
  4. IIS calls the ISAPI extension's HttpExtensionProc function, passing a pointer to the EXTENSION_CONTROL_BLOCK structure that was created for this request.
  5. The ISAPI extension performs whatever actions it was designed to perform. This can include reading more data from the client, as in a POST operation, or writing headers and data back to the client.
  6. For synchronous operations, the extension indicates to IIS that it has finished processing the request by simply exiting the HttpExtensionProc function.
  7. IIS performs cleanup on the connection used for the request, and then closes the connection.
  8. Once the ISAPI extension is no longer needed, IIS calls the TerminateExtension function, if the extension provides one. This function is commonly used by extensions to perform cleanup.
It is important to note that GetExtensionVersion is not called for every request. In contrast, HttpExtensionProc is called exactly once for each and every request for the ISAPI extension. It is also important to note that exactly one EXTENSION_CONTROL_BLOCK structure is used for each incoming request.

If IIS is configured so that IIS extensions are cached, TerminateExtension will not be called until the IIS Web server is shutdown or restarted.

The Next Step

The next step would be to add some functionality to this ISAPI to make it just a little useful. The functionality we'll add is:
  1. Error Handling
  2. Some form of "action" handling
The source code for the modified project is listed below:
library ByHand;
uses
  SysUtils,
  Classes,
  ISAPI2;
{$R *.RES}
const
  crlf = #13#10;
function SendErrorPage(E: Exception) : string;
begin
  Result :=
    'HTTP/1.0 200 OK'   crlf   crlf  
    '<html>'   crlf  
    '<head>'   crlf  
    '<title>ISAPI ERROR</title>'   crlf  
    '<body>'   crlf  
    '<h2>'   E.ClassName   ': '   E.Message   '</h2>'   crlf  
    '</body>'   crlf  
    '</head>'   crlf  
    '</html>'   crlf;
end;
function GetDefaultPage : string;
begin
  Result :=
    'HTTP/1.0 200 OK'   crlf   crlf  
    '<html>'   crlf  
    '<head>'   crlf  
    '<title>ISAPI By Hand</title>'   crlf  
    '<body>'   crlf  
    '<h2>ISAPI "By Hand" - Default Page</h2>'   crlf  
    '<h3>'  FormatDateTime('dddd dd mmm yyyy hh:nn:ss' , Now)   '</h3>'   crlf  
    '</body>'   crlf  
    '</head>'   crlf  
    '</html>'   crlf;
end;
function GetHomePage : string;
begin
  Result :=
    'HTTP/1.0 200 OK'   crlf   crlf  
    '<html>'   crlf  
    '<head>'   crlf  
    '<title>ISAPI By Hand - Home Page</title>'   crlf  
    '<body>'   crlf  
    '<h2>ISAPI "By Hand" - Home Page</h2>'   crlf  
    '<h3>'  FormatDateTime('dddd dd mmm yyyy hh:nn:ss' , Now)   '</h3>'   crlf  
    '</body>'   crlf  
    '</head>'   crlf  
    '</html>'   crlf;
end;
{==============================================================================}
{=============================Export functions=================================}
{==============================================================================}
function GetExtensionVersion(var Ver : THSE_VERSION_INFO): Boolean; stdcall;
begin
  Ver.dwExtensionVersion := 1;
  Ver.lpszExtensionDesc := 'ISAPI By Hand Example 2.0';
  Result := True;
end;
function HttpExtensionProc(var ECB : TEXTENSION_CONTROL_BLOCK): LongInt; stdcall;
var
  WriteClient   : TWriteClientProc;
  Content       : string;
  ContentLength : Cardinal;
begin
  { Get the callback function }
  @WriteClient := @ECB.WriteClient;
  try
    { Default Action if PathInfo is empty or "/" }
    if (ECB.lpszPathInfo = EmptyStr) or
       (ECB.lpszPathInfo = '/') then
      Content := GetDefaultPage
    else if AnsiStrIComp(ECB.lpszPathInfo,'/home') = 0 then
      Content := GetHomePage;
    ContentLength := Length(Content);
    { Send To Client }
    WriteClient(ECB.ConnID, PChar(Content), ContentLength, 0);
    Result := HSE_STATUS_SUCCESS;
  except
    on E: Exception do
    begin
      Content := SendErrorPage(E);
      ContentLength := Length(Content);
      WriteClient(ECB.ConnID, PChar(Content), ContentLength, 0);
      Result := HSE_STATUS_ERROR;
    end;
  end;
end;
exports
  GetExtensionVersion,
  HttpExtensionProc;
begin
end.
We've added three functions to the previous listing:
function SendErrorPage(E: Exception) : string;
function GetDefaultPage : string;
function GetHomePage : string;
These functions are not exported from the DLL but in fact are used internally as support functions. Examining the HttpExtensionProc function, it should be clear, how we've implemented Error Handling and a basic form of "action" handling.

Sure, we could improve this code. Build our own Framework for ISAPI DLLs with a slew of classes etc. In time, if we're really good, we'll arrive at the Web Broker technology ! So in this case, we're keeping it simple but at the same time functional.

Comments have been Disabled for this post





Menus

Theme

Privacy Policy  |  Terms Of Service  |  Contact Us  |  Support  |  Help/FAQs  |  News