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:
- TROInMemorySessionManager and TROInMemoryEventRepository – This pair keeps all required data in memory.
- TROOlympiaSessionManager and TROOlympiaEventRepository – This pair uses an Olympia server to store the necessary data. Requires an Olympia server to be running on the same host as the server.
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 supportsIHTTPChatEvents
andIHTTPChatServerEvents
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 theRegisterEventHandlers
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 theEventReceiver
toTrue
.
JavaScript client
Open http://localhost:8099/js/ when the HTTP chat server is running. Examine the page source to see the EventReceiver
usage example.