{
    This file is part of the Web Service Toolkit
    Copyright (c) 2006 by Inoussa OUEDRAOGO

    This file is provide under modified LGPL licence
    ( the files COPYING.modifiedLGPL and COPYING.LGPL).


    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.
}
{$INCLUDE wst_global.inc}
unit synapse_tcp_server;

interface

uses
  Classes, SysUtils, blcksock, synsock, server_listener;

{$INCLUDE wst.inc}
{$INCLUDE wst_delphi.inc}
  
const
  sSERVER_PORT = 1234;
  
type

  TwstSynapseTcpListener = class;

  { TClientHandlerThread }

  TClientHandlerThread = class(TThread)
  private
    FDefaultTimeOut: Integer;
    FSocketObject : TTCPBlockSocket;
    FSocketHandle : TSocket;
    FInputStream : TMemoryStream;
    FOutputStream : TMemoryStream;
    FOwner : TwstSynapseTcpListener;
  private
    procedure ClearBuffers();
    function ReadInputBuffer():Integer;
    procedure SendOutputBuffer();
  public
    constructor Create (ASocketHandle : TSocket; AOwner : TwstSynapseTcpListener);
    destructor Destroy();override;
    procedure Execute(); override;
    property DefaultTimeOut : Integer read FDefaultTimeOut write FDefaultTimeOut;
  end;
  
  { TServerListnerThread }

  TServerListnerThread = class(TThread)
  private
    FDefaultTimeOut: Integer;
    FSocketObject : TTCPBlockSocket;
    FSuspendingCount : Integer;
    FOwner : TwstSynapseTcpListener;
  public
    constructor Create(AOwner : TwstSynapseTcpListener);
    destructor Destroy(); override;
    procedure Execute(); override;
    procedure SuspendAsSoonAsPossible();
    procedure ResumeListening();
    property DefaultTimeOut : Integer read FDefaultTimeOut write FDefaultTimeOut;
  end;
  
  { TwstSynapseTcpListener }

  TwstSynapseTcpListener = class(TwstListener)
  private
    FServerThread : TServerListnerThread;
    FServerIpAddress : string;
    FListningPort : Integer;
    FDefaultClientPort : Integer;
    FServerSoftware : string;
  public
    constructor Create(
      const AServerIpAddress   : string  = '127.0.0.1';
      const AListningPort      : Integer = sSERVER_PORT;
      const ADefaultClientPort : Integer = 25000;
      const AServerSoftware    : string  = 'Web Service Toolkit Application'
    );
    destructor Destroy();override;
    procedure Start();override;
    procedure Stop();override;
  end;
  
implementation
uses binary_streamer, server_service_intf, server_service_imputils
     {$IFNDEF FPC}, Windows, ActiveX, ComObj{$ENDIF};


{ TClientHandlerThread }

procedure TClientHandlerThread.ClearBuffers();
begin
  FInputStream.Size := 0;
  FOutputStream.Size := 0;
end;

function TClientHandlerThread.ReadInputBuffer(): Integer;
var
  strBuff : string;
  bufferLen : LongInt;
  i, j, c, readBufferLen : PtrInt;
begin
  FInputStream.Size := 0;
  Result := 0;
  bufferLen := 0;
  readBufferLen := FSocketObject.RecvBufferEx(@bufferLen,SizeOf(bufferLen),DefaultTimeOut);
  if ( readBufferLen = 0 ) and ( FSocketObject.LastError = WSAETIMEDOUT ) then begin
    Result := 0;
    WriteLn('ReadInputBuffer() => TimeOut');
  end else begin
    FSocketObject.ExceptCheck();
    bufferLen := Reverse_32(bufferLen);
    FInputStream.Size := bufferLen;
    if ( bufferLen > 0 ) then begin
      c := 0;
      i := 1024;
      if ( i > bufferLen ) then
        i := bufferLen;
      SetLength(strBuff,i);
      repeat
        j := FSocketObject.RecvBufferEx(@(strBuff[1]),i,DefaultTimeOut);
        FSocketObject.ExceptCheck();
        FInputStream.Write(strBuff[1],j);
        Inc(c,j);
        if ( ( bufferLen - c ) > 1024 ) then
          i := 1024
        else
          i := bufferLen - c;
      until ( i = 0 ) or ( j <= 0 );
    end;
    FInputStream.Position := 0;
    Result := FInputStream.Size;
  end;
end;

procedure TClientHandlerThread.SendOutputBuffer();
begin
  FSocketObject.SendBuffer(FOutputStream.Memory,FOutputStream.Size);
end;

constructor TClientHandlerThread.Create(
  ASocketHandle : TSocket;
  AOwner : TwstSynapseTcpListener
);
begin
  FSocketHandle := ASocketHandle;
  FreeOnTerminate := True;
  FDefaultTimeOut := -1;//90000;
  FOwner := AOwner;
  inherited Create(False);
end;

destructor TClientHandlerThread.Destroy();
begin
  FreeAndNil(FOutputStream);
  FreeAndNil(FInputStream);
  inherited Destroy();
end;

function GetFormatForContentType(const AContentType : string):string ;
begin
  Result := Trim(AContentType);
  if AnsiSameText(Result,'text/xml') then
    Result := 'soap'
  else
    Result := 'binary';
