Browse Source

Thread pool start

Michaël Van Canneyt 4 years ago
parent
commit
5bf9de22d6
1 changed files with 117 additions and 37 deletions
  1. 117 37
      packages/fcl-web/src/base/fphttpserver.pp

+ 117 - 37
packages/fcl-web/src/base/fphttpserver.pp

@@ -25,6 +25,7 @@ uses
 
 Const
   ReadBufLen = 4096;
+  DefaultKeepaliveTimeout = 50; // Ms
 
 Type
   TFPHTTPConnection = Class;
@@ -99,8 +100,9 @@ Type
   TFPHTTPConnectionThread = Class(TThread)
   private
     FConnection: TFPHTTPConnection;
+    FOnDone : TNotifyEvent;
   Public
-    Constructor CreateConnection(AConnection : TFPHTTPConnection; aOnTerminate : TNotifyEvent); virtual;
+    Constructor CreateConnection(AConnection : TFPHTTPConnection; aOnConnectionDone : TNotifyEvent); virtual;
     Procedure Execute; override;
     Property Connection : TFPHTTPConnection Read FConnection;
   end;
@@ -160,6 +162,7 @@ Type
   Protected
     Procedure RemoveConnection(aConnection :TFPHTTPConnection); override;
   Public
+    procedure CheckRequests; override;
     Procedure HandleConnection(aConnection : TFPHTTPConnection); override;
     Function GetActiveConnectionCount : Integer; override;
     Procedure CloseSockets; override;
@@ -170,8 +173,9 @@ Type
 
   TFPThreadedConnectionHandler = Class(TFPHTTPServerConnectionListHandler)
   private
-    procedure ThreadDone(Sender: TObject);
+    procedure ConnectionDone(Sender: TObject);
   Public
+    procedure CheckRequests; override;
     Procedure HandleConnection(aConnection : TFPHTTPConnection); override;
   end;
 
@@ -180,7 +184,22 @@ Type
   TFPPooledConnectionHandler = Class(TFPHTTPServerConnectionListHandler)
   Private
     FPool : TFPCustomSimpleThreadPool;
+  Protected
+    Type
+
+       { THandleRequestTask }
+
+       THandleRequestTask = Class(TThreadPoolTask)
+         Constructor Create(aConnection : TFPHTTPConnection);
+       private
+         FConnection: TFPHTTPConnection;
+       Protected
+         procedure DoExecute; override;
+       Public
+         Property Connection : TFPHTTPConnection Read FConnection;
+       end;
   Public
+    procedure CheckRequests; override;
     Constructor Create(aServer : TFPCustomHttpServer); override;
     Procedure HandleConnection(aConnection : TFPHTTPConnection); override;
     function CreatePool : TFPCustomSimpleThreadPool;
@@ -200,6 +219,7 @@ Type
     FAdminName: string;
     FAfterSocketHandlerCreated: TSocketHandlerCreatedEvent;
     FCertificateData: TCertificateData;
+    FEnableKeepAlive: Boolean;
     FOnAcceptIdle: TNotifyEvent;
     FOnAllowConnect: TConnectQuery;
     FOnGetSocketHandler: TGetSocketHandlerEvent;
@@ -232,6 +252,7 @@ Type
     procedure SetThreaded(const AValue: Boolean);
     procedure SetThreadMode(AValue: TThreadMode);
     procedure SetupSocket;
+    procedure SetupConnectionHandler;
   Protected
     Class procedure HandleUnexpectedError(E : Exception); virtual;
     // Override this to create descendent
@@ -247,6 +268,8 @@ Type
     Procedure InitResponse(AResponse : TFPHTTPConnectionResponse); virtual;
     // Called on accept errors
     procedure DoAcceptError(Sender: TObject; ASocket: Longint; E: Exception;  var ErrorAction: TAcceptErrorAction);
+    // Called when accept is idle. Will check for new requests.
+    procedure DoAcceptIdle(Sender: TObject);
     // Create a connection handling object.
     function CreateConnection(Data : TSocketStream) : TFPHTTPConnection; virtual;
     // Create a connection handler object depending on threadmode
@@ -268,7 +291,9 @@ Type
                             Var AResponse : TFPHTTPConnectionResponse); virtual;
     // Called when a connection encounters an unexpected error. Will call OnRequestError when set.
     procedure HandleRequestError(Sender: TObject; E: Exception); virtual;
-    // Connection count
+    // Connection Handler
+    Property Connectionhandler : TFPHTTPServerConnectionHandler Read FConnectionHandler;
+    // Connection count. Convenience shortcut for Connectionhandler.GetActiveConnectionCount;
     Property ConnectionCount : Integer Read GetConnectionCount;
   public
     Constructor Create(AOwner : TComponent); override;
@@ -280,6 +305,8 @@ Type
     Property Address : string Read FAddress Write SetAddress;
     // Port to listen on.
     Property Port : Word Read FPort Write SetPort Default 80;
