unit server_unit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Windows, Dialogs,
  WSocket, WSocketS;

  
Type

  { TTcpSrvClient }

  TTcpSrvClient = class(TWSocketClient)
  Private
    FConnectTime: TDateTime;
    FDataLentgh: LongInt;
    FRequestStream : TStream;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy();override;
    function TryRead():Boolean;
    property ConnectTime : TDateTime Read FConnectTime Write FConnectTime;
    property RequestStream : TStream Read FRequestStream;
    property DataLentgh : LongInt Read FDataLentgh;
  end;

  { TTcpSrvApp }

  TTcpSrvApp = class
  Private
    procedure HandleClientConnect(Sender: TObject;Client: TWSocketClient; Error: Word);
    procedure HandleClientDisconnect(Sender: TObject;
      Client: TWSocketClient; Error: Word);
    procedure HandleBgException(Sender: TObject; E: Exception;
      var CanClose: Boolean);
  private
    FWSocketServer: TWSocketServer;
    procedure ClientDataAvailable(Sender: TObject; Error: Word);
    procedure ProcessData(Client : TTcpSrvClient);
    procedure ClientBgException(Sender       : TObject;
                                E            : Exception;
                                var CanClose : Boolean);
    procedure ClientLineLimitExceeded(Sender        : TObject;
                                      Cnt           : LongInt;
                                      var ClearData : Boolean);
  Public
    constructor Create();
    destructor Destroy();override;
    procedure Display(Msg : String);
    procedure Start();
    procedure Stop();
    function IsActive():Boolean;
  End;
  
Implementation
uses umain, server_service_intf, server_service_imputils, binary_streamer;

procedure LogMsg(const Msg : String);
Begin
  fMain.LogMessage(Msg);
End;

procedure TTcpSrvApp.Display(Msg : String);
begin
  LogMsg(Msg);
end;

procedure TTcpSrvApp.Start();
begin
  Display('Starting...');
  FWSocketServer.Proto       := 'tcp';         { Use TCP protocol  }
  FWSocketServer.Port        := '1234';
  FWSocketServer.Addr        := '0.0.0.0';     { Use any interface }
  FWSocketServer.ClientClass := TTcpSrvClient;
  FWSocketServer.Listen;                       { Start litening    }
  Display('Waiting for clients...');
end;

procedure TTcpSrvApp.Stop();
begin
  FWSocketServer.CloseDelayed();
end;

function TTcpSrvApp.IsActive(): Boolean;
begin
  Result := ( FWSocketServer.State < wsClosed );
end;

procedure TTcpSrvApp.HandleClientConnect(
    Sender : TObject;
    Client : TWSocketClient;
    Error  : Word);
begin
    with Client as TTcpSrvClient do begin
        Display('Client connected.' +
                ' Remote: ' + PeerAddr + '/' + PeerPort +
                ' Local: '  + GetXAddr + '/' + GetXPort);
        Display('There is now ' +
                IntToStr(TWSocketServer(Sender).ClientCount) +
                ' clients connected.');
        LineMode            := False;
        LineEdit            := False;
        OnDataAvailable     := @ClientDataAvailable;
        OnLineLimitExceeded := @ClientLineLimitExceeded;
        OnBgException       := @ClientBgException;
        ConnectTime         := Now;
    end;
end;

procedure TTcpSrvApp.HandleClientDisconnect(
    Sender : TObject;
    Client : TWSocketClient;
    Error  : Word);
begin
    with Client as TTcpSrvClient do begin
        Display('Client disconnecting : ' + PeerAddr + '   ' +
                'Duration: ' + FormatDateTime('hh:nn:ss',
                Now - ConnectTime));
        Display('There is now ' +
                IntToStr(TWSocketServer(Sender).ClientCount - 1) +
                ' clients connected.');
    end;
end;

procedure TTcpSrvApp.ClientLineLimitExceeded(
    Sender        : TObject;
    Cnt           : LongInt;
    var ClearData : Boolean);
begin
    with Sender as TTcpSrvClient do begin
        Display('Line limit exceeded from ' + GetPeerAddr + '. Closing.');
        ClearData := TRUE;
        Close;
    end;
end;

