Solved

FastCGI with Delphi

Posted on 2013-01-06
24
2,002 Views
Last Modified: 2013-08-05
Hello Experts,

I am trying to convert some FastCGI sample code provided by Coast Research & Development (http://www.coastrd.com) from PowerBasic to Delphi.

First off, Coast R&D have provided a free FastCGI API (libfcgi2.dll) which includes a SIGTERM handler for IIS. Coast R&D also provide headers for PowerBasic, and Delphi (among others). They have also written some sample applications in PowerBasic, but no examples for Delphi. So I have tried to convert the PowerBasic example (Which I have compiled and tested successfully in Powerbasic), unfortunately I am having no luck with the Delphi code.

The PowerBasic code is as follows,

#COMPILE EXE

#INCLUDE "FCGX_Header.inc"

FUNCTION PBMAIN

  LOCAL sReply AS STRING
  LOCAL FCGXReq AS FCGX_REQUEST '// FCGX Structure

    '// Create the STDIN/STDOUT/STDERR buffers, Connect with the Web Server via FCGI Library
    CALL FCGX_InitRequest(VARPTR(FCGXReq) , 0, 0)


    DO '// Main Request processing loop

      '// Execution blocked here until an HTTP request arrives
      IF FCGX_Accept_r(VARPTR(FCGXReq) ) < 0 THEN EXIT LOOP '// SIGTERM/Error recieved - jump out and cleanup


      '// The reply must begin with a valid HTTP header
      sReply = "Content-Type: text/html"+$CRLF+$CRLF
      sReply = sReply + "ReqCount=" + STR$(FCGXReq.ReqCount)

      '// Add the Reply string to the STDOUT buffer
      IF FCGX_PutStr( STRPTR(sReply), LEN(sReply), FCGXReq.pOut ) < 0 THEN EXIT LOOP '// Error Occured

    LOOP '// STDOUT is sent when FCGX_Accept_r is called again


END FUNCTION

Open in new window


Now my failed conversion attempt...

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, libfcgi2;
var
    FCGXReq   :FCGX_Request;
    sReply    :WideString;
begin

    FCGX_InitRequest(FCGXReq, 0, 0);

    while True do  // Main Request processing loop
    begin
      // Execution blocked here until an HTTP request arrives
      if FCGX_Accept_r(FCGXReq) < 0 then break;  // SIGTERM/Error recieved - jump out and cleanup

      // The reply must begin with a valid HTTP header
      sReply := 'Content-Type: text/html' + #13#10 +#13#10;
      sReply := sReply + 'ReqCount=' + IntToStr(FCGXReq.ReqCount);

      // Add the Reply string to the STDOUT buffer
      IF FCGX_PutStr(PWideChar(sReply), Length(sReply), FCGXReq.pOut ) < 0 then break // Error Occured
    end;

end.

Open in new window


From what I can make out IIS fails with an Error 500 on the first line of code 'FCGX_InitRequest(FCGXReq, 0, 0);' I have tried passing in a pointer to the structure but the compiler rejects it.

Any ideas will much appreciated.

Thanks.
libfcgi2.pas
FCGX-Header.txt
0
Comment
Question by:CursoryGlance
  • 9
  • 9
  • 5
  • +1
24 Comments
 
LVL 25

Expert Comment

by:Sinisa Vuk
ID: 38748752
you should initialize structure FCGX_Request first before calling FCGX_InitRequest.

...
ZeroMemory(@FCGXReq, SizeOf(FCGX_Request));
FCGX_InitRequest(FCGXReq, 0, 0);
...

Open in new window

Because of function definition (first parameter is defined as var), you don't need to pass pointer to structure. Which delphi version you use?
0
 

Author Comment

by:CursoryGlance
ID: 38749240
Hello Sinisav,

I have added the 'Windows' unit to the 'uses' clause and also added the ZeroMemory function call. No change - IIS is still returning error 500. I am using Delphi XE3 on Windows 7 64bit.

Many thanks for your suggestion.
0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 38750688
Did you try:
FCGX_InitRequest(@FCGXReq, 0, 0);

Also, I saw in their api the function should return zero and to call their error msg if not working.

e.g. try

if FCGX_InitRequest(@FCGXReq, 0, 0) <> 0
  then Writeln(FCGX_GetErrorStr());
0
 

Author Comment

by:CursoryGlance
ID: 38751337
Hello PierreC,

I tried FCGX_InitRequest(@FCGXReq, 0, 0); but the compiler rejected the call with the following error.

[dcc32 Error] Project1.dpr(18): E2033 Types of actual and formal var parameters must be identical

The compiler also rejected the function call 'FCGX_GetErrorStr()' as it's not defined in libfcgi2.pas file. Where did you find it and what type of string does it return - cdecl null terminated perhaps?

Thanks for your help.
0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 38752688
ok, the first parameter must then be of type FCGX_Request as you had it. Remove the @

As for the info on how to call and to use the GetErrorStr function, I saw it here:
http://www.coastrd.com/cheader

I looked in the dll from coastrd.com and the FCGX_GetErrorStr function is not an export thereof, so I'm not sure. I will see if I can find it somewhere but maybe this comment is intended for future development?
0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 38752736
Another thing came to mind. Is the FastCGI library actually loaded? Do you call FastCGI_LoadLibray somewhere?

something like:
program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, libfcgi2;
var
    FCGXReq   :FCGX_Request;
    sReply    :WideString;
    libloaded, RequestInitialised         : boolean;
begin
    libLoaded:= FastCGI_LoadLibray('c:\yourpath\libfcgi2.dll') 
    if not LibLoaded
      then writeln('Error loading FastCGI library')
      else RequestInitialised:= (FCGX_InitRequest(FCGXReq, 0, 0) = 0);

    if not RequestInitialised
      then  writeln('Error initialising request')
      else 
    begin      
      while True do  // Main Request processing loop
      begin
        // Execution blocked here until an HTTP request arrives
        if FCGX_Accept_r(FCGXReq) < 0 then break;  // SIGTERM/Error recieved - jump out and cleanup

        // The reply must begin with a valid HTTP header
        sReply := 'Content-Type: text/html' + #13#10 +#13#10;
        sReply := sReply + 'ReqCount=' + IntToStr(FCGXReq.ReqCount);

        // Add the Reply string to the STDOUT buffer
        IF FCGX_PutStr(PWideChar(sReply), Length(sReply), FCGXReq.pOut ) < 0 then break // Error Occured
      end;
    end;
    if LibLoaded then FastCGI_FreeLibray;

end.

Open in new window

0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 38753665
Another thought, since it is a console app, you may want to encase the whole thing in a try loop, e.g.


begin
  try
    ...
  except on e: exception do
    writeln('Error: '+e.Message);
  end;
end.
0
 

Author Comment

by:CursoryGlance
ID: 38754008
Hello PierreC,

Many thanks for your ideas, I have tried your code (had to make one or two small syntactical changes to get it to compile), it seems that the 'libfcgi2.dll' is being loaded successfully but it is still failing when executed within IIS.

Also, I initially used a try-except block in the hope that this would tell me something more, but IIS responds with the usual Error 500. If I run the code from the console the LoadLibrary call works but the next call to FCGX_InitRequest fails, and this is what one would expect running it outside of IIS. The problem is how to debug this application, perhaps I need to attach to the IIS process, only I'm not sure how to do this. I will spend some time now trying to find out how to debug the application from within IIS.

For reference your modified code contribution follows,

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, libfcgi2;
var
    FCGXReq     :FCGX_Request;
    sReply         :WideString;
    libloaded    :Boolean;
    reqInitOK   :Boolean;
begin
    reqInitOK := False;
    libLoaded:= FastCGI_LoadLibray('C:\inetpub\wwwroot\TestFCGI\libfcgi2.dll');
    try
    if not LibLoaded then
        writeln('Error loading FastCGI library')
    else
        reqInitOK:= (FCGX_InitRequest(FCGXReq, 0, 0) = 0);

    if not reqInitOK then
        writeln('Error initialising request')
    else
      begin
          while True do  // Main Request processing loop
          begin
            // Execution blocked here until an HTTP request arrives
            if FCGX_Accept_r(FCGXReq) < 0 then break;  // SIGTERM/Error recieved - jump out and cleanup

            // The reply must begin with a valid HTTP header
            sReply := 'Content-Type: text/html' + #13#10 +#13#10;
            sReply := sReply + 'ReqCount=' + IntToStr(FCGXReq.ReqCount);

            // Add the Reply string to the STDOUT buffer
            IF FCGX_PutStr(PWideChar(sReply), Length(sReply), FCGXReq.pOut ) < 0 then break // Error Occured
          end;
      end;

    except
      on e: exception do
      writeln('Error: '+e.Message);
    end;

    if LibLoaded then FastCGI_FreeLibrary;

end.

Open in new window

0
 
LVL 21

Expert Comment

by:developmentguru
ID: 38754491
One thing a lot of people miss is that you can't cross the streams (sorry, old Ghost Busters references there).  If you try to cross the streams, bad things happen.  What I mean by that is any combination of using 32 bit DLLs with 64 bit DLLs or programs, or vice versa.  Are you sure that all of the pieces of the puzzle are either 64 bit or 32 bit:
1) Your program
2) the DLL
3) IIS
0
 