end;

procedure TClientHandlerThread.Execute();
var
  wrtr : IDataStore;
  rdr : IDataStoreReader;
  buff, trgt,ctntyp, frmt : string;
  rqst : IRequestBuffer;
  i : PtrUInt;
begin
{$IFNDEF FPC}
  CoInitialize(nil);
  try
{$ENDIF}
    FInputStream := TMemoryStream.Create();
    FOutputStream := TMemoryStream.Create();
    FSocketObject := TTCPBlockSocket.Create();
    try
      FSocketObject.RaiseExcept := True;
      try
        FSocketObject.Socket := FSocketHandle;
        FSocketObject.GetSins();
        while not Terminated do begin
          FOutputStream.Size := 0;
          if ( ReadInputBuffer() >= SizeOf(LongInt) ) then begin
            rdr := CreateBinaryReader(FInputStream);
            trgt := rdr.ReadStr();
            ctntyp := rdr.ReadStr();
            frmt := rdr.ReadStr();
            buff := rdr.ReadStr();
            rdr := nil;
            FInputStream.Size := 0;
            FInputStream.Write(buff[1],Length(buff));
            FInputStream.Position := 0;
            rqst := TRequestBuffer.Create(trgt,ctntyp,FInputStream,FOutputStream,frmt);
            HandleServiceRequest(rqst);
            i := FOutputStream.Size;
            SetLength(buff,i);
            FOutputStream.Position := 0;
            FOutputStream.Read(buff[1],i);
            FOutputStream.Size := 0;
            wrtr := CreateBinaryWriter(FOutputStream);
            wrtr.WriteStr(buff);
            SendOutputBuffer();
            ClearBuffers();
          end;
        end;
      except
        on e : Exception do begin
          FOwner.NotifyMessage(Format('Error : ThreadID = %d; Message = %s',[Self.ThreadID,e.Message]));
        end;
      end;
    finally
      FreeAndNil(FSocketObject);
    end;
{$IFNDEF FPC}
  finally
    CoUninitialize();
  end;
{$ENDIF}    
end;

{ TServerListnerThread }

constructor TServerListnerThread.Create(AOwner : TwstSynapseTcpListener);
begin
  FSocketObject := TTCPBlockSocket.Create();
  FreeOnTerminate := True;
  FDefaultTimeOut := 1000;
  FOwner := AOwner;
  inherited Create(false);
end;

destructor TServerListnerThread.Destroy();
begin
  FreeAndNil(FSocketObject);
  inherited Destroy();
end;

procedure TServerListnerThread.Execute();
var
  ClientSock : TSocket;
begin
{$IFNDEF FPC}
  CoInitialize(nil);
  try
{$ENDIF}
    try
      FSocketObject.RaiseExcept := True;
      FSocketObject.CreateSocket();
      FSocketObject.SetLinger(True,10);
      FSocketObject.Bind(FOwner.FServerIpAddress,IntToStr(FOwner.FListningPort));
      FSocketObject.Listen();
      while not Terminated do begin
        if ( FSuspendingCount > 0 ) then begin
          Suspend();
        end;
        if FSocketObject.CanRead(DefaultTimeOut) then begin
          ClientSock := FSocketObject.Accept();
          TClientHandlerThread.Create(ClientSock,FOwner);
        end;
      end;
    except
      on e : Exception do begin
        FOwner.NotifyMessage(Format('Listner Thread Error : ThreadID = %d; Message = %s',[Self.ThreadID,e.Message]));
        FOwner.NotifyMessage('Listner stoped.');
      end;
    end;
{$IFNDEF FPC}
  finally
    CoUninitialize();
  end;
{$ENDIF}
end;

procedure TServerListnerThread.SuspendAsSoonAsPossible();
begin
  InterLockedIncrement(FSuspendingCount);
end;

procedure TServerListnerThread.ResumeListening();
begin
  InterLockedDecrement(FSuspendingCount);
  if ( FSuspendingCount <= 0 ) then begin
    if Suspended then
      Resume();
  end;
end;

{ TwstSynapseTcpListener }

constructor TwstSynapseTcpListener.Create(
  const AServerIpAddress : string;
  const AListningPort : Integer;
  const ADefaultClientPort : Integer;
  const AServerSoftware : string
);
begin
  FServerIpAddress := AServerIpAddress;
  FListningPort := AListningPort;
  FDefaultClientPort := ADefaultClientPort;
  FServerSoftware := AServerSoftware;
end;

destructor TwstSynapseTcpListener.Destroy();
begin
  if ( FServerThread <> nil ) then begin
    FServerThread.Terminate();
    Start();
  end;
  inherited Destroy();
end;

procedure TwstSynapseTcpListener.Start();
begin
  if ( FServerThread = nil ) then
    FServerThread := TServerListnerThread.Create(Self);
  if FServerThread.Suspended then
    FServerThread.ResumeListening();
end;

procedure TwstSynapseTcpListener.Stop();
begin
  if ( FServerThread <> nil ) and ( not FServerThread.Suspended ) then
    FServerThread.SuspendAsSoonAsPossible();
end;

end.

