Link to home
Start Free TrialLog in
Avatar of Richard2000
Richard2000

asked on

CGI program won't work

Hi,

I have created a simple CGI program for test purposes using Delphi 4 Pro.

To do this I have created a new application with only one unit containing the following code:

program test_cgi;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils;

{$R *.RES}

var
  Params, Output: string;

begin
  Readln(Params);
  Output := '<html><head><title>CGI Test</title></head><body>' +
    'Parameters: ' + Params + '<br>' + 'A random number: ' + IntToStr(Random(100) + 1) +
    '</body></html>';
  Writeln(Output);
end.

I compiled it successfully and renamed the EXE from test_cgi.exe to test_cgi.cgi.  I uploaded it with FTP successfully to the cgi-bin directory provided by my web hosting company who hosts my site on NT servers.  All seems well so far.

But when I try to run the script by entering the URL into IE 6 i.e. http://www.mysite.com/cgi-bin/test_cgi.cgi it never seems to returns anything.  The progress bar on IE 6 just increments *very* slowly (it took about a minute to reach 50%).  I don't see why it should take so long because the script is very simple and there is no possibility of it getting stuck in any loops because it doesn't use any.  All other HTML files and ASP files work fine on my site, so what is wrong?

Thanks in Advance,

Richard
Avatar of loop_until
loop_until

First, you can have it rename it to cgi for you automatically in Project | Options | Application | Output settings, and set Target file extension to 'cgi'. :-)

Next, is it possible your web hosting company does not allow running of executable? Altought it is on NT and *could* run on it, you might want to ask them if they set the permissions for doing so.

To see if your code works, just run your .EXE from the DOS prompt on your computer. If you have the text getting out, you're set ;-).


1) maybe they have Linux server ? ;-) ask ISP details
2) try to run it on your own PC (use personal web server or that server: http://www.ritlabs.com/tinyweb/index.html)
Hi,

>Readln(Params);
What is this line supposed to do?

Regards, Geo
Avatar of Richard2000

ASKER

Hi loop_until,

Thanks for your comments.

1) Nice tip - thanks!

2) As far as I'm aware my web hosting company does allow the running of executables and I have the required permissions set.  I'll just e-mail them and check to be sure though.  I have compiled a CGI program in Microsoft Visual C++ before using the same CGI-BIN directory with the same web hosting company and it worked fine, so I'm puzzled as to why this Delphi one isn't working.

3) I've tried running the program locally and it did return the HTML text in the window.  I ran it from within Windows '98 SE and I didn't see any text until I pressed a key which closed the window.  The HTML text then flashed up for a brief moment.

Richard
Hi geobul,

Readln(Params) is supposed to get the parameters that are passed into the CGI program.

For example, if the user runs the program like this:

http://www.mysite.com/cgi-bin/test_cgi.cgi?p1=some+value&p2=another+value

Params would contain "p1=some+value&p2=another+value".

Richard
When you tested on your server, did you pass by some parameters? Maybe it is waiting for something...

Most of the time, when you see the very slow progress bar, it is because the CGI is stuck somewhere. Might want to try without the Readln(Params) first, just to see if you have an HTML output and then test with parameters...

Just gessing thought ;-)...
Hi,

I've tried running the CGI from IE 6 with and without parameters and it failed with both.  This was with using the Readln(Params), I haven't tried it without the Readln(Params) yet.

I've read an interesting article at http://www.undu.com/Articles/990315b.html.  The code given there uses Readln to get the parameters.

Richard
you don't send an HTTP header, this is most likely a problem...

try this procedure:

procedure HTMLout( CGIout : String );
var
   HTMLSize : Integer;