Author Comment

by:CursoryGlance
ID: 38755517
Hello DevelopmentGuru,

Thank you for your input, I don't think I have any 32/64 bit issues. Although I am using Windows 7 64 bit O/S I have configured the application pool in IIS to accept 32 bit applications. I know this works as I have compiled and tested the PowerBasic listing I provided at the very start of my question. I have also ensured that Delphi is compiling to 32 bit Debug executable's.

I am beginning to think that there must be a problem in the libfcgi2.pas header file, in theory it should just be a straight translation from the PowerBasic file 'FCGX_Header.inc'. What I noticed is that the Delphi header is using dynamic DLL linking via LoadLibrary, whilst the PowerBasic version is using static linking, but other than this difference I can't see any issues.  

Perhaps if any Experts have a few moments spare they could try and follow the FastCGI install as prescribed by the Coast Research & Development web site. The target library 'libfgci2.dll' and usage examples and are made freely available, so maybe somebody can get it work with Delphi?
0
 
LVL 21

Expert Comment

by:developmentguru
ID: 38767131
One thing I would like to know before attempting the install is, "is your Delphi XE3 up to date on it's patches?"  I have Delphi XE3 and I would like to know we are working with the same build.  I got this version info off of the Help | About screen (at the top)

Embarcadero® Delphi® XE3 Version 17.0.4625.53395