constructor TTcpSrvApp.Create();
begin
  FWSocketServer := TWSocketServer.Create(Nil);
  FWSocketServer.Banner := '';
  FWSocketServer.OnClientConnect := @HandleClientConnect;
  FWSocketServer.OnBgException := @HandleBgException;
  FWSocketServer.OnClientDisconnect := @HandleClientDisconnect;
end;

destructor TTcpSrvApp.Destroy();
begin
  FWSocketServer.Free();
end;

procedure TTcpSrvApp.ClientDataAvailable(Sender : TObject;Error  : Word);
Var
  cliTCP : TTcpSrvClient;
begin
  cliTCP := Sender as TTcpSrvClient;
  //Display('ClientDataAvailable()');
  If cliTCP.TryRead() And ( cliTCP.DataLentgh > 0 ) Then
    ProcessData(cliTCP)
end;

procedure TTcpSrvApp.ProcessData(Client : TTcpSrvClient);
Var
  buff, trgt,ctntyp : string;
  rqst : IRequestBuffer;
  wrtr : IDataStore;
  rdr : IDataStoreReader;
  inStream, outStream, bufStream : TMemoryStream;
  i : Integer;
begin
  inStream := Nil;
  outStream := Nil;
  bufStream := Nil;
  Try
    Client.RequestStream.Position := 0;
    Try
      inStream := TMemoryStream.Create();
      outStream := TMemoryStream.Create();
      bufStream := TMemoryStream.Create();
      rdr := CreateBinaryReader(Client.RequestStream);
      trgt := rdr.ReadStr();
      ctntyp := rdr.ReadStr();
      buff := rdr.ReadStr();
      inStream.Write(buff[1],Length(buff));
      inStream.Position := 0;
      rqst := TRequestBuffer.Create(trgt,ctntyp,inStream,bufStream);
      HandleServiceRequest(rqst);
      i := bufStream.Size;
      SetLength(buff,i);
      bufStream.Position := 0;
      bufStream.Read(buff[1],i);
      wrtr := CreateBinaryWriter(outStream);
      wrtr.WriteStr(buff);
      //Display('ProcessData() resp Ln =' + IntToStr(i) + '; resp = ' + buff);
      Client.Send(outStream.Memory,outStream.Size);
    Finally
      //Display('ProcessData()>> END');
      bufStream.Free();
      outStream.Free();
      inStream.Free();
      Client.FDataLentgh := -1;
      Client.RequestStream.Size := 0;
    End;
  Except
    On e : Exception Do
      Display('ProcessData()>> Exception = '+e.Message);
  End;
end;

procedure TTcpSrvApp.HandleBgException(
    Sender       : TObject;
    E            : Exception;
    var CanClose : Boolean);
begin
    Display('Server exception occured: ' + E.ClassName + ': ' + E.Message);
    CanClose := FALSE;  { Hoping that server will still work ! }
end;

procedure TTcpSrvApp.ClientBgException(
    Sender       : TObject;
    E            : Exception;
    var CanClose : Boolean);
begin
    Display('Client exception occured: ' + E.ClassName + ': ' + E.Message);
    CanClose := TRUE;   { Goodbye client ! }
end;

{ TTcpSrvClient }

constructor TTcpSrvClient.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FDataLentgh := -1;
  FRequestStream := TMemoryStream.Create();
end;

destructor TTcpSrvClient.Destroy();
begin
  FRequestStream.Free();
  inherited Destroy();
end;

function TTcpSrvClient.TryRead(): Boolean;
Var
  i,j : PtrInt;
  buff : string;
begin
  If ( FDataLentgh < 0 ) Then Begin
    i := 4;
    If ( Receive(@FDataLentgh,i) < 4 ) Then Begin
      FDataLentgh := -1;
      Result := False;
      Exit;
    End;
    FDataLentgh := Reverse_32(FDataLentgh);
  End;
  If ( FDataLentgh > FRequestStream.Size ) Then Begin
    i := Min((FDataLentgh-FRequestStream.Size),1024);
    SetLength(buff,i);
    j := Receive(@(buff[1]),i);
    FRequestStream.Write(buff[1],j);
    //LogMsg(Format('Read %d bytes;  buff=%s',[j,buff]));
  End;
  Result := ( FDataLentgh <= FRequestStream.Size );
  //LogMsg(Format('TryRead() >> FDataLentgh=%d;  Size=%d',[FDataLentgh,FRequestStream.Size]));
end;

end.