begin
    writeln( 'Content-length: ' + Inttostr( Length( CGIout ) + 1 ) );
    write( 'Content-type: text/html'#10#10#10 );
    writeln( CGIout );
end;


sorry, remove this:

var
  HTMLSize : Integer;
Hi,

I've done some more testing and this is what I've found:

The main problem seems to be with the Readln(Params) code.  When I removed it and replaced it with Params := '' the CGI program did run properly.  But when it is included it won't output anything regardless of whether parameters or headers are present.

Why does this Readln(Params) code fail?  I need to be able to get the parameters somehow, preferably as one string (still left encoded) for processing.  How else can I get the parameters?

The second problem as Alisher_N correctly points out is the need to send a HTTP header.  The program did run without sending a header but it displayed an error message at the top of the page saying that the CGI program had misbehaved by not providing the header, however the HTML I sent was still displayed underneath.

I have a few questions about Alisher_N's code below:

procedure HTMLout( CGIout : String );
begin
   writeln( 'Content-length: ' + Inttostr( Length( CGIout ) + 1 ) );
   write( 'Content-type: text/html'#10#10#10 );
   writeln( CGIout );
end;

1) Why is the Content-length the length of the string + 1?  I'm wondering what the reason is for the + 1 and why it is not simply the length of the string.

2) Why on the second line is Write used rather than Writeln?

3) Why are the linefeed characters (#10#10#10) after the closing apostrophe because they won't be written to the output?  Should they be included before the final apostrophe?

4) Why are there *3* linefeed characters?

5) Should each linefeed character be paired with a carriage-return e.g. #13#10, rather than a #10 on its own?

Thanks in Advance,

Richard
Don't you want to use ParamStr and ParamCount?

var
  i: Integer;
begin
  for i := 1 to ParamCount do
  begin
    if LowerCase(ParamStr(i)) = 'beep' then
      Beep
    else if LowerCase(ParamStr(i)) = 'exit' then
      Application.Terminate;
  end;
end;

Or use

  var CmdLine: PChar platform;


Does that work for you?
Avatar of Russell Libby

Richard,

Two comments:
 
1.) Don't use ReadLn, it "reads a line of data". A line is terminated by an end of line char (#10 or #13). You aren't going to get either (per MSDN regarding IE 4.0 versions and higher), thus ReadLn() gets "stuck"

One example of getting the parameter data:

var
 datalen: Integer;
 stdin:   Integer;
 output:  String;

 stdin:=GetStdHandle(STD_INPUT_HANDLE);
 datalen:=FileSeek(stdin, 0, FILE_END);
 if (datalen = 0) then
 begin
   // No data passed in, do what you need to do
 end
 else
 begin
   // Reset pointer in file to the start
   FileSeek(stdin, 0, FILE_BEGIN);
   // Set string buffer to size that will be read
   SetString(output, nil, datalen);
   // Read from the file
   FileRead(stdin, output[1], datalen);
 end;

You could also perform the FileSeek test to get the length, reset the file pointer, and if you had data you could then use Read(output). Or, you could also get the environment variable for "CONTENT_LENGTH" and use the numeric value for the FileRead size. Couple of choices that way.

