Databases - Delphi/WebServer Debug Techniques
Delphi has one of the best interactive debuggers available ...
but you can't use it with web servers.
There are many types of web servers and Delphi allows you to develop those which
can be directly accessed via their own ports.
This page applies to both CGI and ISAPI html producers - web servers that
are run by other web servers (such as IIS or Apache)
and generate html that is normally displayed in a browser.
Running the code
| Debug Page
| Conditional Debug Trace
| Program Trace
Internal Server Error 500
- Undefined Object
| Database Connection Error
| File not found
Running the code
When developing code, you need to compile and execute the program.
Web servers have special problems.
- Normally, there is no way to run the program by it self
- There is no way to use the Delphi interactive debugger
- The compiled file may be cached (accessed in a way that keeps
you from testing your changes)
- CGI files are ok
- ISAPI dll's require special software to unload the old copy
and then run the new copy.
Set
Project / Options / Directories/Conditionals / Output directory
to a directory available to IIS (or the server of your choice).
Debug Page
I find it useful to define a Debug action
to display various configuration parameters
(such as database connection info).
By actually coding a separate Debug subroutine makes it easier
to call it (fewer parameters).
// This is the debug action's OnAction method
procedure TWebModule_AB.WebModule_ABWebActionItem_DebugAction(
Sender: TObject; Request: TWebRequest; Response: TWebResponse;
var Handled: Boolean);
begin
debug(Response);
end;
// I use this to add debug information to all possible web pages
procedure TWebModule_AB.WebModuleAfterDispatch(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
// debug(Response); // Remove double slashes to include Debug info
end;
procedure TWebModule_AB.debug(Response: TWebResponse);
var
s, Con_Str : string;
begin
Trace_Str := Trace_Str + '
debug';
try
if SamplesDataModule.KADaoDatabase1.Connected then
Con_Str := 'true'
else
Con_Str := 'false';
s := 'Debug info
'
+ ' Current Database - ' + SamplesDataModule.Database
+ '
Connected - ' + Con_Str
+ '
End of debug info
'
+ 'Start of Trace
' + Trace_Str + 'End of Trace
';
;
Response.Content := Response.Content + s;
except
on E: Exception do begin
//ErrorDialog(E.Message, E.HelpContext);
s := s + '' + E.Message ;
Response.Content := 'TWebModule_AB.debug failed - ' + s
+ '
Start of Trace
' + Trace_Str + 'End of Trace
';
end;
end;
end;
Conditional Debug Trace
I want a way to turn the debug trace on and off without recompiling the program.
One way to accomplish this is to pass a query parameter
with the url
(query parameters start with a question mark)
http://computer-name/path/WebServer.exe/table?debug=p
In the following code, the ?debug=p causes the server to display the trace
and other debug data.
var // global variables
Trace_Str : string;
Debug_Trace : string;
procedure TWebModule1.WebModuleAfterDispatch(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
Debug_parm : string;
begin
Trace_Str := Trace_Str + '
{WebModuleAfterDispatch}' + CRLF_mc ;
Debug_parm := Request.QueryFields.Values ['debug'];
if Debug_parm <> '' then debug(Response);
if Debug_parm = 'keep' then Debug_Trace := 'Keep';
if Debug_parm = 'clear' then Debug_Trace := '';
if Debug_Trace = '' then begin
Trace_Str := ''; // clear the trace string ... for ISAPI dll's
end;
if length(Trace_Str) > 30000 then Trace_Str := ''; // just a safety
end;
One issue is that, when the server is run as an ISAPI dll
(ie, it stays in memory for days at a time),
the trace could get very long because
the Trace_Str global parameter is common to all page requests.
The example code sets (and clears) a flag based on the value of the
debug parameter. This way, the trace string is automatically
cleared after each page is generated
unless instructed not to.
http://computer-name/path/WebServer.exe/table?debug=keep
http://computer-name/path/WebServer.exe/table?debug=clear
If the trace string was never cleared, it would eventually get
large enough to crash the server.
Forms
When using web forms, I sometimes use a checkbox
to control displaying the debug information.
This code will return a value ONLY IF the box is checked.
This code will be hidden ... but still show the debug information.
Program Trace
When rendering a web page, many subroutines are called.
This is one method to generate and display a trace.
At the beginning of the application, define Trace_Str
as a global variable
In each subroutine, add
Trace_Str := Trace_Str + '
{subroutine name}' + CRLF_mc;
This will display the trace
procedure TWebModule_AB.WebModuleAfterDispatch(...);
begin
Trace_Str := 'Start of Trace
' + Trace_Str + 'End of Trace
';
Response.Content := Response.Content + Trace_Str ;
end;
I use code similar to this to display various variables.
Trace_Str := Trace_Str + '
{WebModuleBeforeDispatch}' + CRLF_mc ;
Trace_Str := Trace_Str + '' + CRLF_mc
+ 'Request.PathInfo' + ' | | ' + Request.PathInfo + CRLF_mc
+ ' |
Request.Content' + ' | | ' + Request.Content + CRLF_mc
+ ' |
Request.ContentFields.Text' + ' | | ' + Request.ContentFields.Text + CRLF_mc
+ ' |
Request.Query' + ' | | ' + Request.Query + CRLF_mc;
if SamplesDataModule = nil then
Trace_Str := Trace_Str + ' |
SamplesDataModule | | nil' + CRLF_mc
else
Trace_Str := Trace_Str + ' |
SamplesDataModule | | ok' + CRLF_mc;
Trace_Str := Trace_Str + ' |
' + CRLF_mc ; // closes the table
Because this technique uses a global variable,
the results may be incorrect in a true multiuser system.
CRLF_mc is a global variable I use to add a CR/LF pair to strings,
in this case to make the html more readable.
Internal Server Error 500
If you try to access an unallocated object or connect
to a database without the right permissions, the
browser will display an error - either "page not found"
or "Internal Server Error 500". If you are very lucky,
there may be more specific information.
Errors in TWebModule_xyz.WebModuleCreate
do not produce any 500 errors.
To detect/report these problems,
set Trace_Str in a try..except block.
These are a couple of common errors.
(Common, but hard to trouble shoot.)
Undefined Object
Internal Server Error 500
Exception: EAccessViolation
Message: Access violation at address 00489C37
in module 'Experiments.exe'.
Read of address 00000050
|
Generic error
The page cannot be displayed
HTTP 500 - Internal server error
|
I don't understand why there are 2 different errors, but
both of these errors were caused by exactly the same *.exe file -
it tried to access an undefined object.
Fixed by adding (caused by removing)
SamplesDataModule := TSamplesDataModule.Create(self);
Database Connection Error
When moving a working Delphi application
over to web server
(by reusing the DataModule),
SamplesDataModule.KADaoDatabase1.Connected := true; // this fails
generated the following error
Internal Server Error 500
Exception: EReadError
Message: Error reading KADaoDatabase1.Connected:
The Microsoft Jet database engine cannot open the file
'c:\inetpub\wwwroot\rlc\SLP_DB\SamplePrep\Samples.mdb'.
It is already opened exclusively by another user,
or you need permission to view its data
|
The solution is in the security settings
- Under Windows XP NTFS permissions - "Network" must have write permission
to create the *.ldb (lock) file
- Under Windows 2000 NTFS permissions - only "Everyone" needs write permission
I can't figure out why some directories inherit security settings from their
parent directories (the check box is grayed out) and others don't.
It took a couple of days to find this because there were 2 directories
under a parent. The parent directory had the "correct" settings and a
CGI file in one directory had no problems (the settings were inherited).
However, the other directory had inheritance disabled
(I don't even know how to do that) and produced this error.
I also don't know why Windows 2000 and Windows XP require different settings
- Network vs Everyone
Notice that IIS does not need to be configued (via the control panel)
to allow user write premission. The write premission
must be set via Windows Explorer.
For more information, see
Microsoft KB article 306269
about problems using ASP to access MS Access.
(Different language, same problems because both use IIS.)
It even suggest using
Sysinternals File Monitor (free)
to track down the problem.
File not found
This exception was generated trying to open a file that was not there.
Internal Server Error 500
Exception: EReadError
Message: Error reading KADaoDatabase1.Connected: Cannot find Database:
c:\inetpub\wwwroot\rlc\SLP_DB\SamplePrep\Samples.mdb
|
Author: Robert Clemenzi -
clemenzi@cpcug.org