summaryrefslogtreecommitdiff
path: root/packages/fcl-web/src/base/custfcgi.pp
diff options
context:
space:
mode:
Diffstat (limited to 'packages/fcl-web/src/base/custfcgi.pp')
-rw-r--r--packages/fcl-web/src/base/custfcgi.pp330
1 files changed, 221 insertions, 109 deletions
diff --git a/packages/fcl-web/src/base/custfcgi.pp b/packages/fcl-web/src/base/custfcgi.pp
index 65660994fd..4be71d53f1 100644
--- a/packages/fcl-web/src/base/custfcgi.pp
+++ b/packages/fcl-web/src/base/custfcgi.pp
@@ -21,7 +21,13 @@ unit custfcgi;
Interface
uses
- Classes,SysUtils, httpdefs,custweb, custcgi, fastcgi;
+ Classes,SysUtils, httpdefs,
+{$ifdef unix}
+ BaseUnix, TermIO,
+{$else}
+ winsock2,
+{$endif}
+ Sockets, custweb, custcgi, fastcgi;
Type
{ TFCGIRequest }
@@ -29,7 +35,8 @@ Type
TFCGIRequest = Class;
TFCGIResponse = Class;
- TProtocolOption = (poNoPadding,poStripContentLength, poFailonUnknownRecord );
+ TProtocolOption = (poNoPadding,poStripContentLength, poFailonUnknownRecord,
+ poReuseAddress, poUseSelect );
TProtocolOptions = Set of TProtocolOption;
TUnknownRecordEvent = Procedure (ARequest : TFCGIRequest; AFCGIRecord: PFCGI_Header) Of Object;
@@ -60,9 +67,7 @@ Type
TFCGIResponse = Class(TCGIResponse)
private
- FNoPadding: Boolean;
FPO: TProtoColOptions;
- FStripCL: Boolean;
procedure Write_FCGIRecord(ARecord : PFCGI_Header);
Protected
Procedure DoSendHeaders(Headers : TStrings); override;
@@ -75,6 +80,8 @@ Type
Response : TFCgiResponse;
end;
+ { TFCgiHandler }
+
TFCgiHandler = class(TWebHandler)
Private
FOnUnknownRecord: TUnknownRecordEvent;
@@ -84,10 +91,14 @@ Type
FHandle : THandle;
Socket: longint;
FAddress: string;
+ FTimeOut,
FPort: integer;
function Read_FCGIRecord : PFCGI_Header;
+ function DataAvailable : Boolean;
protected
- function WaitForRequest(out ARequest : TRequest; out AResponse : TResponse) : boolean; override;
+ function ProcessRecord(AFCGI_Record: PFCGI_Header; out ARequest: TRequest; out AResponse: TResponse): boolean; virtual;
+ procedure SetupSocket(var IAddress: TInetSockAddr; var AddressLength: tsocklen); virtual;
+ function WaitForRequest(out ARequest : TRequest; out AResponse : TResponse) : boolean; override;
procedure EndRequest(ARequest : TRequest;AResponse : TResponse); override;
Public
constructor Create(AOwner: TComponent); override;
@@ -96,6 +107,7 @@ Type
property Address: string read FAddress write FAddress;
Property ProtocolOptions : TProtoColOptions Read FPO Write FPO;
Property OnUnknownRecord : TUnknownRecordEvent Read FOnUnknownRecord Write FOnUnknownRecord;
+ Property TimeOut : Integer Read FTimeOut Write FTimeOut;
end;
{ TCustomFCgiApplication }
@@ -126,14 +138,16 @@ ResourceString
SListenFailed = 'Failed to listen to port %d. Socket Error: %d';
SErrReadingSocket = 'Failed to read data from socket. Error: %d';
SErrReadingHeader = 'Failed to read FastCGI header. Read only %d bytes';
+ SErrWritingSocket = 'Failed to write data to socket. Error: %d';
Implementation
-uses
{$ifdef CGIDEBUG}
- dbugintf,
+uses
+ dbugintf;
{$endif}
- Sockets;
+
+
{$undef nosignal}
@@ -315,9 +329,13 @@ begin
P:=PByte(Arecord);
Repeat
BytesWritten := sockets.fpsend(TFCGIRequest(Request).Handle, P, BytesToWrite, NoSignalAttr);
+ If (BytesWritten<0) then
+ begin
+ // TODO : Better checking for closed connection, EINTR
+ Raise HTTPError.CreateFmt(SErrWritingSocket,[BytesWritten]);
+ end;
Inc(P,BytesWritten);
Dec(BytesToWrite,BytesWritten);
-// Assert(BytesWritten=BytesToWrite);
until (BytesToWrite=0) or (BytesWritten=0);
end;
@@ -346,15 +364,18 @@ begin
pl := 8-(cl mod 8);
ARespRecord:=nil;
Getmem(ARespRecord,8+cl+pl);
- FillChar(ARespRecord^,8+cl+pl,0);
- ARespRecord^.header.version:=FCGI_VERSION_1;
- ARespRecord^.header.reqtype:=FCGI_STDOUT;
- ARespRecord^.header.paddingLength:=pl;
- ARespRecord^.header.contentLength:=NtoBE(cl);
- ARespRecord^.header.requestId:=NToBE(TFCGIRequest(Request).RequestID);
- move(str[1],ARespRecord^.ContentData,cl);
- Write_FCGIRecord(PFCGI_Header(ARespRecord));
- Freemem(ARespRecord);
+ try
+ FillChar(ARespRecord^,8+cl+pl,0);
+ ARespRecord^.header.version:=FCGI_VERSION_1;
+ ARespRecord^.header.reqtype:=FCGI_STDOUT;
+ ARespRecord^.header.paddingLength:=pl;
+ ARespRecord^.header.contentLength:=NtoBE(cl);
+ ARespRecord^.header.requestId:=NToBE(TFCGIRequest(Request).RequestID);
+ move(str[1],ARespRecord^.ContentData,cl);
+ Write_FCGIRecord(PFCGI_Header(ARespRecord));
+ finally
+ Freemem(ARespRecord);
+ end;
end;
procedure TFCGIResponse.DoSendContent;
@@ -392,14 +413,17 @@ begin
pl := 8-(cl mod 8);
ARespRecord:=Nil;
Getmem(ARespRecord,8+cl+pl);
- ARespRecord^.header.version:=FCGI_VERSION_1;
- ARespRecord^.header.reqtype:=FCGI_STDOUT;
- ARespRecord^.header.paddingLength:=pl;
- ARespRecord^.header.contentLength:=NtoBE(cl);
- ARespRecord^.header.requestId:=NToBE(TFCGIRequest(Request).RequestID);
- move(Str[BS+1],ARespRecord^.ContentData,cl);
- Write_FCGIRecord(PFCGI_Header(ARespRecord));
- Freemem(ARespRecord);
+ try
+ ARespRecord^.header.version:=FCGI_VERSION_1;
+ ARespRecord^.header.reqtype:=FCGI_STDOUT;
+ ARespRecord^.header.paddingLength:=pl;
+ ARespRecord^.header.contentLength:=NtoBE(cl);
+ ARespRecord^.header.requestId:=NToBE(TFCGIRequest(Request).RequestID);
+ move(Str[BS+1],ARespRecord^.ContentData,cl);
+ Write_FCGIRecord(PFCGI_Header(ARespRecord));
+ finally
+ Freemem(ARespRecord);
+ end;
Inc(BS,cl);
Until (BS=L);
FillChar(EndRequest,SizeOf(FCGI_EndRequestRecord),0);
@@ -420,6 +444,7 @@ begin
FRequestsAvail:=5;
SetLength(FRequestsArray,FRequestsAvail);
FHandle := THandle(-1);
+ FTimeOut:=50;
end;
destructor TFCgiHandler.Destroy;
@@ -452,6 +477,30 @@ begin
end;
function TFCgiHandler.Read_FCGIRecord : PFCGI_Header;
+{ $DEFINE DUMPRECORD}
+{$IFDEF DUMPRECORD}
+ Procedure DumpFCGIRecord (Var Header :FCGI_Header; ContentLength : word; PaddingLength : byte; ResRecord : Pointer);
+
+ Var
+ s : string;
+ I : Integer;
+
+ begin
+ Writeln('Dumping record ', Sizeof(Header),',',Contentlength,',',PaddingLength);
+ For I:=0 to Sizeof(Header)+ContentLength+PaddingLength-1 do
+ begin
+ Write(Format('%:3d ',[PByte(ResRecord)[i]]));
+ If PByte(ResRecord)[i]>30 then
+ S:=S+char(PByte(ResRecord)[i]);
+ if (I mod 16) = 0 then
+ begin
+ writeln(' ',S);
+ S:='';
+ end;
+ end;
+ Writeln(' ',S)
+ end;
+{$ENDIF DUMPRECORD}
function ReadBytes(ReadBuf: Pointer; ByteAmount : Word) : Integer;
@@ -477,12 +526,11 @@ function TFCgiHandler.Read_FCGIRecord : PFCGI_Header;
end;
var Header : FCGI_Header;
- {I,}BytesRead : integer;
+ BytesRead : integer;
ContentLength : word;
PaddingLength : byte;
ResRecord : pointer;
ReadBuf : pointer;
- s : string;
begin
@@ -490,119 +538,183 @@ begin
ResRecord:=Nil;
ReadBuf:=@Header;
BytesRead:=ReadBytes(ReadBuf,Sizeof(Header));
- If (BytesRead<>Sizeof(Header)) then
+ If (BytesRead=0) then
+ Exit // Connection closed gracefully.
+ // TODO : if connection closed gracefully, the request should no longer be handled.
+ // Need to discard request/response
+ else If (BytesRead<>Sizeof(Header)) then
Raise HTTPError.CreateFmt(SErrReadingHeader,[BytesRead]);
ContentLength:=BetoN(Header.contentLength);
PaddingLength:=Header.paddingLength;
Getmem(ResRecord,BytesRead+ContentLength+PaddingLength);
- PFCGI_Header(ResRecord)^:=Header;
- ReadBuf:=ResRecord+BytesRead;
- BytesRead:=ReadBytes(ReadBuf,ContentLength);
- ReadBuf:=ReadBuf+BytesRead;
- BytesRead:=ReadBytes(ReadBuf,PaddingLength);
- Result := ResRecord;
-{
- Writeln('Dumping record ', Sizeof(Header),',',Contentlength,',',PaddingLength);
- For I:=0 to Sizeof(Header)+ContentLength+PaddingLength-1 do
+ try
+ PFCGI_Header(ResRecord)^:=Header;
+ ReadBuf:=ResRecord+BytesRead;
+ BytesRead:=ReadBytes(ReadBuf,ContentLength);
+ If (BytesRead=0) and (ContentLength>0) then
+ begin
+ FreeMem(resRecord);
+ Exit // Connection closed gracefully.
+ // TODO : properly handle connection close
+ end;
+ ReadBuf:=ReadBuf+BytesRead;
+ BytesRead:=ReadBytes(ReadBuf,PaddingLength);
+ If (BytesRead=0) and (PaddingLength>0) then
+ begin
+ FreeMem(resRecord);
+ Exit // Connection closed gracefully.
+ // TODO : properly handle connection close
+ end;
+ Result := ResRecord;
+ except
+ FreeMem(resRecord);
+ Raise;
+ end;
+end;
+
+procedure TFCgiHandler.SetupSocket(var IAddress : TInetSockAddr; Var AddressLength : tsocklen);
+
+begin
+ AddressLength:=Sizeof(IAddress);
+ Socket := fpsocket(AF_INET,SOCK_STREAM,0);
+ if Socket=-1 then
+ raise EFPWebError.CreateFmt(SNoSocket,[socketerror]);
+ IAddress.sin_family:=AF_INET;
+ IAddress.sin_port:=htons(Port);
+ if FAddress<>'' then
+ Iaddress.sin_addr := StrToHostAddr(FAddress)
+ else
+ IAddress.sin_addr.s_addr:=0;
+ {$IFDEF Unix}
+ // remedy socket port locking on Posix platforms
+ If (poReuseAddress in ProtocolOptions) then
+ fpSetSockOpt(Socket, SOL_SOCKET, SO_REUSEADDR, @IAddress, SizeOf(IAddress));
+ {$ENDIF}
+ if fpbind(Socket,@IAddress,AddressLength)=-1 then
+ begin
+ CloseSocket(socket);
+ Socket:=0;
+ Terminate;
+ raise Exception.CreateFmt(SBindFailed,[port,socketerror]);
+ end;
+ if fplisten(Socket,1)=-1 then
begin
- Write(Format('%:3d ',[PByte(ResRecord)[i]]));
- If PByte(ResRecord)[i]>30 then
- S:=S+char(PByte(ResRecord)[i]);
- if (I mod 16) = 0 then
- begin
- writeln(' ',S);
- S:='';
- end;
+ CloseSocket(socket);
+ Socket:=0;
+ Terminate;
+ raise Exception.CreateFmt(SListenFailed,[port,socketerror]);
+ end;
+end;
+
+{$ifdef unix}
+function TFCgiHandler.DataAvailable: Boolean;
+
+var
+ FDS: TFDSet;
+ TimeV: TTimeVal;
+
+begin
+ fpFD_Zero(FDS);
+ fpFD_Set(FHandle, FDS);
+ TimeV.tv_usec := (Timeout mod 1000) * 1000;
+ TimeV.tv_sec := Timeout div 1000;
+ Result := fpSelect(FHandle + 1, @FDS, @FDS, @FDS, @TimeV) > 0;
+end;
+{$else}
+function TFCgiHandler.DataAvailable: Boolean;
+
+var
+ FDS: TFDSet;
+ TimeV: TTimeVal;
+
+begin
+ FD_Zero(FDS);
+ FD_Set(FHandle, FDS);
+ TimeV.tv_usec := (Timeout mod 1000) * 1000;
+ TimeV.tv_sec := Timeout div 1000;
+ Result := Select(FHandle + 1, @FDS, @FDS, @FDS, @TimeV) <> 0;
+end;
+{$endif}
+
+function TFCgiHandler.ProcessRecord(AFCGI_Record : PFCGI_Header; out ARequest: TRequest; out AResponse: TResponse): boolean;
+
+var
+ ARequestID : word;
+ ATempRequest : TFCGIRequest;
+begin
+ Result:=False;
+ ARequestID:=BEtoN(AFCGI_Record^.requestID);
+ if AFCGI_Record^.reqtype = FCGI_BEGIN_REQUEST then
+ begin
+ if ARequestID>FRequestsAvail then
+ begin
+ inc(FRequestsAvail,10);
+ SetLength(FRequestsArray,FRequestsAvail);
+ end;
+ assert(not assigned(FRequestsArray[ARequestID].Request));
+ assert(not assigned(FRequestsArray[ARequestID].Response));
+ ATempRequest:=TFCGIRequest.Create;
+ ATempRequest.RequestID:=ARequestID;
+ ATempRequest.Handle:=FHandle;
+ ATempRequest.ProtocolOptions:=Self.Protocoloptions;
+ ATempRequest.OnUnknownRecord:=Self.OnUnknownRecord;
+ FRequestsArray[ARequestID].Request := ATempRequest;
+ end;
+ if (ARequestID>FRequestsAvail) then
+ begin
+ // TODO : ARequestID can be invalid. What to do ?
+ // in each case not try to access the array with requests.
+ end
+ else if FRequestsArray[ARequestID].Request.ProcessFCGIRecord(AFCGI_Record) then
+ begin
+ ARequest:=FRequestsArray[ARequestID].Request;
+ FRequestsArray[ARequestID].Response := TFCGIResponse.Create(ARequest);
+ FRequestsArray[ARequestID].Response.ProtocolOptions:=Self.ProtocolOptions;
+ AResponse:=FRequestsArray[ARequestID].Response;
+ Result := True;
end;
- Writeln(' ',S)
-}
end;
function TFCgiHandler.WaitForRequest(out ARequest: TRequest; out AResponse: TResponse): boolean;
+
var
IAddress : TInetSockAddr;
AddressLength : tsocklen;
- ARequestID : word;
AFCGI_Record : PFCGI_Header;
- ATempRequest : TFCGIRequest;
begin
Result := False;
- AddressLength:=Sizeof(IAddress);
-
if Socket=0 then
- begin
if Port<>0 then
- begin
- Socket := fpsocket(AF_INET,SOCK_STREAM,0);
- if Socket=-1 then
- raise EFPWebError.CreateFmt(SNoSocket,[socketerror]);
- IAddress.sin_family:=AF_INET;
- IAddress.sin_port:=htons(Port);
- if FAddress<>'' then
- Iaddress.sin_addr := StrToHostAddr(FAddress)
- else
- IAddress.sin_addr.s_addr:=0;
- if fpbind(Socket,@IAddress,AddressLength)=-1 then
- begin
- CloseSocket(socket);
- Socket:=0;
- raise Exception.CreateFmt(SBindFailed,[port,socketerror]);
- end;
- if fplisten(Socket,1)=-1 then
- begin
- CloseSocket(socket);
- Socket:=0;
- raise Exception.CreateFmt(SListenFailed,[port,socketerror]);
- end;
- end
+ SetupSocket(IAddress,AddressLength)
else
Socket:=StdInputHandle;
- end;
-
if FHandle=THandle(-1) then
begin
FHandle:=fpaccept(Socket,psockaddr(@IAddress),@AddressLength);
if FHandle=THandle(-1) then
+ begin
+ Terminate;
raise Exception.CreateFmt(SNoInputHandle,[socketerror]);
+ end;
end;
-
repeat
- AFCGI_Record:=Read_FCGIRecord;
- if assigned(AFCGI_Record) then
+ If (poUseSelect in ProtocolOptions) then
+ begin
+ While Not DataAvailable do
+ If (OnIdle<>Nil) then
+ OnIdle(Self);
+ end;
+ AFCGI_Record:=Read_FCGIRecord;
+
+ if assigned(AFCGI_Record) then
try
- ARequestID:=BEtoN(AFCGI_Record^.requestID);
- if AFCGI_Record^.reqtype = FCGI_BEGIN_REQUEST then
- begin
- if ARequestID>FRequestsAvail then
- begin
- inc(FRequestsAvail,10);
- SetLength(FRequestsArray,FRequestsAvail);
- end;
- assert(not assigned(FRequestsArray[ARequestID].Request));
- assert(not assigned(FRequestsArray[ARequestID].Response));
-
- ATempRequest:=TFCGIRequest.Create;
- ATempRequest.RequestID:=ARequestID;
- ATempRequest.Handle:=FHandle;
- ATempRequest.ProtocolOptions:=Self.Protocoloptions;
- ATempRequest.OnUnknownRecord:=Self.OnUnknownRecord;
- FRequestsArray[ARequestID].Request := ATempRequest;
- end;
- if FRequestsArray[ARequestID].Request.ProcessFCGIRecord(AFCGI_Record) then
- begin
- ARequest:=FRequestsArray[ARequestID].Request;
- FRequestsArray[ARequestID].Response := TFCGIResponse.Create(ARequest);
- FRequestsArray[ARequestID].Response.ProtocolOptions:=Self.ProtocolOptions;
- AResponse:=FRequestsArray[ARequestID].Response;
- Result := True;
- Break;
- end;
+ Result:=ProcessRecord(AFCGI_Record,ARequest,AResponse);
Finally
FreeMem(AFCGI_Record);
AFCGI_Record:=Nil;
end;
- until (1<>1);
+ until Result;
end;
{ TCustomFCgiApplication }