2.) The write is used to control the automatic crlf from being added (which is what WriteLn does). The header sequence should have *2* crlf (#13#10) following it
ie:

Write('Content-Type: text/html'+#13#10#13#10);
// ... write your data out, ie:
Write('<html>Hello world</html>');

A couple of notes: If you don't add the "Content-Length: xxx" header, then IIS will do it for you. (based on the data in stdout after the dbl crlf). You might have also picked up on the fact that there is no mention of writing "HTTP/1.0 200 OK" to the output. That is because IIS will do this for you as well. (I have had no problems just using the content-type header by itself). If you do add these headers yourself, make sure they are correct.

Hope this helps,
Russell
ASKER CERTIFIED SOLUTION
Avatar of Alisher_N
Alisher_N

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi,

I asked about the purpose of your ReadLn because you could have wanted some kind of file operation to be performed in the beginning.

CGI scripts receive its parameters (query string) and all the other info via environment variables. For query string there is a special env var called QUERY_STRING. You should read this variable instead of using Read/Readln.

Use something like the following to read environment variables:

function GetEnvVar(variable: string) : string;
begin
 SetLength(result,1000);
 SetLength(result,GetEnvironmentVariable(pchar(variable),pchar(result),1000));
end;

// usage:
var
  theQueryString: string;
...
theQueryString := GetEnvVar('QUERY_STRING');

Regards, Geo
in addition ty my previous posts, read this detailed description of how GET and POST work:

http://www.speakeasy.org/%7Ecgires/readdata/index.html
http://www.jmarshall.com/easy/cgi/
About the header:

It contains one or more lines separated by CR+LF and followed by one blank line (one extra CR+LF pair) before the HTML part. Example (you are filling in the output html page):
<-- begin
Content-type: text/html

<html>
...
</html
--> end

or if you are sending a file as output:
<-- begin
Location: /path/doc.txt

--> end

and many more. Take a look at http://hoohoo.ncsa.uiuc.edu/cgi/interface.html about environment variables describing the request and header options.

Regards, Geo

Richard,
Just to set all the facts straight:

W3C specs for Common Gateway Interface
(http://cgi-spec.golux.com):

There are 3 defined server directives, those being:
Content-type, Location, and Status (new spec also allows for Extension header fields). Any headers which are not server directives are sent back to the client without modification.

<quote>
7.2.2. HTTP header fields
   
   The script MAY return any other header fields defined
   by the specification for the SERVER_PROTOCOL (HTTP/1.0
   [3] or HTTP/1.1 [8]). Servers MUST resolve conflicts
   beteen CGI header and HTTP header formats or names (see
   section 8).

<end quote>

So yes, it is legal for the cgi to write the "HTTP/1.0 200 OK"+crlf itself. (They even demonstrate this in a few of their samples...). All I was trying to say is that while I don't recommend it, it is technically legal :-). That goes for Content-Length as well.

In regards to the LF vs CRLF comments:

<quote>
The server MUST translate the header data from the CGI header field syntax to the HTTP header field syntax if these differ. For example, the character sequence for newline (such as Unix's ASCII NL) used by CGI scripts may not be the same as that used by HTTP (ASCII CR followed by LF). The server MUST also resolve any conflicts between header fields returned by the script and header fields that it would otherwise send itself.
<end quote>

So sending CRLF, which is the character sequence for HTTP line termination, MUST be accepted by all compliant servers.

In regards to Read() and ReadLn().

You should only attempt to read the number of bytes specified by the environment variable for CONTENT_LENGTH.
Do not assume an EOL, or an EOF for that matter. (Read() until EOF(input) can also hang)

<quote>
If the CONTENT_LENGTH value (see section 6.1.2) is non-NULL, the server MUST supply at least that many bytes to scripts on the standard input stream. Scripts are not obliged to read the data. Servers MAY signal an EOF condition after CONTENT_LENGTH bytes have been read, but are not obligated to do so. Therefore, scripts MUST NOT attempt to read more than CONTENT_LENGTH bytes, even if more data are available.
<end quote>


Hope this clarifies

-------

Russell


Hi,

Many thanks for your interesting and useful comments.  Based upon them I have now written a new version below.  What do you think?  I've tried it with/without parameters and it runs fine with no errors :-).

program test_cgi;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils;

const CRLF: string = Chr(13) + Chr(10);

function GetParams: string;
var
  BytesNeeded: DWORD;
begin
  BytesNeeded := GetEnvironmentVariable('QUERY_STRING', nil, 0);
  if BytesNeeded > 0 then
  begin
    SetLength(Result, BytesNeeded - 1);
    GetEnvironmentVariable('QUERY_STRING', PChar(Result), BytesNeeded);
  end
  else
    Result := '';
end;

procedure WriteHeaderAndHTML(HTML: string);
begin
  Writeln('Content-type: text/html');
  Writeln('Content-length: ' + IntToStr(Length(HTML)));
  Writeln('HTTP/1.0 200 OK');
  Writeln('');

  Write(HTML);
end;

{$R *.RES}

var
  S: string;

begin
  Randomize;

  S := '<html>' + CRLF +
  '<head>' + CRLF +
  '<title>CGI Test</title>' + CRLF +
  '</head>' + CRLF +
  '<body>' + CRLF +
  'Parameters: ' + GetParams + '<br>' + CRLF +
  'A random number: ' + IntToStr(Random(100) + 1) + CRLF +
  '</body>' + CRLF +
  '</html>';

  WriteHeaderAndHTML(S);
end.

A few points to note:

* I've added the Content-length and HTTP/1.0 200 OK header information.  Whilst this isn't strictly necessary it hasn't caused any noticable problems.
* I've defined CRLF as a constant to add CRLFs were needed.  I've always used this method myself in the past before.  Of course, #13#10 could be used instead.  I'm pleased to see this alternative method being used as I hadn't thought of that before.
* The Randomize procedure has been added because without it the random number was always 1 (I assume this is because the same seed would have been used each time).
* The exact number of bytes needed for the buffer size with GetEnvironmentVariable is calculated before the value is actually retrieved rather than using a fixed buffer size.

Richard
Hi,

I've often wondered - if I run a CGI program on a remote server and it "gets stuck" (such as it gets into an endless loop), what will happen?  How long would it run for?  Is there any way (or need) to stop it?

Of course, when running a program on a local machine if something goes wrong it is possible to stop execution with the debugger, but when it is running on a remote server this option isn't accessible.

Richard

Richard,

I can only speak for the IIS web server in regards to your "hung" CGI question:

IIS allows you to limit both the CPU usage (throttling), as well as the timeout for CGI executables. I believe the default value for the timeout is set to 300 seconds. (IIS 4.0 and 5.0). These values can be changed from the IIS Admin snapin.

Russell
Yes, Russell's right, normally there is a setting in your server to set the maximum script/executable execution time. After x seconds, it will issue a timeout error message.
few notes:

1)
S := '<html>' + CRLF +
 '<head>' + CRLF +
 '<title>CGI Test</title>' + CRLF +.........


actually you don't need these CRLF here! html can go straight as stream of characters, you can remove CRLFs and save some bytes, unless you are going to inspect the received HTML source on client's PC

2)
I would recommend you to debug everything on your local system, install any free HTTP server available (and I have mentioned before) and do your CGI locally, only when you have it working fine, put to ISP server... this will save a lot of time and nerves ;-)

3)
do not forget that GET is default method, if you use POST for forms, you should add this option in code
Hi,

