1. Tech
Zarko Gajic your About Delphi Guide About Delphi Programming
From Zarko Gajic, your Guide to Delphi Programming

ISAPI tutorial for Delphi developers
A brief introduction to web-broker technology; Web Actions; The first ISAPI application; TWebRequestObject; TPageProducers; Database enabled applications, and more.

Article submitted by: Hadi Hariri

Delphi's Web-broker technology allows you to create four different types of web server applications:

  • CGI Standalones
  • Win-CGI

    ISAPI/NSAPI are virtually the same thing. One is Microsoft specification and the other is Netscapes. ISAPI stands for Internet Server Application Programmers Interface. From now one we will refer to both ISAPI/NSAPI as just ISAPI. NSAPI will actually disapear soon becoming ISAPI the standard.

    NOTE: Don't confuse the terms Web Server with Web Server Application (or Extension). The Web Server is a HTTP server that you buy (or get free) to host your web sites. Examples of these are Internet Information Server from Microsoft and Apache. A Web Server Application (or Extension) is a sort of add-on to your Web Server that provides additional functionality and facilites. I will use the terms application/extension interchangably in this tutorial.

    CGI's are basically the same as the ones you have seen on the Internet for so long. They read information from the standard input and write to the standard output.

    A Win-CGI receives information from a INI file written by the server, write the result back to this file and the server then sends it to the client.

    Delphi doesn't do anything magical with this technology. It doesn't mean that if you don't use it you can't create ISAPI's or CGI's. You could create a a webserver app without Delphi's web-broker technology, but let's put it this way: 'it makes life much much easier'. What the web-broker does (a part from other things) is encapsulate the requests and responses in objects that are very intuitive and easy to use, thus making the communication much easier.

       Difference between ISAPI's and CGI's
    This tutorial will mainly focus on creating ISAPI's. However, nearly everything mentioned is applicable to CGI's. The difference between them is that:

    An ISAPI is a dynamic link library (dll). As like any other dll, it is loaded into memory and stays there until it is no longer needed. What does this mean? Well for one thing, the load time is smaller; it only loads once. This reduces response time for a client request. CGI's on the other hand are like normal executables. Everytime a request is made, the exe is loaded, the request is served and then the executable is unloaded from memory. This makes everything slower.

    It's not all benefits though. ISAPI's have their disadvantages. Let's take a look at the TISAPIApplication and see what these disadvantages are.

    Each time a request is made, a new instance of the webmodule is created. This also creates a new instance of all the objects the webmodule contains. If you declare a variable in the webmodule as global, all requests to your webserver application will be refering to the same variable. Imagine this scenario:

    1. A client makes a request to your webserver application and sets the global variable x to value 10.
    2. While the first client is still waiting for a response, another client makes a request and set the value of the variable x to 20.
    3. Now the first client needs the value of x to return a response. It reads it and instead of retrieving the value 10 as it set it, it instead retrieves 20.

    Conclusion: DON'T USE GLOBAL VARIABLES. Every request creates a new instance of the webmodule and the objects it contains. This does not include global variables. It doesn't matter whether you declare it as global, in the private or public section, it is still global to all requests.

    So, the main disadvantage to ISAPI's over CGI's is that you have to be carefull with the code you write. You can't use global variables just like that. There are work-arounds to this such as using some kind of unique access to them (critical sections, mutexes or semaphores).

    I read that you can make an existing application web-enabled very easily, is this true?

    You might have read that Delphi's RAD power let's you make your application web-enabled. This is true to an extent. However, don't ever think that by just dropping a component, viola, you are web-enabled. No. What Delphi offers you is a component called the TWebDispatcher. Basically what this component does is convert a HTTP request into a webaction. We will look at what a web action is later. A TWebModule already has a TWebDispatcher component. You do not need to add one ( you can't drop a TWebDispatcher into a TWebModule).

    Therefor a TWebmodule is a TDataModule + TWebDispatcher. What they say when they tell you that you can make an existing application web-enabled, is that if you have a TDataModule with non-visual components, bussiness rules, etc. you can drop a TWebDispatcher onto it and make a webserver application. The TWebDispatcher will take care of converting the HTTP requests into webactions. But don't think for one minute, that in some magical way all the forms in your application will appear in the clients browser.

       What is CacheConnections?
    When you make a ISAPI application, the Application is instantiated to a descendant of TWebApplication. This gives you a property called CacheConnections. It's of boolean type and what it does is that if you set it to True, saves inactive web-modules in the cache. This allows a new request to be served without having to create a new instance of the webmodule again. It uses a cached one. However, changes made to the contents of the webmodule might effect the new request. This implies you having to initialize what you need on each request. If you set CacheConnections to False, every request to the web server application will create the webmodule, serve the request and then destroy it. This is the same behaviour as a CGI and you can see that it takes one of the major benefits away from ISAPI's. By default CacheConnections is set to True.

       What is an ISAPI filter and what is an ISAPI extension?
    Think of an ISAPI filter as just that: a filter. It sits between your webserver and the client. Everytime a client makes a request to the webserver, it is passed through this filter.

    An ISAPI extension on the other hand, is similar to a normal application. You have to call it to activate it. This tutorial focuses on the latter. For information about creating ISAPI filters, look at thelinks. in the overview.

    HTTP is stateless

    Rember this phrase. It is very important to understand what this means before starting to program internet applications:

    1. A client makes a request to your webserver application (extension). The server replies with a response.
    2. The same client makes a new request. The server does not know what request was made previously by the same client or what state the client was left in.

    There are ways to work around this. We will see how to keep information with the use of fat urls, cookies and hidden fields. But always remember this. You don't know what state the client was in. Read it!

    I think I should add a bit more than the previous statement to this chapter. To use this tutorial, you will have to have a web-server installed on your computer and also Delphi 4 Client/Server or the Professional version with the Webbroker add-on.Some chapters require Delphi 5 (Professional or Enterprise). I have tested these examples with PWS for Windows 98 and IIS for Windows NT Workstation and Server. My system is Windows Workstation and IIS 4.

    By default IIS creates a directory called
    and another one called

    The first one contains all the documents that will be available on your webserver (html, gifs, jpegs, etc). The second one is where you can place the dll's you have created. You can replace these directories by any other ones. If you are using NT, make sure that you have assigned the correct permissions on each directory, both at NTFS level and in the IIS. In particular:

    C:\Inetpub\wwwroot has to have at least read permissions for the IUSR_MACHINENAME user.

    C:\Inetpub\scripts has to have execute permissions for the IUSR_MACHINENAME user.

    In NT, a request to the webserver is executed under a user. This user by default is IUSR_MACHINENAME, where MACHINENAME is the name of your computer.

    What do I do with the source files?
    Place the dll generated in the scripts directory and the html files in the wwwroot directory (unless otherwise stated). You then call the dll with the html files.

    Debugging an ISAPI
    As with any other application, the best way to learn and find out what is going on is to debug, and see hands-on what each line of code is doing. And as with any other application, you can also debug an ISAPI. However, depending on the webserver, there are different things you would have to do. Here are the basic steps for PWS on Windows 95/98 and IIS on Windows NT WS/Server.

    PWS on Windows 95/98
    1. Open your project.
    2. Set the following the in the Run Parameters:

    Run: \inetinfo.exe
    Parameters: -e w3svc

    3. Now, set your breakpoints and click on Run. Make sure that your dll is located in the scripts directory.

    4. Call the dll from your browser.

    IIS on Windows NT WS/Server
    Debugging in IIS on NT is the same as in Windows 95/98 and PWS. However, there is one difference. In NT IIS runs as a service. To be able to debug, you will have to make some registry changes and some permission changes.

    1. Run the User manager and change the rights for the user that is going to debug (the user that you log on to NT). Give this user the following permissions

    Log on as a Service
    Act as part of the operating system
    Generate security audits

    2. Next step, run the following registry file to make some changes. This will flick a few switches to allow the IIS to run as a process instead of a service. Remember, if you want to undo this, run the other registry file:

    Run as a Process (to be able to debug run this one)
    Run as a Service

    If you have installd Option Pack 4 with SDK you should be able to find these files on your system.

    3. Stop the WWW publishing service, FTP service and the IIS admin, and make the startup of all them manual.

    4. Restart the computer.

    5. Open your project.

    6. Set the following the in the Run Parameters:

    Run: \inetinfo.exe
    Parameters: -e w3svc

    7. Now, set your breakpoints and click on Run. Make sure that your dll is located in the scripts directory.

    8. Call the dll from your browser.

    Before we actually start programming, let's take a brief look at how to start an ISAPI application and what Delphi gives us.

    To start a new ISAPI project, in the File menu, click on New and then choose Web Server Application.

    Delphi will then present you with a choice of three types of web server applications. In our case we are going to choose the default (ISAPI/NSAPI).

    New Web Server Application

    Once you click on Ok, a new project will be opened. By default a new form is created. This form is the main starting point of your ISAPI application. It is a TWebModule. As mentioned in the previous chapter, the TWebModule consists of a TDataModule + TWebDispatcher. The first thing you will see is that, as with datamodules, the webmodule has very few properties and events. In particular, it consists of the following:


    ·Actions These are the WebActions. We will take a look at them later.
    ·Name, OldCreateOrder,Tag Nothing new. Same as other component properties.


    ·AfterDispatch This event is fired after a call has been made to one of the Webactions
    ·BeforeDispatch This event is fired before a call has been made to one of the Webactions
    ·OnCreate/OnDestroy As with other forms, you have the OnCreate/OnDestroy events

       WebActions: The heart of the application
    It's true. Without the Webactions, you couldn't really do much of an ISAPI application. Let's see how these webactions work.

    Every request made by the client (in our case normally a web browser) is a standard HTTP request. To make things easier, Delphi brings you the power of the TWebDispatcher. What this does is translate the HTTP request to a web action. Imagine the web action as an entry point to your DLL, like any other DLL you create and export the functions, web actions have a similar way of working.

    procedure TWebModule1.WebModule1WebActionItemAction
    (Sender:TObject; Request: TWebRequest;
    Response: TWebResponse; var Handled: Boolean);

    ·Default This sets the web action to the default one. When a HTTP request is made, the TWebDispatcher tries to match the MethodType and PathInfo to an action. If one is not found, then the default action will be used.
    ·Enabled Enable or disable the web action. Very rarely would you want to create a web action and then disable it. However, this is exactly what this property does.
    ·MethodType Type of HTTP request. We will be focusing more on the mtPut and mtGet. However, you could leave this type as mtAny.
    ·Name The name of the web action
    ·PathInfo This is one of the most important properties. It's how the web action is called from the client. For example: http://localhost/scripts/project1.dll/LetsGoForIt. LetsGoForIt constitutes the PathInfo. Before we start our first application (be patient, it will be the next chapter), I want to take a quick look at the heart of all web server extensions: The WebActions.

    Let's take a look at what a request to our application might look like:


    As you can see, each call to the application is composed of:

    1. The domain name

    2. The virtual directory where the application is located

    3. The name of the application (including it's extension)

    4. And last but not least, the action.

    In this case, our action is called "theaction". This last parameter (action) can be obtained in our application with the PathInfo property of the TWebRequest object (we will look into this in more depth later).

    Once the request is made, the Dispatcher tries to match the PathInfo with an action. Once it finds one it then let's the action take care of the request. We will see later that you can actually make an action reply to a request but not "handle" it. That is, it sort of does some of the work and then passes it on to someone else (another action).

    If an action is not found that matches the PathInfo, then the Dispatcher looks for the default action. You can set an action to be default by just assigning the Default property to True. Note that you can only have on default action per application. It doesn't matter what MethodType the default action is or whether it's enabled or not.

       Method Types
    Each action can have a different method type. These are: mtGet, mtPut, mtHead, mtPost and mtAny. I won't go into details about each one (I assume that you know a bit about the HTTP protocol). I normally leave this set at mtAny.

       Before/After Dispatch
    The TWebModule has four events. As with any other form it has the OnCreate and OnDestroy. Remember, as mentioned previously, if you have CacheConnections set to True (default), these events will only be fired once.

    There are another two interesting and useful events called BeforeDispatch and AfterDispatch. The BeforeDispatch event gets called whenever an action is called in the application and it's fired before the actual action. AfterDispatch is called after an action has been fired. Later, when we look at more advanced topics such as cookies and security, you will see that these events (specially the first one) come in very handy.

       Handled Parameter
    Both the Before/After Dispatch events an every web action has a paramerter passed by reference called Handled. By default the value is True. Setting it to false indicates that the request has not been fully handled and that some other request will be called.

    Ok, that's enought theory. Let's go to the next topic and let's get down to some coding. We are going to start off our tutorial with a very simple example of an ISAPI extension. This example is going to put to use what we saw in the previous chapter regarding web actions. This chapter is also intented to show you how to make use of the extension you have created. Please note, that as for all the examples of this tutorial I am assuming that you have a web-server installed on your computer (being it PWS, IIS, Apache or any other one).

    From now on, I assume that the basic steps for creating a web server application are already known. If you still don't know how to start one, refer back to Pg 3: Getting started.

    You should already have a new webmodule on the screen waiting for you to start dropping components on. Sorry to say, in this first example there will be * no component dropping *.

    We are going to make only one action. Double click on the WebActionItems in the Object Inspector and create a new action. Name it waDEFAULT. Set the Default property to True and the PathInfo to /default

    Now that we have the new action, double-click on the OnAction event and Delphi will insert the code for you. Once that is done, we are going to add the following code:

    procedure TwmCHAPTER5.wmCHAPTER5waDEFAULTAction
    (Sender: TObject; Request: TWebRequest;
    Response: TWebResponse; var Handled: Boolean); 
    Response.SendRedirect (Request.Referer); 

    All this web action does is re-direct the request to the original URL from where it was made, i.e. to the call. SendRedirect redirects to a URL that is passed as a parameter. It is a method of TWebResponse. Response is basically the response to the HTTP request. You can respond in a wide variety of ways, either by a re-direction (as in this case), responding by setting the Content property and so on. I will focus on the TWebRequest and TWebResponse objects in later chapters. So, up to this point we know how to create a simple ISAPI application, insert the code needed and run it. In this chapter I am going to examine the TWebRequest object, covering it's most important methods and properties.

    The TWebRequest contains the information that is made by the client request and passed on to the web server application. It has three descendants, depending on the application you are working with:

    1. TISAPIRequest (if you are creating an ISAPI/NSAPI application).

    2. TCGIRequest (if you are creating a CGI console application).

    3. TWinCGIRequest (if you are creating a WinCGI application).

    We will be focusing on the TISAPIRequest object since the examples in this tutorial are mainly based on ISAPI's. However, if you list the properties, methods of each one you can see that they are basically the same.

    I am not going to cover each and every method and property in this chapter, only the ones I see as relevant for this tutorial. If I did, it would basically be a copy of the Delphi help files, and since we all have those, I'll leave it at that.

    URL, Host, ScriptName, PathInfo and Query
    First off, let's examine the URL property. This contains the target value of the requested object. Additionally, the TWebRequest object parses this string into several fields which are Host, ScriptName, PathInfo and Query. To see this better, I am going to show it with an example.

    http://localhost/scripts/chapter6.dll /action?target=10

    As I mentioned in the previous chapter, the Referer contains the URI where the request came from. For example, if you have page with URL: http://localhost/test1.htm, and there is a link to call the DLL http://localhost/scripts/test1.dll/action, the Referer property will contain the value: "http://localhost/test1.htm".

    Similarly, RemoteAddr contains information about where the request came from. This time it contains the clients IP address.

    The UserAgent contains information about the clients browser and operating system. For example, if you are viewing a web page with Internet Explorer 4 and your operating system is Windows NT, the UserAgent could contains something similar to: Mozilla/Microsoft IE 4.1 (Windows NT).

    Let's pause for a minute here. Taking into account the information we have up to now, see how easy it would be to make a Statistics application for your web-site. With the Referer you could see what page is being visited. With RemoteAddr you could see the user's IP address and therefor find out what domain it corresponds to, thus extracting the country of origin. Finally with the UserAgent property you could see what browser and operating system they are using. The first ISAPI application I ever made (and had to, so it wan't by choice) was an auditing system that did what I have just described. It additionaly inserted this information into a database and made graphics for you the customer to see a report on the hits to his/her website. If you think about it, it's actually very easy to do.

    The next two properties we are going to examine are the most basic and important ones. They let us interact and exchange information with the client. I am referring to the ContentFields and QueryFields properties.

    Have you ever filled out an HTML form? Well if you haven't by now...WHY THE HELL NOT? You can find them everywhere, whether it's checking your mail from a web-based mail system, downloading trial versions of software, contacting a company, etc. They are everywhere on the Internet. Forms provide us with a very powerful mean of gathering information. They also allow us to make on-line database application where people could update a database or make queries with just a browser.

    Well, when submitting information to a DLL (or CGI) application, you are using the HTTP Post method. Below is an example of what a form looks like:



    and here is the HTML code:

    <FORM METHOD="post" ACTION="http://localhost/scripts/chapter6.dll/form"> Name:<br><input type="text" name="NAME_FIELD"><br> SurName:<br><input type="text" name="SURNAME_FIELD"><br> <input type="submit" name="Submit" value="Submit"><br> <input type="reset" name="reset" value="Reset"> </FORM>

    (Note that I have taken tags like Font Face, Paragraph, etc out do make it simple).

    First, let's take a look at the form tag. It indicates that the information is going to be submitted via POST method. It then tells what action it's going to call (this is basically the same actions that your application consists of). In this case, it's going to call the action "form" of our "chapter6.dll". Next we have the fields that the form contains. The first one is of type TEXT and is called NAME_FIELD. The next one is called SURNAME_FIELD. Lastly, there are two buttons. The first one is the one that actually submits the information contained in the form to the action specified. The second one just resets the form.

    IMPORTANT: Only one Submit is performed per form. Note that the actual action isn't associated with the SUBMIT button, but with the FORM. You can never make a form submit to two different places with just plain HTML. There are ways to do this with JavaScript and it's should be quite simply (I'm not going to get into that right now though).

    So, what has all this got to do with the ContentFields property we were talking about? Well, the ContentFields property actually contains all the information that is in the form. Let's take a look at our next example program. This example makes use of the ContentFields property to extract the name and surname and create a response that contains the phrase: "Hello Name Surname, how are you doing today?".

    Let's look at the code:

    Response.Content := 'Hello '+ Request.ContentFields.Values['NAME_FIELD'] + ' ' + Request.ContentFields.Values['SURNAME_FIELD'] + ' how are you today?';

    Well, that looks easy. As you can see, to retrieve the values of an HTML form field, all you need to do is access the stringlist of values of the ContentFields, indicating the name you have the field in the HTML form.

    Of course, this works with TEXT fields. What about things like a CHECKBOX. Again, this is quite simple. When you post a form that contains a checkbox to your application, if the box is ticked (checked), the value will be in the ContentFields. If it is not checked, then the field won't even be contained in the ContentFields. For example, if you have a CHECKBOX named Tick1:

    to actually see if the form was posted with the checkbox ticked, you could do something like: if Request.ContentFields.IndexOfName('Tick1') -1 ..... We will examine the ContentFields with more detail in later chapters when we need to make use of the properties. This is just a small sample to show you how to access the fields.

    But what about a URL like: 'http://localhost/scripts/chapter6b.dll/action?proto=http&target=www.borland.com? This is what a GET looks like. To access the parameters of the call you would use the QueryFields and NOT the ContentFields.

    Let's take a look at the code for this example.

    if (Request.QueryFields.Values['proto'] = 'http') or
       (Request.QueryFIelds.Values['proto'] = 'ftp') then
    if Request.QueryFields.Values  '' then
            (Request.QueryFields.Values['proto'] + '://' +

    We'll examine the SendRedirect method in the next chapter. The idea here is to see how to access the QueryFields values. Again as with the ContentFields you can use the Values property indicating the name of the field. All this action does is re-direct the call to the URI specified.To run the example, open your browser and type different URI's. Make sure you have the DLL copied to the correct location.

    We will examine more properties of the TWebRequest object in later chapter, as we need them.

    I am not going to go into too much detail about the Methods of the TWebRequest. I have hardly ever needed to use any of them. However, I have occassionaly used two, so I'll quickly tell you about these. For more information, refer to the on-line Delphi help.

    As you saw with the Properties, there are a number of HTTP headers the the TWebRequest handles (such as PathInfo, Host, etc). You might want to one day have access to a certain HTTP header that is not available to you through this properties. To do so, you can use the GetFieldByName method.

    As great and easy to use as the ContentFields are, they have their restrictions. The main one is that the Content is limited in size. You can only retrieve contents of up to 48K. Past this value, you need to use the ReadClient function to get access to additional data. This could be used for example when you want to create an application to upload files via the web to your server.

    The basic methods and properties needed to start doing some serious applications are covered now. In the next chapter I'll examine the TWebResponse object. We can now retrieve information that the client send us via the web-browser, but how do we respond to the requests? From the examples in the previous chapters we have seen two ways to do this:

    1. Setting the value of the Response.Content property.

    2. Using the Response.SendRedirect method.

    Note: In Delphi 5, the WebAction has a new property called the Producer. You can respond to a request by specifying a Producer in the property. However, I will not mention this anymore until we have seen the TPageProducer chapter.

    Have you ever been to a site that you knew was there yesturday, but today only a blank page appears with a small text saying:

    This site has been moved. You will be re-directed to the new location in a few seconds........

    This can actually be done in several ways. One way is to put a HTML meta-tag in the index page that does a re-direct. Some servers allow you to setup a site as a re-direction. For example in IIS you can create a new web-site that all it does is re-direct to another site.

    Well, Response.SendRedirect can be viewed as something similar to the above. All it does is respond to the request by re-directing the client to a new URI. Take a look at the first example of this chapter (chapter7a.dpr). This has only one action which is also the default. It retrieves the value sent my the Query and re-directs you to the new location.


    That's all you need to know to be able to use SendRedirect. In later examples I will make use of this feature alot.

    The Content property contains the information that is going to be sent back to the client. This information can be either an HTML string or an MIME compliant content type (images, files, etc). In the example Chapter7b, we take a look on how to send different contents depending on the request made. The first type is just an html string. The second type is a file. In the code there are three ifs. As I mentioned in the previous page, Delphi 5 gives you another option when responding to the request. This is done by filling in the Producer property of the web action. However, as I also said, I would not cover this topic until we have seen what a PageProducer is. Well, in this chapter we are going to examine just this. The TPageProducer comes in many flavours, the most simple one TPageProducer and then more complex types that are Data-aware enabled (TDataSetPageProducer, etc). These will be covered once we have seen Database applications with ISAPI's.

    The TPageProducer is actually a simple and yet very common and powerful component. In it's simplest for you can think of it as just a stringlist that contains HTML strings and acts like a template. It has very little properties/methods and only one event:

    Property Description
    DispatcherRefers to the Dispatcher. Normally you should not need to modify this property in an ISAPI application and leave the default Dispatcher that is created.

    ·HTMLDoc This is a string of HTML values

    ·HTMLFile This is an actual HTLM File. You cannot set both the HTMLDoc and HTMLFile property at the same time. Setting the HTMLFile property will clear the HTMLDoc value.

    ·Name, Tag As with other component

    Method Description
    ·Content When you call content, the strings of the PageProducer will be parsed according to the template and the value will be returned.

    ·ContentFromStream Similar to the previous, but in this you read the contents from a stream that you pass as a parameter to the method.

    ·ContentFromString Again as before but read from a string

    Event Description
    ·OnHTMLTag Everytime the PageProducer encounters an HTML transparent tag, it will fire this event (shortly we will see what an "HTML Transparent Tag" is.)

    Let's see a very simple example of how to use the TPageProducer (example chapter8a). As you can see the only line that this

    if (Request.QueryFields.Values['file'])  '' then 
     ppRESULT.HTMLFile := Request.QueryFields.Values['file'] 
    else begin
     with ppRESULT.HTMLDoc do begin
      Add('You did not specify a file'); 
      Add('Therefor, you are seeing the result of'); 
      Add('assigning a value to the HTMLDoc property'); 
      Add('of the TPageProducer');
    Response.Content := ppRESULT.Content ;

    By now this code should seem simple enough to you. The only new thing is the assignments to the ppRESULT TPageProducer we have in this project. What we do is see if the Query contains a parameter calledfile. If so, we assign the value to the HTMLFile property. In other case, we assign a set of strings to the HTMLDoc property.

    The last call is the same in both cases and call the Content method of the TPageProducer. In the same line we assign this value to the Response.Content property. Test this example and see the result.

    NOTE: if you are using IIS make sure that the file parameter you pass in the URL has the correct permissions to be read by the IUSR_MACHINENAME.

    Well that seemed easy enough. But you might ask yourself, what difference is there to this than the previous example? We could assign a file in the previous example aswell. Up to this point, there is no difference. This is also because we haven't used the full power of the TPageProducer yet.

    Let's go back to the example 6A in chapter 6 for a minute. Do you remember this line of code:

    Response.Content := 'Hello '+Request.ContentFields.Values['NAME_FIELD'] + ' ' + Request.ContentFields.Values['SURNAME_FIELD'] + ' how are you today?';

    All it did was respond with a blank page with "Hello Name + Surname. How are you today?", where it took the name and surname from the values of the form we submitted. Let's take this one step further and use the TPageProducer to produce the same effect:

    with ppRESULT.HTMLDoc do begin
     Add(Request.ContentFields.Values['NAME_FIELD']+' '+
     Add('How are you today?'); 
     Add('of the TPageProducer');

    This would produce the exact same result. Since this is an HTML page, we could spruce it up a bit and add some color, a few images and a border around the text. Imagine how tedious it would be to do all this? Throw the WYSIWIG HTML editor out the window and go back to hard-coding HTML tags. We wouldn't have to take it that far. We could actually design the HTML page with our favorite tool and then copy/paste into the source code of our project and adapt it to our needs. This seems ok and not too painful of a task at first, but there are two major problems:

    1. What if you want to change the image? You would have to change the source code and then re-compile the DLL again.

    2. I don't know about you but I'm not very good at designing web pages. In many companies, the web designer and the programmer are two different people and departments. This results in you asking the designer to create a page so you can copy the HTML source into your pascal code. No, thank you.

    So how do we overcome these problems? Well as great as Delphi is, it just got greater. Delphi provides you with a what is called an HTML-transparent tag. As you know, tags in HTML are represented with"". In Delphi you can use tags that are transparent to the standard HTML by prefixing them with the # sign. A tag would therefor be represented with "".

    In the next example, I'm going to produce the exact same result as the previous one, but in this case with the use of tags. The HTML page would look something like this:

    Hello . How are you today?

    NOTE: When using a WYSIWYG HTML editor, make sure that once you have saved the page, the '' symbols appear correctly. Since these are used in HTML code, most editors replace them with .

    If you examine the code for this example (Chapter8B.dpr), we have only one action which is waTAG. As before, we assign the the HTMLFile property and then call the method Content assigning the output to the Response. This method in turn will fire the OnHTMLTagEvent of the PageProducer, which looks like:

    if TagString = 'NAME' then 
     ReplaceText := Request.ContentFields.
     if TagString = 'SURNAME' then 
       ReplaceText := Request.ContentFields.

    The TagString parameter contains the name of the Tag we put in our HTML file without the enclosing '' or '#'. The ReplaceText parameter is the text that the tag will be replace with.

    This is a very simple example to show you how tags work. You can additionally pass parameters to the tags in the HTML file and process these parameters in your code. It's well worth the while to play around with the tags until you get confortable with them. For more information, look at the on-line help in Delphi.

    In Page 7 I mentioned that in Delphi 5 each Web-action has a new property called the Producer. Now that we have seen what a PageProducer is and does, I can explain this new property. It's actually very simple and really doesn't need any explanation. Since nearly all responses to a request to your application imply the use of a PageProducer (in any of it's flavours, TDataSetPageProducer, etc. I'll explain these in Chapter 11), the guys at Borland probably decided to save us even more from writing code, by adding a property to each request that let's us assign the Producer that will provide the result. Look at the action Producer of example Chapter8b. It does exactly the same as the tag action. We've all heard of cookies. Some of us love them (specially chocalate-chip ones), and some of us hate them. For the later, skip the rest of this tutorial since I am going to make great use of them. I'm from the school of cookie lovers. I think they are one of the best inventions that have been made (if used correctly of course). In this chapter I am going to give you a simple example of how to set a cookie and how to read one. In the next chapter, I will focus on using cookies to overcome HTTP's stateless nature. In the remaining chapters of this tutorial I will use them very frequently.

    I am not going to go into details about the specification of cookies or any technical information. If you want all that stuff check outwww.cookiecentral.com or Netscape's specification at.

       Setting a cookie in Delphi
    Cookies are a list of pair values (Name=Value). As such, we can take advantage of Object Pascal's TStringList. This makes cookie managing very easy. Take a look at the example below (from chapter9.dpr).

    var slstCookie: TStringList;
     slstCookie := TStringList.Create;
      with slstCookie do begin
        (slstCookie, '', '/scripts',-1,False);
      Response.Content :=
        'Cookie has been set. Thank you!'; 

    Well that was easy enough. That's all there is to it. First we define a stringlist to store the values of the cookie. Next, we add the field name (NAME) and then the value (VALUE). Finally we call the SetCookieMethod to set the cookie. This method takes 5 parameters. The first one is the stringlist with the cookie information. The second parameter is the domain we are setting the cookie for. If non is specified the current domain will be used. The third parameter is the path (again if non is specified the current path will be used). The fourth parameter indicates the expiry date for the cookie. By setting the value to -1 we will make the cookie disapear once the browser is closed (i.e., it will not be stored on the hard-disk). The last parameter indicates whether the cookie should be set only if a secure connection is available.

    As you can see, this is basically the same format as you would use when setting a cookie in any other language.

       Reading the cookie
    There would be no use to setting a cookie if we could not later read it. Here's how we can read the cookie:

    if Request.CookieFields.Values['CookieField1']'' then 
     Response.Content :=
       'The value for CookieField1 is ' +
       Request.CookieFields.Values['CookieField1'] + ''
     Response.Content := 'Where is the cookie?';

    As you can see, the values of the cookies are stored in the CookieFields property of the request object.

       Cookies and re-direction
    Once you start using cookies like hot-cakes, you will start to realise that in some cases you would need to set a cookie and then re-direct the user to another location. One use this would have is to check whether the user accepts cookies in his/her browser. To do this we could set a cookie and the re-direct the response to retrieve the cookie. If the cookie is not present, it would lead us to believe that the user has disactivated cookies. This seems simple enough at first, so let's see what the code would look like:

    var slstCookie:TStringList;
     slstCookie := TStringList.Create;
      with slstCookie do begin
         (slstCookie, '', '/scripts',-1,False);

    If you try and run this code, don't be surprised when you don't get the expected result. The problem is that SetCookieField and SendRedirect just don't work together. Period. Forget about it. I don't know whether this is a bug or a *special feature*, but it just won't work.

    Now that we know the previous code doesn't work, we need a workaround. We can make use the the HTML meta-tag Refresh for our purposes.

    var slstCookie:TStringList;
     slstCookie := TStringList.Create;
      with slstCookie do begin
        (slstCookie, '', '/scripts',-1,False); 
      Response.Content := '';

    All we are doing is sending back a header that contains the instruction to reload (re-direct) to the URL specified in the 0 seconds specified. I've used this method successfully with all my applications and I'm quite happy with the results. I'm sure there might be some other ways to do this, but for now I'm sticking to this one. As I mentioned in Page 1, HTTP is a stateless protocol. To overcome this problem we can resort to various methods. The three most common ones are: Cookies, (Fat) URL's., and Hidden Fields.

    I will discuss briefly all three methods. Personally I like the use of cookies. I have seen a lot of questions on the newsgroups with a short parenthesis saying "I don't want to use cookies". As I mentioned in the previous chapter, cookies is a very debated issue and I'm not going to add to it. I just think that if they are used in the correct way they can save you a lot of work. However, if you are still against using cookies, I will describe other methods to maintain state information. You can then pick which ever one suites your needs and taste better.

    Since I will be making great use of cookies in the next chapter and later, I will not explain this method now. Basically it boils down to keeping an ID as a pointer to a record in a database, a file on disk and so on. Normally you shouldn't use cookies to maintain large amounts of information for two reasons: first of all they are limited in size and second of all they are not very *secure* as we could say. Anyone can examine the contents of a cookie. Keeping information like passwords, etc in there isn't wise. Instead you could keep an integer value for example that points to a record where the user's personal information is stored. You could also opt for encoding the information before sending it to the cookie and then decoding when retrieving, but really I don't see much point into going to all the trouble.

    Having said that, let's examine the other two methods for passing information from one state to another.

    One of the easiest and most commonly used methods for passing information is using the URL. To pass information from one request to another using the URL can be easily accomplished using the SendRedirect method:


    In this line, we are calling our dll again. However we are also passing information that we retrieved in this action from the content fields of the form (in particular THE_FIELD field).

    Notice, that in the new call (redirect), you would only have access to the information via the QueryFields.

    Sometimes it's not adequate to pass information via the URL. Also you might want to use the information from the previous state in a new form and therfor have access to it with the ContentFields. To acheive this you can use hidden fields. Hidden fields are like any other HTML field except that they are "hidden", i.e. not visible to the user. Passing information in hidden fields is very simple with the use of tags.

    Let's say you have an HTML form that calls an action in your DLL. Add to this form a hidden field and set the value to be equal to an html transparent tag, as in:

    In your call to the DLL, put a PageProducer and replace the value of the tag with the information you need to pass to the next request.

    if TagString = 'STATE_FIELD1' then ReplaceText := 'State_information';

    Also make sure of course that the result is contained in a form so you could then have access to this field with the ContentFields property. In you next action, all you need to do to read this information is:


    This method implies a bit more work on your part the using URL's or cookies to pass information. Also all the pages have to be processed with a PageProducer.

    Up to know, we have seen alot about ISAPI programming in Delphi. However, if you look back, really we haven't seen anything. All that stuff with Cookies, PageProducers, etc is very good but not really a lot of use to us. Sure, there are tons of things you could do with that, but nowadays most internet-programming is focused around databases. Anything you need to do at work or at home really needs you to interact in someway with a database. This is where the true power of Delphi and Internet programming comes to life.

    Since we have a lot of ground to cover, I have thouhgt it best to divide this chapter in several parts. From here you can jump to what most interests you. I recommend that if you are new to this area, to read them in consecutive order.

    All these examples are going to use Interbase as the database server. This is due to two reasons, first of all I love Interbase and prefer it to many RDBMS's out there (very little maintanance). Second of all, it comes with Delphi, so you can easily run all the examples.

    I don't recommend using Paradox or any flat-file database for Internet usage. If you think that MS-SQL, Interbase or Oracle are too expensive to purchase for your company or private use, remember that you have Interbase 4.2 for free on Linux. For the most usage you will be giving it over the Internet, this version should be sufficient. You do not have things like cascading updates in this version, but you could easily overcome this feature.

       Setting up Interbase
    To setup Interbase correclty to be able to use it with ISAPI applications you need to do the following:

    1. Make sure Interbase is running as a service.
    2. Click on the Services icon in Control Panel and choose Interbase Server. Click on the Startup button and tick the checkbox "Allow to interact with Desktop".
    3. Do the same for WWW services.
    4. In the BDE, make sure that you prefix the path to the database with your machinename, e.g.machine.domain:c:\databases\data.gdb. (notice the : after the domain).

    As you can notice (point 4), I'll be using the BDE to access Interbase in the examples and not the IBX components. This is so people that do not have Delpih 5 can still follow the examples. IBX will probably be covered in part two of this tutorial (not part 2 of this chapter!).

    This chapter will be divided up into three parts, but first read the FAQ. 1. I get this message in IE5 saying Page cannot be displayed
    This is because IE5 has the option 'Show friendly HTTP errors' activated. When created web-server applications, I recommend turning this option off in Tools -> Internet Options -> Advanced. That way, you will receive an error message that's a little more descriptive.

    2. Why do I get the error 'The specified module could not be found' when running my DLL?
    Make sure you have copied your DLL to the correct location that corresponds to the URL you are calling. For example, if your URL is the default http://localhost/scripts, then the DLL should be copied to c:\inetpub\scripts (default).

    3. When I try to debug my application under IIS4 I get "Access violation" and the CPU window comes up.
    IIS runs as a service in NT. To be able to debug, you need to change some registry settings to run it as a process. Look at chapter 2 for more information regarding debugging.

    4. I keep having to re-start my computer everytime I change something in my DLL.
    This should not happen if you are debugging, since when you reset the debugger the web-server stops and the DLL's are unloaded. If you are running the DLL's stand-alone, then you have various options:

    1. If you are using IIS 4 or above, in the properties of your scripts directory there is an option to allow you to run the DLL's in a separate memory space. This will activate the Unload button, thus allowing you to unload a DLL that's been loaded into memory.

    2. Use the kill.exe program that is included in the NT Resource Kit. From the command line type: kill inetinfo. This will stop the web-server. Make sure you start it again if you are using it in a production environment.

    5. When the Tags are replaced in my HTML files, memo fields appear as (MEMO) instead of the value. There are two ways you can correct this:

    1. In the OnHTMLTag, check for the field in question by examining the TagString and if it corresponds to a memo field, then do a manual replace with ReplaceText := Field.AsString.

    2. Check the ReplaceText value and if it contains (MEMO), then to a manual replace with ReplaceText := Field.AsString. I am going to base chapter 11 on constructing a client database. In this part, I will show you how to setup the TDatabase and prepare the web-module for multiple accesses to the database. In the next part I will cover insertion and updates (deleting aswell) of records.

    First of all, let's create our database. Run the SQL file DBSTRUCT.SQLto generate the database file. Make sure you change the machine.domain: c:\databases\client.gdb to the appropriate settings for your machine. Next, let's configure the BDE.

    Like any other BDE application, during development I have set the ENABLE SCHEMA CACHE to False. On deployment, change this to TRUE. Before going any further, check to see everything is working ok by opening the database from the BDE.

    Your BDE settings should look something like this:

    BDE Administrator

    Now that we have our database and BDE configured, let's start a new ISAPI project. Once this is done, drop a TDatabase and a TSession on the form and set the following properties for each one:

    Alias = CLIENTAutoSessionName = True
    LoginPrompt = False
     DatabaseName = dbDATA
     Name = dbDATA

    Of course, in real life you would not be setting the username and password at design time but at runtime. This is just for demonstration purposes. If you have Delphi 5, you will notice that the red circles will disappear in the designer as soon as you set the AutoSessionName = True. What this does is take care of giving each request a unique session to access the data safely and not interfere with any other petitions.

    Now let's code a bit. We want to open the database when the DLL is first loaded and close it when the DLL is destroyed, thus preventing each request having to open/close the connection. To do this, we place the following code in the OnCreate/OnDestroy of the web module. Remember that this works as mentioned if you leave CacheConnections set to the default value of True. If you set CacheConnection to False (Application.CacheConnections := False), then every request will call the OnCreate/OnDestroy.

    Next, we create a DEFAULT web action that just replies with 'Invalid Request'. Later on, when I make use of the BeforeDispatch we will see that we can control this there and not have to create a default action. In any case, it's not harmful and doesn't effect us at this point.

    So this covers our first part of Page 11. Easy wasn't it? In the next part we are going to add the ability to insert/update and delete clients over the web.

    This is going to be a client database over the Internet. This means that any one with a browser and an Internet connection, with no need for additional software can add, delete, modify and view information. This gives great power and it brings with it some security issues. I am going to explain a way so that only certain people that have access can do this. Of course, the security I am going to employ is quite simple, you can extend it as much as you want to provide additional security (restrict by IP, SSL, etc).

    As we've seen in the chapter covering PageProducers, the HTMLFile property assigns an HTML file to the PageProducer. This file can be located anywhere on your machine as long as the IUSR_MACHINENAME user has read access to the file at NTFS level (considering you are using NT as your server. If you are not, then you shouldn't really worry about security issues with your ISAPI application as Windows 95, 98 doesn't have any other means of security anyway....so just open yourself up to the whole world ).

    We are going to make use of this feature combined with the use of cookies to restrict access to certain parts of the application. First of all, let's start with the boring part of making the HTML pages. These pages are located (by default) in the source directory of this chapter. On deployment, you could move these to any directory on your server that is not in a public location (e.g. inetpub\wwwroot) and somewhere where IUSR_MACHINENAME has read access to.

    The files you should be looking at up to this point are: menu.htm

    This last one (login.htm) on the contrary, should be located in the public directory where your web-server points to. This page will be used for the user to input his/her login information so that we can verify his credential. Your directory structure should look something like this: C:\ApplicationPrivateFiles: (only with read permission for IUSR_MACHINENAME).

    and contains:


    C:\inetpub\wwwroot: (public location for web-server)

    and contains:


    In the BeforeDispatch, we are going to examine the PathInfo to see what petition is being made (action requested). Depending on whether this value is public access or restricted we take the appropriate action. If it is a restricted action we check to see if the cookie we set when the user has logged in is present. If not we know that the user has not yet logged in. Since there is only one public action (login), we check to see if it is this, if not then we respond indicating that there was an invalid request (thus "over-riding" the default action we made in part 1).

     Check to see what action is asked for.
     If it is a restricted action then check
     for credential (cookie is present)
    if ( Request.PathInfo = '/action' ) or
       (Request.PathInfo = '/page')
        then begin
    	// Restricted-access
      if (Request.CookieFields.Values['Cookie'] =
          'CLIENT_DATABASE') then // has access 
        // We Set handled to false and let
    	// the appropriate action take it's course 
        Handled := False 
      else begin
       Response.Content := 'Unauthorized access'; 
       Handled := True ;
    end else
     if (Request.PathInfo = '/login') then 
      Handled := False // Login page. Public. Allow user in. 
     else begin 
      // This will over-ride the default 
      // action. DEFAULT will never be used 
      Response.Content := 'Invalid Request'; 
      Handled := True ; 
    end ;

    This action verifies that the user has introduced his/her correct login information. If so, it then places a cookie that will be later verified by the BeforeDispatch event. The cookie also holds the ID entry of the user. In this example we might not need this ID at all, but I'm placing it so that if you ever need information regarding the user, all you need to do is query the USER_LIST table given the ID and retrieve the necessary information.

    Since most of the code has been explained in previous chapters, I won't go into details. The only really new thing is the Stored Procedure that checks to see if the user is valid. If the username/password is correct it then responds with the user's ID. All user ID's are greater or equal to zero, so if the user does not exist or the combination is incorrect it will return -1.

    The "Page" action
    Since the pages to access the application are in a private location (as intended), we need some way to access them. This is why we are going to create an action that returns a page requested. For example, before we can add a new record to the database we need to see the html form where we can fill in the information.

    The BeforeDispatch event takes care of the security issues, so in our "Page" action all we need to do is to return the requested page that will be indicated by the "target" parameter (look at the source code for the menu.htm file). If you look at the code for this action you will see that it is very simple:

    Note that the path to the private files is hard-coded. Other ways to do it is either using constants (but still would be coded) or use a INI file of some sort that would read the values from a the file in the OnCreate and store them in a global variable (remember, it would be ok since it would be a read-only variable). You could then easily change the paths without the need to re-compile.

    Try calling this action without logging in first and see the result:. If you debug, you will see that it doesn't even get to the Page action. The BeforeDispatch takes care of it.

    As you can see also, the errors and pages i've generating aren't what you could say the best or nicest. This is all for demonstration purposes and the error messages could be replaced with something more friendlier and less explicit!

    The "Action" action
    This is where it all takes place. First I want to start off by saying that normally I don't use strings as the target of the actions (i.e. AddRecord, DeleteRecord, etc). I prefer to use number in which case the whole lot of "if's" could be replaced by a much faster "case" sentence. However, so as not to be confused by numbers I thought it best to be explicit and use text to indicate exactly what action is going to be performed. In this part of Chapter 11 we will only focus on the "addrecord" target.

    We call the function "AddRecord", which reads the contents of the form, creates an SQL instruction and executes it. If no exception is raised then it returns an 'OK' , otherwise it returns the message of the exception. Notice that there is no need to pass the Request.ContentFIelds as a parameter to the function since the current Request property of the web-module is instantiated with the current contents of the current request.

    This was very easy. Examine the code carefully and make sure you understand everything before you proceed to the next part of this chapter. Searching for a Record: The TQueryTableProducer

    There are dozens of ways you could go about doing this. I'm not referring to the actual searching, but more in the way in which you return the result. To be quite honest I never or hardly ever used the TQueryTableProducer or the TDataSetTableProducer. But since they are on the Palette and as I've seen on the Newsgroups, a lot of people do use them, I've decided to experiment a bit and try them out.

    The QueryTableProducer does exactly that. It returns an HTML table based on the query. It doesn't only save us the trouble of wiring the html code to generate a table and loop through the records returned, but it also automatically reads the parameters the query is based on from the request. This parameters can be either QueryFields in a GET or ContentFields in a post. They most coincide with the same name as the parameters given in the Query.

    If you look at the source code you will see that I have placed a specific TQuery component with the following sentence:


    The reason I am using CONTAINING instead of = is because for illustration purposes I want the result to be a little less exact so that it returns more than one record in a query. Now take a look at the search.htm page. It's a simple HTML form with a field name CLIENT_NAME and it uses the POST method. We next drop a TQueryTableProducer on the web-module and assign it's dataset property quSEARCH (our TQuery). Next, if you double-click on the component (qtpSEARCH), the component editor will appear where you can add the database fields of the query as columns of the page and other fields if you want. We actually use an additional field that contains a checkbox which will allow us to delete the selected records. What follows is just simple property settings to make it look half decent. Once that is done, we next add the following line of code:

    if Request.QueryFields.Values['target']=
       'searchrecord' then begin
      Response.Content := qtpSEARCH.Content ; 
      sResult := 'DONE'; 

    Notice how I do a close of the query after calling the Content method. This is because as I've mentioned, once a request is serviced, the web-module remains in memory waiting for other request (CacheConnections = True). If we didn't close the query after calling the Content method, the next request would just call Content and not refresh the query with the new parameters of the request.

    Next, we'll take a look at the OnFormatCell event of the TQueryTableProducer. This event gets fired for every cell. We are going to use this event to add a link to the ID of the client so as the user can click on it to consult and/or modify the record. We will also add a checkbox in the last column of the table so we can use it do delete the record. The first check is to see if we are not in the Title row (CellRow >0). If not, we then add certain data depending on the row we are in. If it is the Client_id column, we add a link to call the action?target=consultrecord&client_id=XXX. This will call the action ConsultRecord given the parameter client_id. The other column adds a checkbox with name 'DEL' + Client_id. We'll see how to make use of this in the next section.

    One last thing: Take a look at the header and footer properties of the qtpSEARCH component. I have added the necessary html tags to incorporate a form so we can process the values returned by the checkboxes.

    Consulting and modifying a record: The TDataSetPageProducer

    Up to this point, we have an action that returns a list of records in an HTML table with a link to consult/modify the record and a checkbox to delete the record. In this section will see how to modify and consult the record. The call to "consultrecord" is basically the same as the previous action. The difference here is that we use a TDataSetPageProducer as the response. This component is used to produce the values of a record in the dataset. What it does is replace all the tags in an html page that have the same name as a field in the dataset with their values. If you take a look at the modify.htm file you will see how I used tags to fill in the edit boxes with the current value of the record. Certain fields might need additional processing such as memo fields or checkbox fields, drop-down lists, etc. All this processing can be done in the OnHTMLTag event.

    So, this action retrieves the information from the database based on the value passed in the query and produces the modify.htm page as the result, filling in the appropriate values. This page is contained in a form. By clicking on the Modify button you can modify the record with the current contents. This is performed by the "modifyrecord" target of the Action event. This event is very similar to AddRecord.

    Deleting records
    As we saw in the search section, I added a checkbox so the user can select the records to delete. When you submit a form that contains a checkbox, if the checkbox is ticked, a corresponding ContentField is available with the value of the checkbox. If it is not ticked then the ContentFields is not present. I make use of this feature to construct and SQL sentence with the records needed to delete. I loop through the ContentFields and see if any of have DEL in them. If so, I subtract the record number and construct the SQL sentence. Executing this sentence will delete the corresponding records. Of course, this method is not foolproof if you have other fields beginning with DEL or containing DEL but it allows me to show you how you can do something similar.

    Well, that's about it! This is the last chapter of this tutorial (until I expand it). I hope it has been useful and will allow you to get started on Internet programming with Delphi. It's now 6 am and I have to get up in two hours for work, so I'm going to stop here!

    Zarko Gajic, BSCS
    About.com Guide to Delphi Programming
    email: delphi@aboutguide.com
    free newsletter: http://delphi.about.com/library/blnewsletter.htm
    forum: http://forums.about.com/ab-delphi/start/

  • ©2016 About.com. All rights reserved.