+    // Set to true if you want to support HTTP 1.1 connection: keep-alive - only available for threaded server
+    Property EnableKeepAlive: Boolean read FEnableKeepAlive write FEnableKeepAlive;
     // Max connections on queue (for Listen call)
     Property QueueSize : Word Read FQueueSize Write SetQueueSize Default 5;
     // Called when deciding whether to accept a connection.
@@ -365,8 +392,25 @@ begin
   end;
 end;
 
+{ TFPPooledConnectionHandler.THandleRequestTask }
+
+constructor TFPPooledConnectionHandler.THandleRequestTask.Create(aConnection: TFPHTTPConnection);
+begin
+  FConnection:=aConnection;
+end;
+
+procedure TFPPooledConnectionHandler.THandleRequestTask.DoExecute;
+begin
+  Connection.HandleRequest;
+end;
+
 { TFPPooledConnectionHandler }
 
+procedure TFPPooledConnectionHandler.CheckRequests;
+begin
+
+end;
+
 constructor TFPPooledConnectionHandler.Create(aServer: TFPCustomHttpServer);
 begin
   inherited Create(aServer);
@@ -375,7 +419,7 @@ end;
 
 procedure TFPPooledConnectionHandler.HandleConnection(aConnection: TFPHTTPConnection);
 begin
-
+  FPool.AddTask(THandleRequestTask.Create(aConnection));
 end;
 
 function TFPPooledConnectionHandler.CreatePool: TFPCustomSimpleThreadPool;
@@ -393,6 +437,7 @@ end;
 procedure TFPHTTPServerConnectionListHandler.RemoveConnection(aConnection: TFPHTTPConnection);
 begin
   Flist.Remove(aConnection);
+  aConnection.Free;
 end;
 
 constructor TFPHTTPServerConnectionListHandler.Create(aServer: TFPCustomHTTPServer);
@@ -426,15 +471,20 @@ end;
 
 { TFPThreadedConnectionHandler }
 
-procedure TFPThreadedConnectionHandler.ThreadDone(Sender: TObject);
+procedure TFPThreadedConnectionHandler.ConnectionDone(Sender: TObject);
 begin
-  RemoveConnection(TFPHTTPConnectionThread(Sender).Connection)
+  RemoveConnection(Sender as TFPHTTPConnection)
+end;
+
+procedure TFPThreadedConnectionHandler.CheckRequests;
+begin
+  // Do nothing
 end;
 
 procedure TFPThreadedConnectionHandler.HandleConnection(aConnection: TFPHTTPConnection);
 begin
   Inherited; // Adds to list
-  TFPHTTPConnectionThread.CreateConnection(aConnection,@ThreadDone);
+  TFPHTTPConnectionThread.CreateConnection(aConnection,@ConnectionDone);
 end;
 
 
