After this general introduction to developing server-side applications with WebBroker, I'll end this part of the chapter with two practical examples. The first is a classic web counter. The second is an extension of the WebFind program presented in Chapter 19, which produces a dynamic page instead of filling a list box.
The server-side applications you'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, you save the current number of hits to a file and then read and increase the value every time the counter program is called. To return this information, all you need is HTML text with the number of hits. The code is straightforward:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); 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;
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 support a similar scenario, you'll need to use a mutex (or a critical section in a multithreaded program) to let each subsequent thread wait until the thread currently using the file has completed its task.
It's more interesting to create a graphical counter that can be easily embedded into any HTML page. There are two approaches to building a graphical counter: You can prepare a bitmap for each digit up front and then combine them in the program, or you can let the program draw over a memory bitmap to produce the graphic you want to return. In the WebCount program, I chose the second approach.
Basically, you can create an Image component that holds a memory bitmap, which you can paint on with the usual methods of the TCanvas class. Then you can attach this bitmap to a TJpegImage object. Accessing the bitmap through the JpegImage component converts the image to the JPEG format. Then, you can save the JPEG data to a stream and return it. As you can see, there are many steps, but the code is not complicated:
// 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. The Response object also has a SendStream method, but it should be called only after sending the type of the data with a separate call. Here you can see the effect of this program:
To embed the program in a page, add the following code to the HTML:
<img src="http://localhost/scripts/webcount.exe" border=0 alt="hit counter">
In Chapter 19, I discussed the use of the Indy HTTP client component to retrieve the result of a search on the Google website. Let's extend the example, turning it into a server-side application. The WebSearcher program, available as a CGI application or a Web App Debugger executable, has two actions: The first returns the HTML retrieved by the search engine; and the second parses the HTML filling a client data set component, which is hooked to a table page producer for generating the final output. Here is the code for the second action:
const strSearch = '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 to fill the cds HtmlStringToCds; end; cds.First; // return producer content Response.Content := DataSetTableProducer1.Content; end;
The GrabHtml method is identical to the WebFind example. The HtlStringToCds method is similar to the corresponding method of the WebFind example (which adds the items to a list box); it adds the addresses and their textual descriptions by calling
cds.InsertRecord ([0, strAddr, strText]);
The ClientDataSet component is set up with three fields: the two strings plus a line counter. This extra empty field is required in order to include the extra column in the table producer. The code fills the column in the cell-formatting event, which also adds the hyperlink:
procedure TWebModule1.DataSetTableProducer1FormatCell(Sender: TObject; CellRow, CellColumn: Integer; var BgColor: THTMLBgColor; var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs, CellData: String); begin if CellRow <> 0 then case CellColumn of 0: CellData := IntToStr (CellRow); 1: CellData := '<a href="' + CellData + '">' + SplitLong(CellData) + '</a>'; 2: CellData := SplitLong (CellData); end; end;
The call to SplitLong is used to add extra spaces in the output text, to avoid having grid columns that are too large—the browser won't split the text on multiple lines 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) that produces output like that shown in Figure 20.5.