HTTP Chat sample (Delphi)

Overview

The HTTP Chat sample demonstrates how to use callback events through an HTTP Channel to create an HTTP based chat program. The client polls for new messages every few seconds and the server distributes the messages to the appropriate client(s). To demonstrate all implemented features, you need to run at least three clients.
Two different session manager and event repository pairs can be used in this sample:

Getting started

  • Compile both projects.
  • Run the server application.
  • Set the desired Session Manager; the corresponding event repository is chosen automatically.
  • Activate the server.

  • Run several client applications (3 client instances are recommended to demonstrate all implemented features).
  • Send a message to all clients.
  • Send a message to selected clients.

  • Close a client by server command.
  • Log out a user and see the effect on the server and other clients.
  • Issue a shutdown warning from the server.
  • Deactivate the server and try to send messages.

Examine the code

The Server

  • Open the HTTPChatLibrary in the Service Builder in the RO IDE menu to see its service and some events under the two EventSync elements.
  • Examine the HTTPChatService service implementation:
function THTTPChatService.Login(const aUserID: Unicodestring): Unicodestring;
var
  newuser: TUserInfo;
  ev: IHTTPChatEvents_Writer;
  useridx: integer;
begin
  fcs.Enter;
  try
    if SessionManager.FindSession(Session.SessionID) <> nil then
      { Checks if the user is already logged in }
      if (fUsers.Search('UserID', aUserID) <> nil) then raise Exception.CreateFmt('User %s is already logged in', [aUserID])
    else
    begin
      useridx := fUsers.GetIndex('UserID', aUserID);
      if (useridx >= 0) then fUsers.Delete(useridx);
    end;

    { Adds the user to the internal list of logged users }
    newuser := fUsers.Add;
    newuser.UserID := aUserID;
    newuser.SessionID := GUIDToString(Session.SessionID);

    { Stores the UserID of the user in the session.
      This will be used in the OnLogout and OnSendMessage methods }
    Session.Values['UserID'] := aUserID;
    result := newuser.SessionID;

    RegisterEventClient(ClientID, EID_HTTPChatEvents);
    RegisterEventClient(ClientID, EID_HTTPChatServerEvents);

    ev := (EventRepository as IHTTPChatEvents_Writer);
    ev.ExcludeSender := false; // make sure to send it back to sender too

    { Generates the OnLogin event }
    ev.OnLogin(Session.SessionID, newuser);
  finally
    fcs.Release;
  end;
end;
function THTTPChatService.Login(const aUserID: Unicodestring): Unicodestring;
var
  newuser: TUserInfo;
  ev: IROEventWriter<IHTTPChatEvents>;
  useridx: integer;
begin
  fcs.Enter;
  try
    if SessionManager.FindSession(Session.SessionID) <> nil then
      { Checks if the user is already logged in }
      if (fUsers.Search('UserID', aUserID) <> nil) then raise Exception.CreateFmt('User %s is already logged in', [aUserID])
    else
    begin
      useridx := fUsers.GetIndex('UserID', aUserID);
      if (useridx >= 0) then fUsers.Delete(useridx);
    end;

    { Adds the user to the internal list of logged users }
    newuser := fUsers.Add;
    newuser.UserID := aUserID;
    newuser.SessionID := GUIDToString(Session.SessionID);

    { Stores the UserID of the user in the session.
      This will be used in the OnLogout and OnSendMessage methods }
    Session.Values['UserID'] := aUserID;
    result := newuser.SessionID;

    RegisterEventClient(ClientID, 'HTTPChatEvents');
    RegisterEventClient(ClientID, 'HTTPChatServerEvents');

    ev := (EventRepository.GetWriter<IHTTPChatEvents>(Session.SessionID));
    ev.ExcludeSender := false; // make sure to send it back to sender too

    { Generates the OnLogin event }
    ev.Event.OnLogin( newuser);
  finally
    fcs.Release;
  end;
end;
  • Note that we call the RegisterEventClient function. This function subscribes the current client to the event sink passed there as a parameter.

  • See how we obtain an event proxy in order to fire events. There is the ExcludeSender property which allows to skip sending the event to the current session:

ev := (EventRepository as IHTTPChatEvents_Writer);
ev.ExcludeSender := false; // make sure to send it back to sender too

{ Generates the OnLogin event }
ev.OnLogin(Session.SessionID, newuser);
ev := (EventRepository.GetWriter<IHTTPChatEvents>(Session.SessionID));
ev.ExcludeSender := false; // make sure to send it back to sender too

{ Generates the OnLogin event }
ev.Event.OnLogin(newuser);
  • Examine how we send messages to clients:
procedure THTTPChatService.SendMessage(const aMessageText: Unicodestring; const aDestination: Unicodestring);
var
  thisuserid: Unicodestring;
  chateventswriter: IHTTPChatEvents_Writer;
begin
  { Extracts the name of the current user by reading the session information }
  thisuserid := VarToStr(Session.Values['UserID']);

  { Filters the receivers of this event if necessary }
  chateventswriter := (EventRepository as IHTTPChatEvents_Writer);
  if (aDestination <> '') then begin
    chateventswriter.SessionList.CommaText := aDestination;

    { Only broadcasts to the session listed in SessionList }
    chateventswriter.ExcludeSessionList := FALSE;
  end;
  chateventswriter.ExcludeSender := false;

  { Generates the OnSendMessage event }
  chateventswriter.OnSendMessage(Session.SessionID, thisuserid, aMessageText, aDestination <> '');
end;
procedure THTTPChatService.SendMessage(const aMessageText: Unicodestring; const aDestination: Unicodestring);
var
  thisuserid: Unicodestring;
  chateventswriter: IROEventWriter<IHTTPChatEvents>;
begin
  { Extracts the name of the current user by reading the session information }
  thisuserid := VarToStr(Session.Values['UserID']);

  { Filters the receivers of this event if necessary }
  chateventswriter := (EventRepository.GetWriter<IHTTPChatEvents>(Session.SessionID));
  if (aDestination <> '') then begin
    chateventswriter.SessionList.CommaText := aDestination;

    { Only broadcasts to the session listed in SessionList }
    chateventswriter.ExcludeSessionList := FALSE;
  end;
  chateventswriter.ExcludeSender := false;

  { Generates the OnSendMessage event }
  chateventswriter.Event.OnSendMessage(thisuserid, aMessageText, aDestination <> '');
end;

The Client

  • Examine THTTPChatClientMainForm class. Notice that this class supports IHTTPChatEvents and IHTTPChatServerEvents interfaces that allow it to be the event handler.
  • The ROEventReceiver field of TROEventReceiver type is responsible for the receiving and dispatching of events. It is necessary to call the RegisterEventHandlers method and activate the receiver in order to start receiving events and dispatch them to the appropriate handler.
{ Register the event handlers }
EventReceiver.RegisterEventHandlers([EID_HTTPChatEvents, EID_HTTPChatServerEvents], [Self, Self]);

{ Starts polling }
EventReceiver.Active := TRUE;
  • Note that all events are fired in the main thread because we set the SynchronizeInvoke property of the EventReceiver to True.

JavaScript client

Open http://localhost:8099/js/ when the HTTP chat server is running. Examine the page source to see the EventReceiver usage example.