{ FastCGI requester support for lNet Copyright (C) 2006-2008 Micha Nelissen This library is Free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is diStributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; withOut even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a Copy of the GNU Library General Public License along with This library; if not, Write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. This license has been modified. See file LICENSE.ADDON for more information. Should you find these sources without a LICENSE File, please contact me at ales@chello.sk } unit lfastcgi; {$mode objfpc}{$h+} interface uses classes, sysutils, fastcgi_base, lnet, levents, lstrbuffer, ltimer; type TLFastCGIClient = class; TLFastCGIRequest = class; TLFastCGIPool = class; TLFastCGIRequestEvent = procedure(ARequest: TLFastCGIRequest) of object; PLFastCGIRequest = ^TLFastCGIRequest; TLFastCGIRequest = class(TObject) protected FID: integer; FClient: TLFastCGIClient; FBuffer: TStringBuffer; FBufferSendPos: integer; FHeader: FCGI_Header; FHeaderPos: integer; FContentLength: integer; FInputBuffer: pchar; FInputSize: integer; FOutputDone: boolean; FStderrDone: boolean; FOutputPending: boolean; FNextFree: TLFastCGIRequest; FNextSend: TLFastCGIRequest; FOnEndRequest: TLFastCGIRequestEvent; FOnInput: TLFastCGIRequestEvent; FOnOutput: TLFastCGIRequestEvent; FOnStderr: TLFastCGIRequestEvent; procedure HandleReceive; procedure HandleReceiveEnd; function HandleSend: boolean; procedure DoEndRequest; procedure DoOutput; procedure DoStderr; procedure EndRequest; procedure RewindBuffer; procedure SetContentLength(NewLength: integer); procedure SendEmptyRec(AType: integer); procedure SendGetValues; procedure SetID(const NewID: integer); public constructor Create; destructor Destroy; override; procedure AbortRequest; function Get(ABuffer: pchar; ASize: integer): integer; procedure ParseClientBuffer; function SendBuffer: integer; function SendPrivateBuffer: boolean; procedure SendBeginRequest(AType: integer); procedure SendParam(const AName, AValue: string; AReqType: integer = FCGI_PARAMS); function SendInput(const ABuffer: pchar; ASize: integer): integer; procedure DoneParams; procedure DoneInput; property ID: integer read FID write SetID; property StderrDone: boolean read FStderrDone; property OutputDone: boolean read FOutputDone; property OutputPending: boolean read FOutputPending; property OnEndRequest: TLFastCGIRequestEvent read FOnEndRequest write FOnEndRequest; property OnInput: TLFastCGIRequestEvent read FOnInput write FOnInput; property OnOutput: TLFastCGIRequestEvent read FOnOutput write FOnOutput; property OnStderr: TLFastCGIRequestEvent read FOnStderr write FOnStderr; end; TFastCGIClientState = (fsIdle, fsConnecting, fsConnectingAgain, fsStartingServer, fsHeader, fsData, fsFlush); PLFastCGIClient = ^TLFastCGIClient; TLFastCGIClient = class(TLTcp) protected FRequests: PLFastCGIRequest; FRequestsCount: integer; FNextRequestID: integer; FRequestsSent: integer; FFreeRequest: TLFastCGIRequest; FSendRequest: TLFastCGIRequest; FRequest: TLFastCGIRequest; FState: TFastCGIClientState; FNextFree: TLFastCGIClient; FPool: TLFastCGIPool; FBuffer: pchar; FBufferEnd: pchar; FBufferPos: pchar; FBufferSize: dword; FReqType: byte; FContentLength: integer; FPaddingLength: integer; function Connect: Boolean; override; procedure ConnectEvent(ASocket: TLHandle); override; procedure DisconnectEvent(ASocket: TLHandle); override; procedure ErrorEvent(ASocket: TLHandle; const msg: string); override; function CreateRequester: TLFastCGIRequest; procedure HandleGetValuesResult; procedure HandleReceive(ASocket: TLSocket); procedure HandleSend(ASocket: TLSocket); procedure ParseBuffer; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure AddToSendQueue(ARequest: TLFastCGIRequest); function BeginRequest(AType: integer): TLFastCGIRequest; procedure EndRequest(ARequest: TLFastCGIRequest); procedure Flush; function GetBuffer(ABuffer: pchar; ASize: integer): integer; property ReqType: byte read FReqType; property RequestsSent: integer read FRequestsSent; end; TSpawnState = (ssNone, ssSpawning, ssSpawned); TLFastCGIPool = class(TObject) protected FClients: PLFastCGIClient; FClientsCount: integer; FClientsAvail: integer; FClientsMax: integer; FMaxRequestsConn: integer; FFreeClient: TLFastCGIClient; FTimer: TLTimer; FEventer: TLEventer; FAppName: string; FAppEnv: string; FHost: string; FPort: integer; FSpawnState: TSpawnState; procedure AddToFreeClients(AClient: TLFastCGIClient); function CreateClient: TLFastCGIClient; procedure ConnectClients(Sender: TObject); procedure StartServer; public constructor Create; destructor Destroy; override; function BeginRequest(AType: integer): TLFastCGIRequest; procedure EndRequest(AClient: TLFastCGIClient); property AppEnv: string read FAppEnv write FAppEnv; property AppName: string read FAppName write FAppName; property ClientsMax: integer read FClientsMax write FClientsMax; property Eventer: TLEventer read FEventer write FEventer; property MaxRequestsConn: integer read FMaxRequestsConn write FMaxRequestsConn; property Host: string read FHost write FHost; property Port: integer read FPort write FPort; property Timer: TLTimer read FTimer; end; implementation uses lSpawnFCGI; { TLFastCGIRequest } constructor TLFastCGIRequest.Create; begin inherited; FBuffer := InitStringBuffer(504); FHeader.Version := FCGI_VERSION_1; FHeaderPos := -1; end; destructor TLFastCGIRequest.Destroy; begin inherited; FreeMem(FBuffer.Memory); end; procedure TLFastCGIRequest.HandleReceive; begin case FClient.ReqType of FCGI_STDOUT: DoOutput; FCGI_STDERR: DoStderr; FCGI_END_REQUEST: EndRequest; FCGI_GET_VALUES_RESULT: FClient.HandleGetValuesResult; else FClient.Flush; end; end; procedure TLFastCGIRequest.HandleReceiveEnd; begin case FClient.ReqType of FCGI_STDOUT: FOutputDone := true; FCGI_STDERR: FStderrDone := true; end; end; function TLFastCGIRequest.HandleSend: boolean; begin if FOnInput <> nil then FOnInput(Self); Result := FInputBuffer = nil; end; procedure TLFastCGIRequest.DoOutput; begin if FOnOutput <> nil then FOnOutput(Self); end; procedure TLFastCGIRequest.DoStderr; begin if FOnStderr <> nil then FOnStderr(Self); end; procedure TLFastCGIRequest.DoEndRequest; begin if FOnEndRequest <> nil then FOnEndRequest(Self); end; procedure TLFastCGIRequest.EndRequest; begin FOutputDone := false; FStderrDone := false; FClient.EndRequest(Self); FClient.Flush; RewindBuffer; DoEndRequest; end; function TLFastCGIRequest.Get(ABuffer: pchar; ASize: integer): integer; begin Result := FClient.GetBuffer(ABuffer, ASize); end; procedure TLFastCGIRequest.ParseClientBuffer; begin FOutputPending := false; if (FClient.Iterator <> nil) and FClient.Iterator.IgnoreRead then FClient.HandleReceive(nil) else FClient.ParseBuffer; end; procedure TLFastCGIRequest.SetID(const NewID: integer); begin FID := NewID; FHeader.RequestIDB0 := byte(NewID and $FF); FHeader.RequestIDB1 := byte((NewID shr 8) and $FF); end; procedure TLFastCGIRequest.SetContentLength(NewLength: integer); begin FContentLength := NewLength; FHeader.ContentLengthB0 := byte(NewLength and $FF); FHeader.ContentLengthB1 := byte((NewLength shr 8) and $FF); FHeader.PaddingLength := byte(7-((NewLength+7) and 7)); end; const PaddingBuffer: array[0..7] of char = (#0, #0, #0, #0, #0, #0, #0, #0); type TLFastCGIStringSize = record Size: integer; SizeBuf: array[0..3] of char; end; function GetFastCGIStringSize(ABufferPos: pbyte; var ASize: integer): integer; begin ASize := ABufferPos[0]; if ASize >= 128 then begin ASize := ((ABufferPos[0] shl 24) and $7f) or (ABufferPos[1] shl 16) or (ABufferPos[2] shl 8) or ABufferPos[3]; Result := 4; end else Result := 1; end; procedure FillFastCGIStringSize(const AStr: string; var AFastCGIStr: TLFastCGIStringSize); var lLen: dword; begin lLen := dword(Length(AStr)); if lLen > 127 then begin AFastCGIStr.Size := 4; AFastCGIStr.SizeBuf[0] := char($80 + ((lLen shr 24) and $ff)); AFastCGIStr.SizeBuf[1] := char((lLen shr 16) and $ff); AFastCGIStr.SizeBuf[2] := char((lLen shr 8) and $ff); AFastCGIStr.SizeBuf[3] := char(lLen and $ff); end else begin AFastCGIStr.Size := 1; AFastCGIStr.SizeBuf[0] := char(lLen); end; end; procedure TLFastCGIRequest.SendBeginRequest(AType: integer); var lBody: FCGI_BeginRequestBody; begin lBody.roleB1 := byte((AType shr 8) and $ff); lBody.roleB0 := byte(AType and $ff); lBody.flags := FCGI_KEEP_CONN; FHeader.ReqType := FCGI_BEGIN_REQUEST; SetContentLength(sizeof(lBody)); AppendString(FBuffer, @FHeader, sizeof(FHeader)); AppendString(FBuffer, @lBody, sizeof(lBody)); end; procedure TLFastCGIRequest.SendParam(const AName, AValue: string; AReqType: integer = FCGI_PARAMS); var lNameLen: TLFastCGIStringSize; lValueLen: TLFastCGIStringSize; lTotalLen: integer; begin FillFastCGIStringSize(AName, lNameLen); FillFastCGIStringSize(AValue, lValueLen); lTotalLen := lNameLen.Size+lValueLen.Size+Length(AName)+Length(AValue); if (FHeader.ReqType = AReqType) and (FBufferSendPos = 0) and (0 <= FHeaderPos) and (FHeaderPos < FBuffer.Pos - FBuffer.Memory) then begin { undo padding } Dec(FBuffer.Pos, FHeader.PaddingLength); SetContentLength(FContentLength+lTotalLen); Move(FHeader, FBuffer.Memory[FHeaderPos], sizeof(FHeader)); end else begin FHeader.ReqType := AReqType; SetContentLength(lTotalLen); FHeaderPos := FBuffer.Pos - FBuffer.Memory; AppendString(FBuffer, @FHeader, sizeof(FHeader)); end; AppendString(FBuffer, @lNameLen.SizeBuf[0], lNameLen.Size); AppendString(FBuffer, @lValueLen.SizeBuf[0], lValueLen.Size); AppendString(FBuffer, AName); AppendString(FBuffer, AValue); AppendString(FBuffer, @PaddingBuffer[0], FHeader.PaddingLength); end; procedure TLFastCGIRequest.SendGetValues; var lRequestID: integer; begin { management record type has request id 0 } lRequestID := ID; ID := 0; SendParam('FCGI_MAX_REQS', '', FCGI_GET_VALUES); { if we're the first connection, ask max. # connections } if FClient.FPool.FClientsAvail = 1 then SendParam('FCGI_MAX_CONNS', '', FCGI_GET_VALUES); ID := lRequestID; end; function TLFastCGIRequest.SendInput(const ABuffer: pchar; ASize: integer): integer; begin { first send current buffer if any } if FInputBuffer <> nil then begin Result := SendBuffer; if FInputBuffer <> nil then exit; end else Result := 0; if Result >= ASize then exit; if FInputBuffer = nil then begin FInputBuffer := ABuffer+Result; FInputSize := ASize-Result; FHeader.ReqType := FCGI_STDIN; SetContentLength(FInputSize); AppendString(FBuffer, @FHeader, sizeof(FHeader)); end; Inc(Result, SendBuffer); end; procedure TLFastCGIRequest.RewindBuffer; begin FBufferSendPos := 0; FHeaderPos := -1; { rewind stringbuffer } FBuffer.Pos := FBuffer.Memory; end; function TLFastCGIRequest.SendPrivateBuffer: boolean; var lWritten: integer; begin { nothing to send ? } if FBuffer.Pos-FBuffer.Memory = FBufferSendPos then exit(true); { already a queue and we are not first in line ? no use in trying to send then } if (FClient.FSendRequest = nil) or (FClient.FSendRequest = Self) then begin lWritten := FClient.Send(FBuffer.Memory[FBufferSendPos], FBuffer.Pos-FBuffer.Memory-FBufferSendPos); Inc(FBufferSendPos, lWritten); Result := FBufferSendPos = FBuffer.Pos-FBuffer.Memory; { do not rewind buffer, unless remote side has had chance to disconnect } if Result then RewindBuffer; end else Result := false; if not Result then FClient.AddToSendQueue(Self); end; function TLFastCGIRequest.SendBuffer: integer; var lWritten: integer; begin { already a queue and we are not first in line ? no use in trying to send then } if (FClient.FSendRequest <> nil) and (FClient.FSendRequest <> Self) then exit(0); { header to be sent? } if not SendPrivateBuffer then exit(0); { first write request header, then wait for possible disconnect } if FBufferSendPos > 0 then exit(0); if FInputBuffer = nil then exit(0); lWritten := FClient.Send(FInputBuffer^, FInputSize); Inc(FInputBuffer, lWritten); Dec(FInputSize, lWritten); if FInputSize = 0 then begin FInputBuffer := nil; AppendString(FBuffer, @PaddingBuffer[0], FHeader.PaddingLength); end else FClient.AddToSendQueue(Self); Result := lWritten; end; procedure TLFastCGIRequest.SendEmptyRec(AType: integer); begin FHeader.ReqType := AType; SetContentLength(0); AppendString(FBuffer, @FHeader, sizeof(FHeader)); { no padding needed for empty string } end; procedure TLFastCGIRequest.DoneParams; begin SendEmptyRec(FCGI_PARAMS); end; procedure TLFastCGIRequest.DoneInput; begin SendEmptyRec(FCGI_STDIN); SendPrivateBuffer; end; procedure TLFastCGIRequest.AbortRequest; begin FHeader.ReqType := FCGI_ABORT_REQUEST; SetContentLength(0); AppendString(FBuffer, @FHeader, sizeof(FHeader)); SendPrivateBuffer; end; { TLFastCGIClient } const DataBufferSize = 64*1024-1; constructor TLFastCGIClient.Create(AOwner: TComponent); begin inherited; FBuffer := GetMem(DataBufferSize+1); FBufferPos := FBuffer; FBufferEnd := FBuffer; FRequests := AllocMem(sizeof(TLFastCGIRequest)); FRequestsCount := 1; FFreeRequest := nil; OnReceive := @HandleReceive; OnCanSend := @HandleSend; end; destructor TLFastCGIClient.Destroy; var I: integer; begin for I := 0 to FNextRequestID-1 do FRequests[I].Free; FreeMem(FRequests); FreeMem(FBuffer); inherited; end; function TLFastCGIClient.GetBuffer(ABuffer: pchar; ASize: integer): integer; begin Result := FBufferEnd - FBufferPos; if Result > FContentLength then Result := FContentLength; if Result > ASize then Result := ASize; Move(FBufferPos^, ABuffer^, Result); Inc(FBufferPos, Result); Dec(FContentLength, Result); { buffer empty? reset } if FBufferPos = FBufferEnd then begin FBufferPos := FBuffer; FBufferEnd := FBuffer; end; end; procedure TLFastCGIClient.ConnectEvent(ASocket: TLHandle); begin if FState = fsStartingServer then FPool.FSpawnState := ssSpawned; FState := fsHeader; if FPool <> nil then FPool.AddToFreeClients(Self); inherited; end; procedure TLFastCGIClient.DisconnectEvent(ASocket: TLHandle); var I: integer; needReconnect: boolean; begin inherited; FRequestsSent := 0; needReconnect := false; for I := 0 to FNextRequestID-1 do if FRequests[I].FNextFree = nil then begin { see if buffer contains request, then assume we can resend that } if FRequests[I].FBufferSendPos > 0 then begin needReconnect := true; FRequests[I].FBufferSendPos := 0; FRequests[I].SendPrivateBuffer; end else if FRequests[I].FBuffer.Memory = FRequests[I].FBuffer.Pos then needReconnect := true else FRequests[I].EndRequest; end; if needReconnect then Connect; end; procedure TLFastCGIClient.ErrorEvent(ASocket: TLHandle; const msg: string); begin if (FState = fsConnectingAgain) or ((FState = fsConnecting) and (FPool.FSpawnState = ssSpawned)) then begin FRequest.DoEndRequest; EndRequest(FRequest); FState := fsIdle; end else if FState = fsConnecting then begin FPool.StartServer; FState := fsStartingServer; end; end; procedure TLFastCGIClient.HandleGetValuesResult; var lNameLen, lValueLen, lIntVal, lCode: integer; lBufferPtr: pchar; lPrevChar: char; procedure GetIntVal; begin lPrevChar := lBufferPtr[lNameLen+lValueLen]; lBufferPtr[lNameLen+lValueLen] := #0; Val(lBufferPtr+lNameLen, lIntVal, lCode); lBufferPtr[lNameLen+lValueLen] := lPrevChar; end; begin repeat lBufferPtr := FBufferPos; Inc(lBufferPtr, GetFastCGIStringSize(PByte(lBufferPtr), lNameLen)); Inc(lBufferPtr, GetFastCGIStringSize(PByte(lBufferPtr), lValueLen)); if lBufferPtr + lNameLen + lValueLen > FBufferEnd then exit; if StrLComp(lBufferPtr, 'FCGI_MAX_REQS', lNameLen) = 0 then begin GetIntVal; if (lCode = 0) and (FRequestsCount <> lIntVal) then begin FRequestsCount := lIntVal; ReallocMem(FRequests, sizeof(TLFastCGIRequest)*lIntVal); end; end else if StrLComp(lBufferPtr, 'FCGI_MAX_CONNS', lNameLen) = 0 then begin GetIntVal; if lCode = 0 then FPool.ClientsMax := lIntVal; end; Inc(lBufferPtr, lNameLen+lValueLen); Dec(FContentLength, lBufferPtr-FBufferPos); FBufferPos := lBufferPtr; until FContentLength = 0; end; procedure TLFastCGIClient.HandleReceive(ASocket: TLSocket); var lRead: integer; begin lRead := Get(FBufferEnd^, DataBufferSize-PtrUInt(FBufferEnd-FBuffer)); if lRead = 0 then exit; { remote side has had chance to disconnect, clear buffer } Inc(FBufferEnd, lRead); ParseBuffer; end; procedure TLFastCGIClient.HandleSend(ASocket: TLSocket); var lRequest: TLFastCGIRequest; begin if FSendRequest = nil then exit; lRequest := FSendRequest.FNextSend; repeat if not lRequest.SendPrivateBuffer or not lRequest.HandleSend then exit; lRequest.FNextSend := nil; { only this one left in list ? } if FSendRequest = lRequest then begin FSendRequest := nil; exit; end else begin lRequest := lRequest.FNextSend; FSendRequest.FNextSend := lRequest; end; until false; end; procedure TLFastCGIClient.AddToSendQueue(ARequest: TLFastCGIRequest); begin if ARequest.FNextSend <> nil then exit; if FSendRequest = nil then FSendRequest := ARequest else ARequest.FNextSend := FSendRequest.FNextSend; FSendRequest.FNextSend := ARequest; end; procedure TLFastCGIClient.ParseBuffer; var lHeader: PFCGI_Header; lReqIndex: integer; begin repeat case FState of fsHeader: begin if FBufferEnd-FBufferPos < sizeof(FCGI_Header) then exit; lHeader := PFCGI_Header(FBufferPos); FReqType := lHeader^.ReqType; lReqIndex := (lHeader^.RequestIDB1 shl 8) or lHeader^.RequestIDB0; FContentLength := (lHeader^.ContentLengthB1 shl 8) or lHeader^.ContentLengthB0; FPaddingLength := lHeader^.PaddingLength; Inc(FBufferPos, sizeof(lHeader^)); if lReqIndex > 0 then Dec(lReqIndex); if (lReqIndex < FRequestsCount) and (FRequests[lReqIndex] <> nil) then begin FRequest := FRequests[lReqIndex]; if FContentLength > 0 then FState := fsData else begin FRequest.HandleReceiveEnd; Flush; end; end else Flush; end; fsData: begin FRequest.HandleReceive; if FContentLength = 0 then Flush else begin FRequest.FOutputPending := true; exit; end; end; fsFlush: Flush; end; until FBufferPos = FBufferEnd; end; procedure TLFastCGIClient.Flush; function FlushSize(var ANumBytes: integer): boolean; var lFlushBytes: integer; begin lFlushBytes := ANumBytes; if lFlushBytes > FBufferEnd - FBufferPos then lFlushBytes := FBufferEnd - FBufferPos; Dec(ANumBytes, lFlushBytes); Inc(FBufferPos, lFlushBytes); Result := ANumBytes = 0; end; begin FState := fsFlush; if FlushSize(FContentLength) and FlushSize(FPaddingLength) then begin { buffer empty? reset } if FBufferPos = FBufferEnd then begin FBufferPos := FBuffer; FBufferEnd := FBuffer; end; FState := fsHeader; FRequest := nil; end; end; function TLFastCGIClient.CreateRequester: TLFastCGIRequest; begin if FRequests[FNextRequestID] = nil then FRequests[FNextRequestID] := TLFastCGIRequest.Create; Result := FRequests[FNextRequestID]; Inc(FNextRequestID); Result.FClient := Self; Result.ID := FNextRequestID; { request ids start at 1 } end; function TLFastCGIClient.Connect: Boolean; begin Result := inherited Connect(FPool.Host, FPool.Port); FRequest := FRequests[0]; if FRequest.FBuffer.Pos = FRequest.FBuffer.Memory then FRequest.SendGetValues; if FState <> fsStartingServer then FState := fsConnecting else FState := fsConnectingAgain; end; function TLFastCGIClient.BeginRequest(AType: integer): TLFastCGIRequest; begin if FFreeRequest <> nil then begin Result := FFreeRequest.FNextFree; if FFreeRequest = FFreeRequest.FNextFree then FFreeRequest := nil else FFreeRequest.FNextFree := FFreeRequest.FNextFree.FNextFree; Result.FNextFree := nil; end else if FNextRequestID = FRequestsCount then exit(nil) else begin Result := CreateRequester; end; if not Connected then Connect; Result.SendBeginRequest(AType); Inc(FRequestsSent); end; procedure TLFastCGIClient.EndRequest(ARequest: TLFastCGIRequest); begin if FFreeRequest <> nil then ARequest.FNextFree := FFreeRequest.FNextFree else FFreeRequest := ARequest; FFreeRequest.FNextFree := ARequest; if FPool <> nil then FPool.EndRequest(Self); end; { TLFastCGIPool } constructor TLFastCGIPool.Create; begin FClientsMax := 1; FMaxRequestsConn := 1; inherited; end; destructor TLFastCGIPool.Destroy; var I: integer; begin for I := 0 to FClientsAvail-1 do FClients[I].Free; FreeMem(FClients); if FTimer <> nil then FTimer.Free; inherited; end; function TLFastCGIPool.CreateClient: TLFastCGIClient; begin if FClientsAvail = FClientsCount then begin Inc(FClientsCount, 64); ReallocMem(FClients, FClientsCount*sizeof(TLFastCGIRequest)); end; Result := TLFastCGIClient.Create(nil); Result.FPool := Self; Result.Eventer := FEventer; FClients[FClientsAvail] := Result; Inc(FClientsAvail); end; function TLFastCGIPool.BeginRequest(AType: integer): TLFastCGIRequest; var lTempClient: TLFastCGIClient; begin Result := nil; while FFreeClient <> nil do begin lTempClient := FFreeClient.FNextFree; Result := lTempClient.BeginRequest(AType); if Result <> nil then break; { Result = nil -> no free requesters on next free client } if lTempClient = FFreeClient then FFreeClient := nil else FFreeClient.FNextFree := lTempClient.FNextFree; lTempClient.FNextFree := nil; end; { all clients busy } if Result = nil then if FClientsAvail < FClientsMax then Result := CreateClient.BeginRequest(AType); end; procedure TLFastCGIPool.EndRequest(AClient: TLFastCGIClient); begin { TODO: wait for other requests to be completed } if AClient.RequestsSent = FMaxRequestsConn then AClient.Disconnect; AddToFreeClients(AClient); end; procedure TLFastCGIPool.AddToFreeClients(AClient: TLFastCGIClient); begin if AClient.FNextFree <> nil then exit; if FFreeClient = nil then FFreeClient := AClient else AClient.FNextFree := FFreeClient.FNextFree; FFreeClient.FNextFree := AClient; end; procedure TLFastCGIPool.ConnectClients(Sender: TObject); var I: integer; begin for I := 0 to FClientsAvail-1 do if FClients[I].FState = fsStartingServer then FClients[I].Connect; end; procedure TLFastCGIPool.StartServer; begin if FSpawnState = ssNone then begin FSpawnState := ssSpawning; SpawnFCGIProcess(FAppName, FAppEnv, FPort); if FTimer = nil then FTimer := TLTimer.Create; FTimer.OneShot := true; FTimer.OnTimer := @ConnectClients; end; FTimer.Interval := 2000; end; end.