Thanks for your comments including the information on what happens if a CGI program hangs.  I was hoping there would be something like that in place, but as I'm new to CGI I thought I'd ask to be sure :-).

Regarding Alisha_N's comment:

1) I've added the CRLFs to the HTML to make it look neater.  Of course from a technical point of view these aren't necessary.  But when the pages become more complex (such as pages that use tables) the extra CRLFs make it easier to see the structure of the document.

2) I always try to test my programs out locally first.  I'll download and install some software to make it easier.

3) That's interesting.  This probably explains why the article I read used Readln to access the parameters rather than the environment variables (the article was related to forms which would use POST rather than GET).  At the moment, I'm only needing GET but I'll have to change the code if I need to use it with POST in the future.

Richard
Force accepted Alisha_N's comments per Richard2000's request (https://www.experts-exchange.com/questions/20400491/Share-points-for-Q-20397280.html).

loop_until, geobul and rlibby:
Please check the Delphi section, "Points for" to get credit for your help here.

Thanks all!

Tuvok
Experts-Exchange Moderator
Richard, thanks for the points.

Regards, Geo
thanks to all ;)
Thanks Richards for the points and thanks Alisher_N for the great help you provided at the end. I feel pretty bad of getting some points compared with what you provided ;-).

Have a nice day everyone!
oh, come on...
this joint action anyway ;-)