@@ -449,6 +499,12 @@ procedure TFPSimpleConnectionHandler.RemoveConnection(aConnection: TFPHTTPConnec
 begin
   if aConnection=FConnection then
     FConnection:=Nil;
+  aConnection.Free;
+end;
+
+procedure TFPSimpleConnectionHandler.CheckRequests;
+begin
+  // Do nothing
 end;
 
 procedure TFPSimpleConnectionHandler.HandleConnection(aConnection: TFPHTTPConnection);
@@ -785,6 +841,7 @@ begin
   FSocket:=ASocket;
   FSetupSocket:=True;
   FServer:=AServer;
+  KeepAliveTimeout:=DefaultKeepaliveTimeout;
 end;
 
 destructor TFPHTTPConnection.Destroy;
@@ -816,6 +873,7 @@ begin
       FSetupSocket:=False;
       end;
     // Read headers.
+    Resp:=Nil;
     Req:=ReadRequestHeaders;
     try
       //set port
@@ -835,23 +893,18 @@ begin
         end;
       // Create Response
       Resp:= Server.CreateResponse(Req);
-      try
-        Server.InitResponse(Resp);
-        Resp.FConnection:=Self;
-        // And dispatch
-        if Server.Active then
-          Server.HandleRequest(Req,Resp);
-        if Assigned(Resp) and (not Resp.ContentSent) then
-          begin
-          // Add connection header for HTTP 1.0 keep-alive
-          if FKeepAlive and (Req.HttpVersion='1.0') and not Resp.HeaderIsSet(hhConnection) then
-            Resp.SetHeader(hhConnection,'keep-alive');
-          Resp.SendContent;
-          end;
-      finally
-        FreeAndNil(Resp);
-      end;
+      Server.InitResponse(Resp);
+      // We set the header here now. User can override it when needed.
+      if FKeepAlive and (Req.HttpVersion='1.0') and not Resp.HeaderIsSet(hhConnection) then
+        Resp.SetHeader(hhConnection,'keep-alive');
+      Resp.FConnection:=Self;
+      // And dispatch
+      if Server.Active then
+        Server.HandleRequest(Req,Resp);
+      if Assigned(Resp) and (not Resp.ContentSent) then
+        Resp.SendContent;
     Finally
+      FreeAndNil(Resp);
       FreeAndNil(Req);
     end;
   Except
@@ -865,9 +918,9 @@ end;
 
 { TFPHTTPConnectionThread }
 
-constructor TFPHTTPConnectionThread.CreateConnection(AConnection: TFPHTTPConnection; aOnTerminate : TNotifyEvent);
+constructor TFPHTTPConnectionThread.CreateConnection(AConnection: TFPHTTPConnection; aOnConnectionDone : TNotifyEvent);
 begin
-  OnTerminate:=aOnTerminate;
+  FOnDone:=aOnConnectionDone;
   FConnection:=AConnection;
   FreeOnTerminate:=True;
   Inherited Create(False);
@@ -876,22 +929,24 @@ end;
 
 procedure TFPHTTPConnectionThread.Execute;
 
-  Function AllowReading : Boolean; inline;
+  Function AllowReading : Boolean; // inline;
   begin
     Result:=not Terminated and Connection.EnableKeepAlive and Connection.KeepAlive
   end;
 
 begin
   try
-    repeat
-      if AllowReading and not Connection.RequestPending then
-        break;
-      Connection.HandleRequest;
-    until not (AllowReading and (FConnection.Socket.LastError=0));
+    // Always handle first request
+    Connection.HandleRequest;
+    While AllowReading and (FConnection.Socket.LastError=0) do
+      if Connection.RequestPending then
+        Connection.HandleRequest;
   except
     on E : Exception do
       TFPCustomHttpServer.HandleUnexpectedError(E);
   end;
+  If Assigned(FOnDone) then
+    FOnDone(Connection);
 end;
 
 { TFPCustomHttpServer }
@@ -925,7 +980,10 @@ end;
 
 function TFPCustomHttpServer.GetConnectionCount: Integer;
 begin
-  Result:=FConnectionHandler.GetActiveConnectionCount;
+  if Assigned(FConnectionHandler) then
+    Result:=FConnectionHandler.GetActiveConnectionCount
+  else
+    Result:=0;
 end;
 
 procedure TFPCustomHttpServer.DoCreateClientHandler(Sender: TObject; out AHandler: TSocketHandler);
@@ -933,6 +991,19 @@ begin
   AHandler:=GetSocketHandler(UseSSL);
 end;
 
+procedure TFPCustomHttpServer.DoAcceptIdle(Sender: TObject);
+begin
+  if Assigned(OnAcceptIdle) then
+    OnAcceptIdle(Sender);
+  try
+    // Allow the connection handler to check for requests
+    FConnectionHandler.CheckRequests;
+  except
+    On E : Exception do
+      HandleUnexpectedError(E);
+  end;
+end;
+
 function TFPCustomHttpServer.GetHostName: string;
 begin
   Result:=FCertificateData.HostName;
@@ -963,6 +1034,8 @@ begin
   if not (csDesigning in Componentstate) then
     if AValue then
       begin
+      if (FConnectionHandler=Nil) then
+        SetupConnectionHandler;
       CreateServerSocket;
       SetupSocket;
       StartServerSocket;
@@ -1032,7 +1105,7 @@ begin
   if FTreadMode=AValue then Exit;
   CheckInactive;
   FTreadMode:=AValue;
-  FConnectionHandler:=CreateConnectionHandler();
+  SetupConnectionHandler;
 end;
 
 function TFPCustomHttpServer.CreateRequest: TFPHTTPConnectionRequest;
@@ -1085,6 +1158,7 @@ begin
   Con:=CreateConnection(Data);
   Con.FServer:=Self;
   Con.OnRequestError:=@HandleRequestError;
+  Con.EnableKeepAlive:=Self.EnableKeepAlive;
   FConnectionHandler.HandleConnection(Con);
 end;
 
@@ -1095,8 +1169,16 @@ begin
   FServer.ReuseAddress:=true;
 end;
 
+procedure TFPCustomHttpServer.SetupConnectionHandler;
+begin
+  if Assigned(FConnectionHandler) then
+    FreeAndNil(FConnectionHandler);
+  FConnectionHandler:=CreateConnectionHandler();
+end;
+
 class procedure TFPCustomHttpServer.HandleUnexpectedError(E: Exception);
 begin
+  if Assigned(E) then;
   // Do nothing.
 end;
 
@@ -1112,7 +1194,7 @@ begin
   FServer.OnConnectQuery:=OnAllowConnect;
   FServer.OnConnect:=@DOConnect;
   FServer.OnAcceptError:=@DoAcceptError;
-  FServer.OnIdle:=OnAcceptIdle;
+  FServer.OnIdle:=@DoAcceptIdle;
   FServer.AcceptIdleTimeOut:=AcceptIdleTimeout;
 end;
 
@@ -1191,9 +1273,7 @@ begin
 end;
 
 destructor TFPCustomHttpServer.Destroy;
-var
-  ThreadList: TList;
-  I: Integer;
+
 begin
   Active:=False;
   if (GetConnectionCount>0) then