Let me know
0
 

Author Comment

by:CursoryGlance
ID: 38775519
Hello DevelopmentGuru,

Sorry for the delay, I had all but given up expecting anyone at EE to provide a solution, so I did not check my email over the weekend.  

I have just checked my copy of Delphi XE3 and it reads exactly the same,  

Embarcadero® Delphi® XE3 Version 17.0.4625.53395

Many thanks for your help.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 21

Expert Comment

by:developmentguru
ID: 38776692
OK.  I should be able to take a look at this Saturday.  Sorry for the wait, My work days are 11.5 hours and up right now...
0
 
LVL 21

Expert Comment

by:developmentguru
ID: 38776693
Feel free to POKE me once in a while too, Friday and Saturday would be best!
0
 

Author Comment

by:CursoryGlance
ID: 38798997
Hello DevelopmentGuru,

Have you had a chance to look at the FastCGI setup for IIS?
0
 
LVL 21

Expert Comment

by:developmentguru
ID: 38799407
I wish you had written me Friday on this although I have slept much of the weekend due to not feeling well, I would have at least taken another look at it.  I work Monday through Thursday this week.  Poke me about it again on Thursday or Friday and I will take another look.
0
 

Author Comment

by:CursoryGlance
ID: 38818383
Hello DevelopmentGuru,

Just a quick reminder about the FastCGI setup for IIS, if you have some time to look at it this weekend I would be most grateful.
0
 
LVL 21

Expert Comment

by:developmentguru
ID: 38821424
Thanks for the reminder.  I will look into it tomorrow.
0
 
LVL 21

Expert Comment

by:developmentguru
ID: 38822100
In order for me to attempt to set up a working example of this it looks like it would take more resources than I have (I have no Windows server 2003 or 2008 licenses to setup a full server).  I have Windows 7 64 bit.  This means that I would need to figure out the appropriate IIS to install, install and configure it, install the fastcgi library, then finally be able  to test.  If you can point me in the right direction for some easy guide on getting all that done... at least I do have a virtual machine I could install it on.  That is important as I do not want IIS installed on my development machine.

In the meantime, I have downloaded the Delphi headers for fast CGI 2 and I will see if I can analyze the code to find the issue.
0
 
LVL 21

Expert Comment

by:developmentguru
ID: 38822139
I started comparing the VB include file (since it works) with the Delphi version.  There were two holes in the Delphi translation that were easily spotted.  I will show the VB include line along with the Delphi version to make them clear.

---The first hole is in the declaration of FCGX_Finish_r---

Public Declare Function FCGX_Finish_r Lib "libFCGI2.dll" (ByRef FCGXReq As FCGX_REQUEST) As Long
FCGX_Finish_r: procedure (var request: FCGX_Request); cdecl;

notice that the Delphi version as no result.  Accept_r has the same parameters and compares differently.

Public Declare Function FCGX_Accept_r Lib "libFCGI2.dll" (ByRef FCGXReq As FCGX_REQUEST) As Long
FCGX_Accept_r: function (var request: FCGX_Request): integer; cdecl;

