972 database table, we’ll need to update the element list automatically. As a final solution, we can design the ISAPI DLL to produce a form on-the-fly, and we can fill the selection controls with the available elements. We’ll generate the HTML for this page in the /form action, which we’ve connected to a PageProducer component. The PageProducer contains the following HTML text, which embeds two special tags: <h4>Customer QueryProducer Search Form</h4> <form action=”CustQueP.dll/search” method=”POST”> <table> <tr><td>State:</td> <td><select name=”State”><#State></select></td></tr> <tr><td>Country:</td> <td><select name=”Country”><option> </option><#Country></select></td></tr> <tr><td></td> <td><center><input type=”Submit”></center></td></tr> </table></form> You’ll notice that the tags have the same name as some of the table’s fields. When the PageProducer encounters one of these tags, it adds an <option> HTML tag for every distinct value of the corresponding field. Here’s the OnTag event handler’s code, which is quite generic and reusable: procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag; const TagString: String; TagParams: TStrings; var ReplaceText: String); begin ReplaceText := ‘’; Query2.SQL.Clear; Query2.SQL.Add (‘select distinct ‘ + TagString + ‘ from customer’); try Query2.Open; try Query2.First; while not Query2.EOF do begin ReplaceText := ReplaceText + ‘<option>’ + Query2.Fields[0].AsString + ‘</option>’#13; Query2.Next; end; finally Query2.Close; end; except ReplaceText := ‘{wrong field: ‘ + TagString + ‘}’; end; end; Chapter 22 • Web Programming with WebBroker and WebSnap Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 973 This method used a second Query component, which I manually placed on the form and connected to the DBDEMOS database, and it produces the output shown in Figure 22.6. Finally, this Web server extension, like many others we’ve built, allows the user to view the details of a specific record. As in the last example, we can accomplish this by customizing the output of the first column (column zero), which is generated by the QueryTableProducer component: procedure TWebModule1.QueryTableProducer1FormatCell( Sender: TObject; CellRow, CellColumn: Integer; var BgColor: THTMLBgColor; var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs, CellData: String); begin if (CellColumn = 0) and (CellRow <> 0) then CellData := ‘<a href=”’ + Request.ScriptName + ‘/record?’ + CellData + ‘“>’ + CellData + ‘</a>’#13; if CellData = ‘’ then CellData := ‘ ’; end; TIP When you have an empty cell in an HTML table, most browsers render it without the border. For this reason, I’ve added a “nonbreaking space” symbol ( ) into each empty cell. This is something you’ll have to do in each HTML table generated with Delphi’s table producers. FIGURE 22.6: The form action of the CustQueP example produces an HTML form with a selection component dynamically updated to reflect the current status of the database. Delphi’s WebBroker Technology Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 974 The action for this link is /record, and we’ll pass a specific element after the ? parameter (without the parameter name, which is slightly nonstandard). The code we use to produce the HTML tables for the records doesn’t use the producer components as we’ve been doing; instead, it is very similar to the code of an early ISAPI example: procedure TWebModule1.RecordAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var I: Integer; begin if Request.QueryFields.Count = 0 then Response.Content := ‘Record not found’ else begin Query2.SQL.Clear; Query2.SQL.Add (‘select * from customer ‘ + ‘where Company=”’ + Request.QueryFields[0] + ‘“‘); Query2.Open; Response.Content := ‘<html><head><title>Customer Record</title></head><body>’#13 + ‘<h1>Customer Record: ‘ + Request.QueryFields[0] + ‘</h1>’#13 + ‘<table border>’#13; for I := 1 to Query2.FieldCount - 1 do Response.Content := Response.Content + ‘<tr><td>’ + Query2.Fields [I].FieldName + ‘</td>’#13’<td>’ + Query2.Fields [I].AsString + ‘</td></tr>’#13; Response.Content := Response.Content + ‘</table><hr>’#13 + // pointer to the query form ‘<a href=”’ + Request.ScriptName + ‘/form”>’ + ‘ Next Query </a>’#13 + ‘</body></html>’#13; end; end; Debugging with the Web App Debugger Debugging Web applications written in Delphi is often quite difficult. In fact, you cannot simply run the program and set breakpoints in it, but should convince the Web server to run your CGI program or library within the Delphi debugger. This can be accomplished by indicating a Host application in Delphi’s Run Parameters dialog box, but it implies letting Delphi run the Web server (which is often a Windows service, not a stand-alone program). To solve all of these issues, Borland has added to Delphi 6 a specific Web App Debugger program. This tool, activated by the corresponding item of the Tools menu, is a Web server, which waits for requests on a port you can set up (1024 by default). When a request arrives, the program can forward it to a stand-alone executable, using COM-based techniques. This Chapter 22 • Web Programming with WebBroker and WebSnap Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 975 means you can run the Web server application from within the Delphi IDE, set all the break- points you need, and then (when the program is activated through the Web App Debugger) debug the program as you’ll do for a plain executable file. The Web App Debugger does also a good job in logging all the received requests and the actual responses returned to the browser, as you can see in Figure 22.7. The program also has a Statistics page, which interestingly tracks the time required for each response, allowing you to test the efficiency of an application in different conditions. By using the corresponding option of the New Web Server Application dialog, you can easily create a new application compatible with the debugger. This defines a standard project, which creates both a main form and a data module. The (useless) form includes code for reg- istering the application as an OLE automation server, as: const CLASS_ComWebApp: TGUID = ‘{33A4D4F0-E082-4723-9165-5D8F95AF1577}’; initialization TWebAppAutoObjectFactory.Create(Class_ComWebApp, ‘FirstDemo’, ‘FirstDemo Object’); The information is used by the Web App Debugger to get a list of the available programs. This is done when you use the default URL for the debugger, indicated in the form as a link, as you can see (for example) in Figure 22.8. The list includes all of the registered servers, not FIGURE 22.7: The log of the Web App Debugger with its LogDetail window Delphi’s WebBroker Technology Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 976 only those running. In fact, the use of COM Automation accounts for the automatic activa- tion of a server. Not that this is a good idea, though, as running and terminating the program each time will make the process much slower. Again, the idea is to run the program within the Delphi IDE, to be able to debug it easily. Notice, though that the list can be expanded with the detailed view, which includes a list of the actual executable files and many other details. The data module for this type of project has some initialization code as well: uses WebReq; initialization WebRequestHandler.WebModuleClass := TWebModule2; This approach should be used only for debugging. To deploy the actual application you should then use one of the other options. What you can do is create the project files for another type of Web server program and add to the project the same Web module of the debug application. The presence of the extra initialization line won’t create a problem. The reverse is slightly more complex. To debug an existing application, you have to create a program of this type, remove the Web module, add the existing one, and patch it by adding a line to set the WebModuleClass of the WebRequestHandler, like the one in the preceding code snippet. To account for possible missing initialization of the WebRequestHandler object, you might want to change this type of code into: if WebRequestHandler <> nil then WebRequestHandler.WebModuleClass := // Web module class FIGURE 22.8: A list of applications registered with the Web App Debugger is displayed when you hook to its home page. Chapter 22 • Web Programming with WebBroker and WebSnap Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 977 WARNING By doing this for the CustQueP example (it is the CustQueDebug project), I realized that some of the Web request settings are different. So instead of using the ScriptName property of the request (set to empty for a Web debug application), you have to use the InternalScript- Name property. There are other two interesting elements in the use of the Web App Debugger. The first is that you can test your programs without having a Web server installed and without having to tweak its settings. In other words, you don’t have to deploy your programs to test them—you simply try them out right away. Another advantage is that, contrary to doing early development of the applications as CGI, you can start experimenting with a multithreaded architecture right away, without having to deal with the loading and unloading of libraries, which often implies shutting down the Web server and possibly even the computer. NOTE If your aim is to build an ISAPI application, you can also use a specific ISAPI DLL debugging tool. One such tool, called IntraBob, has been built by Bob Swart and is available on his Web site (www.drbob42.com) as freeware. Working with Apache If you plan on using Apache instead of IIS or another Web server, you can certainly take advantage of the common CGI technology to deploy your applications on almost any Web server. However, using CGI means some reduced speed and some trouble handling state information (as you cannot keep any data in memory). This is a good reason for writing an ISAPI application or a dynamic Apache module. Using Delphi’s WebBroker technology, you can also easily compile the same code for both technologies, so that moving your program to a different Web platform becomes much simpler. Finally, you can also recompile a CGI pro- gram or a dynamic Apache module with Kylix and deploy it on a Linux server. As I’ve mentioned, Apache can run traditional CGI applications but has also a specific technology for keeping the server extension program loaded in memory at all times for faster response. To build such a program in Delphi 6, you can simply use the Apache Shared Mod- ule option of the New Web Server Application dialog box. You end up with a library having this type of source code for its project: library Apache1; uses WebBroker, ApacheApp, ApacheWm in ‘ApacheWm.pas’ {WebModule1: TWebModule}; Delphi’s WebBroker Technology Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 978 {$R *.res} exports apache_module name ‘apache1_module’; begin Application.Initialize; Application.CreateForm(TWebModule1, WebModule1); Application.Run; end. Notice in particular the exports clause, which indicates the name used by Apache configu- ration files to reference the dynamic module. In the project source code, you can add two more definitions, the module name and the content type, in the following way: ModuleName := ‘Apache1_module’; ContentType:= ‘Apache1-handler’; If you don’t set them, Delphi will assign them some default values, which are built adding the _module and -handler strings to the project name, ending up with the two names I’ve used above. An Apache module is generally not deployed within a script folder, but within the modules subfolder of the server itself (by default, c:\Program Files\Apache\modules). Then you have to edit the http.conf file, adding a line to load the module, as: LoadModule apache1_module modules/apache1.dll Finally, you have to indicate when the module is invoked. The handler defined by the mod- ule can be associated with a given file extension (so that your module will process all of the files having a given extension) or with a physical or virtual folder. In the latter case, the folder doesn’t exist, but Apache pretends it is there. This is how you can set up a virtual folder for the simple Apache1 module: <Location /Apache1>SetHandler Apache1-handler</Location> As Apache is inherently case sensitive (because of its Linux heritage), you might also want to add a second, lowercase virtual folder: <Location /apache1>SetHandler Apache1-handler</Location> Now you can invoke the sample application with the URL http://localhost/Apache1. A great advantage of using virtual folder in Apache is that a user doesn’t really distinguish between the physical and dynamic portions of your site, as we’ll better see in the next example. Because the development of Apache modules with WebBroker is almost identical to the development of other types of programs, instead of building an actual application (besides the over-simplistic Apache1 example) I’ve created a new version of the BrokDemo example, already available as a CGI or ISAPI program. To do this, I’ve taken the project file of an Chapter 22 • Web Programming with WebBroker and WebSnap Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 979 Apache module from that example, added the local Web modules to it, and modified the project source code to reflect the proper module name and handler. I’ve actually defined them differently than the default, as the following code excerpt demonstrates: library BrokApache; exports apache_module name ‘brokdemo_module’; begin ContentType:= ‘brokdemo-handler’; After compiling the module and editing the http.conf file as explained above, the program was ready to be used in two different ways, CGI and dynamic module. An obvious difference between the two types of invocation is their URLs: http://localhost/scripts/brokcgi.exe/table http://localhost/brokdemo/table Not only is the latter URL simpler, but it hides the fact that we are running an application with a /table parameter. In fact, it seems we are accessing a specific folder of the server. Actu- ally, the Apache configuration file can be modified to also invoke CGI applications through virtual folders, which explains why CGI applications have a path-like command prefixing the request. Another related explanation is that Linux CGI applications, like any other executable file, have no extension whatsoever, so their names still seem to be part of a path. Practical Examples After this general introduction to the core idea of the development of server-side applications with WebBroker, let me end this part of the chapter with two simple practical examples. The first is a classic Web counter. The second is an extension of the WebFind program presented in the preceding chapter to produce a dynamic page instead of filling a list box. A Web Hit Counter The server-side applications we’ve built up to now were based only on text. Of course, you can easily add references to existing graphics files. What’s more interesting, however, is to build server-side programs capable of generating graphics that change over time. A typical example is a page hit counter. To write a Web counter, we save the current number of hits to a file and then read and increase the value every time the counter program is called. How do we return this information? If all we need is some HTML text with the number of hits, the code is straightforward: procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); Practical Examples Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 980 var nHit: Integer; LogFile: Text; LogFileName: string; begin LogFileName := ‘WebCont.log’; System.Assign (LogFile, LogFileName); try // read if the file exists if FileExists (LogFileName) then begin Reset (LogFile); Readln (LogFile, nHit); Inc (nHit); end else nHit := 0; // saves the new data Rewrite (LogFile); Writeln (LogFile, nHit); finally Close (LogFile); end; Response.Content := IntToStr (nHit); end; WARNING This simple file handling does not scale. When multiple visitors hit the page at the same time, this code may return false results or fail with a file I/O error because a request in another thread has the file open for reading while this thread tries to open the file for writing. To sup- port a similar scenario, you’ll need to use a mutex (or a critical section in a multithreaded pro- gram) to let each subsequent thread wait until the thread currently using the file has completed its task. What’s a little more interesting is to create a graphical counter that can be easily embedded into any HTML page. There are basically two approaches for building a graphical counter: you can prepare a bitmap for each digit up front and then combine them in the program, or you can simply let the program draw over a memory bitmap to produce the graphic you want to return. In the WebCount program, I’ve chosen this second approach. Basically, we can create an Image component that holds a memory bitmap, which we can paint on with the usual methods of the TCanvas class. Then we can attach this bitmap to a TJpegImage object. Accessing the bitmap through the JpegImage component converts the Chapter 22 • Web Programming with WebBroker and WebSnap Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 981 image to the JPEG format. At this point, we can save the JPEG data to a stream and return it. As you can see, there are many steps, but the code is not really complex: // create a bitmap in memory Bitmap := TBitmap.Create; try Bitmap.Width := 120; Bitmap.Height := 25; // draw the digits Bitmap.Canvas.Font.Name := ‘Arial’; Bitmap.Canvas.Font.Size := 14; Bitmap.Canvas.Font.Color := RGB (255, 127, 0); Bitmap.Canvas.Font.Style := [fsBold]; Bitmap.Canvas.TextOut (1, 1, ‘Hits: ‘ + FormatFloat (‘###,###,###’, Int (nHit))); // convert to JPEG and output Jpeg1 := TJpegImage.Create; try Jpeg1.CompressionQuality := 50; Jpeg1.Assign(Bitmap); Stream := TMemoryStream.Create; Jpeg1.SaveToStream (Stream); Stream.Position := 0; Response.ContentStream := Stream; Response.ContentType := ‘image/jpeg’; Response.SendResponse; // the response object will free the stream finally Jpeg1.Free; end; finally Bitmap.Free; end; The three statements responsible for returning the JPEG image are the two that set the ContentStream and ContentType properties of the Response and the final call to SendResponse. The content type must match one of the possible MIME types accepted by the browser, and the order of these three statements is relevant. There is also a SendStream method in the Response object, but it should be called only after sending the type of the data with a sepa- rate call. You can see the effect of this program in Figure 22.9. To obtain it, I’ve added the following code to an HTML page: <img src=”http://localhost/scripts/webcount.exe” border=0 alt=”hit counter”> Practical Examples Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com [...]... is included only in the Enterprise version of Delphi, not all Delphi programmers have the chance to use it (needing to limit their expense to the Professional version of Delphi 6, which includes WebBroker) WebSnap has a few definitive advantages over the plain WebBroker, such as allowing for multiple pages, integrating server-side scripting, and XSL and Delphi 5 Internet Express technology (these last... following ASP script (only slightly modified from the demo script the Delphi wizard will generate for you): Message The interesting element is that the same script (or another ASP script of the same application) can also set global values our Delphi object can access Similarly, multiple objects can communicate,... can only discuss it in relation to Delphi programming One of the features of ASP is that it allows you to create COM objects within a script, and you can write those COM objects in Delphi The Delphi IDE even provides specific support classes and a wizard to help you build ASP objects Compared to ISAPI or CGI, one of the advantages is that your ASP object built in Delphi can get access to session and... ‘DeleteRow’ Caption = ‘Delete’ Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 100 5 10 06 Chapter 22 • Web Programming with WebBroker and WebSnap PageName = ‘table’ end end object AdapterFieldGroup1: TAdapterFieldGroup Adapter = table.DataSetAdapter1 AdapterMode = ‘Edit’ object FldCUST_NO: TAdapterDisplayField DisplayWidth = 10 FieldName = ‘CUST_NO’ end object FldCUSTOMER: TAdapterDisplayField DisplayWidth... ‘http://www.google.com/search?as_q=borland +delphi& num =100 ’; procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var I: integer; begin if not cds.Active then cds.CreateDataSet else cds.EmptyDataSet; for i := 0 to 5 do // how many pages? begin // get the data form the search site GrabHtml (strSearch + ‘&start=’ + IntToStr (i *100 )); // scan it... and unloaded (with OnStartPage and OnEndPage) WebSnap After this lengthy introduction of the core elements of the development of Web server applications with Delphi, we can finally focus on some of the new related technologies introduced in Delphi 6 There were two good reasons for not jumping right into this topic from the beginning of this chapter The first is that WebSnap builds on the foundation offered... Page-Level Event Methods option for the AspTest example), Delphi will bring up the Type Library editor, where you can prepare a list of properties and methods for your ASP object Simply add the features you need, and then write their code For example, you can write the following simple test method: procedure Tasptest.ShowData; begin Response.Write (‘Delphi wrote this text’); end; and activate it... unless it contains spaces or other special characters The result of this program is a rather slow application (because of the multiple HTTP requests it must forward) producing output like Figure 22 .10 FIGURE 22 .10: The WebSearch program shows the result of the multiple searches done on Google Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 984 Chapter 22 • Web Programming with WebBroker and WebSnap... module, including by default an HTML Result page, where you can see the HTML generated after evaluating the scripts, and a Preview page hosting what a user will see inside a browser FIGURE 22.14: The Delphi 6 editor for a WebSnap module includes a simple HTML editor and a preview of its output Copyright ©2001 SYBEX, Inc., Alameda, CA www.sybex.com 990 TIP Chapter 22 • Web Programming with WebBroker and... multiplied values with the following scripting code (see Figure 22. 16 for its output): . only in the Enterprise version of Delphi, not all Delphi programmers have the chance to use it (needing to limit their expense to the Professional version of Delphi 6, which includes WebBroker). WebSnap. in relation to Delphi pro- gramming. One of the features of ASP is that it allows you to create COM objects within a script, and you can write those COM objects in Delphi. The Delphi IDE even. the development of Web server appli- cations with Delphi, we can finally focus on some of the new related technologies introduced in Delphi 6. There were two good reasons for not jumping right