This tells me that the Delphi declaration for FCGX_Finish_r should have been declared like this:
FCGX_Finish_r: function (var request: FCGX_Request): integer; cdecl;

---The second hole is in the declaration of FCGX_EnsureCap---

Public Declare Function FCGX_EnsureCap Lib "libFCGI2.dll" (ByVal pStream As Long, ByRef Capacity As Long) As Long
FCGX_EnsureCap: function (stream: PFCGX_Stream; nbytes: integer): cardinal; cdecl;

The Delphi result is shown as Cardinal when all others of this type were shown as integer.  This is not an error I would expect to cause an issue as it is the same size result.  Cardinal is 4 bytes as integer is 4 bytes.  The only difference is that Cardinal is unsigned.  This may simply be more correct than the VB version.

The first could, theoretically, cause a stack misalignment.  I would correct that declaration first and see if it helps.  If you want to look into your own code and see if it causes a stack misalignment you would be able to see it by watching the CPU window on the registers pane.  Watch the ESP register (stack pointer).  When your code is on a line to call that method, note the ESP value, then make the call.  If the ESP changes then it has become misaligned.  This can have random effects on your program including memory overwrites which cause incredibly hard to find bugs.

Let me know what you find.
0
 

Author Comment

by:CursoryGlance
ID: 38822707
Hello DevelopmentGuru,

Thanks for double checking the Delphi headers.
 
Firstly, I had already spotted the declaration error for FCGX_Finish_r. If you look in the libfcgi2.pas file that I attached at the very start of this question, you will see that I have remarked out the original declaration and replaced the procedure call with a function call. With respect to the second declaration error in FCGX_EnsureCap I'm not concerned by this as I have not called this function in my code.

With regards to debugging a FastCGI application running inside IIS, I have no idea how to do this using Delphi. I you have any suggestions then I would be only too happy to adopt them.

As it happens I too am using Windows 7 64bit (Home Premium) with IIS 7.5 installed. To setup IIS for FastCGI on this version of Windows is fairly simple. There is a good set of instructions available at http://www.coastrd.com/cgioniis7, just ignore the first part which targets Windows 2008 Server and start at the 'Enabling FastCGI Applications' paragraph. Obviously you must first install IIS by following  "Control Panel|Programs and Features|Turn Windows features on or off"and then enabling "Internet Information Services" and "World Wide Web Services" appropriate nodes in the features tree.
If you find the above is a bit onerous to have to follow and install, then I would be happy to arrange for a remote session on my machine at a time of your convenience.
0
 

Author Comment

by:CursoryGlance
ID: 38822956
Hello DevelopmentGuru,

I have finally solved this problem! The issue is related to Delphi XE3's insistence that all strings are unicode unless explicitly defined otherwise. So for function FCGX_PutStr which is defined in libfcgi.pas as follows

FCGX_PutStr: function (str: pchar; n: integer; stream: PFCGX_Stream): integer; cdecl;

I thought that the first argument would be taking a PChar, but in fact Delphi XE3 interprets PChar as PWideChar instead of PAnsiChar. To test this I compiled the code in earlier versions of Delphi and this has proved to be the case.

Many thanks for all your help. I'm sorry to have troubled you for so long, for what is essentially a rather silly oversight on my part. I am happy to award the question points to you for effort alone if this is allowed?
0
 
LVL 21

Accepted Solution

by:
developmentguru earned 500 total points
ID: 38822957
In Delphi you can do an "Attach To Process" under the Run menu.  You would have to pick IIS to attach to I think.  You could then set a break point in your own code and see if you can get it to stop there.

  Another way I have done it is to write a program to use the DLL deliberately and try to debug it that way.  I am not sure that would work in this case because it requires another process (IIS).

  Let me know if you have any success with this method.  I need to wrestle with the idea of putting IIS on my development box... I hate IIS...
0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 39382222
Did you ever get this to work?
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Prologue It is often required to host multiple websites on a single instance of IIS, mostly in development environments instead of on production servers. I am sure it is not much a preferred solution on production servers but this is at least a pos…
Although it can be difficult to imagine, someday your child will have a career of his or her own. He or she will likely start a family, buy a home and start having their own children. So, while being a kid is still extremely important, it’s also …
Explain concepts important to validation of email addresses with regular expressions. Applies to most languages/tools that uses regular expressions. Consider email address RFCs: Look at HTML5 form input element (with type=email) regex pattern: T…
The viewer will learn how to dynamically set the form action using jQuery.

746 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now