HTTP is a stateless protocol (at least up to HTTP 1.1), so the client requests a file, the server sends a response, and the connection is closed (you can enable keep-alive so the connection is not closed immediately, but that is beyond the scope of this article). Sessions allow you to store information about the client, which can be used during a client login for example. You can use any unique session ID, search the list of sessions to see if one already exists, and if not, create a new session. A session can be destroyed after a period of inactivity or manually after client logout.
There are some properties in TsgcWebSocketHTTPServer that enable/disable sessions in the server component. The most important are:
| Property | Description |
| SessionState | This is the first property that must be enabled in order to use Sessions. Without this property enabled, sessions will not work |
|
SessionTimeout |
Here you must set a value greater than zero (in milliseconds) for the maximum time a session will be active. |
| AutoStartSession | Sessions can be created automatically (AutoStartSession = true) or manually (AutoStartSession = false). If sessions are created automatically, the server will use RemoteIP as a unique identifier to check if there is an active session stored. |
| SessionClass | Optional. The class the server uses when it creates a new session. Set it to your own TIdHTTPSession descendant to store your own data inside every session. Must be set before the server is activated. |
| SessionList | The list that holds the active sessions. Read it to search, create or remove sessions by code. You can also assign your own list if you need full control over where sessions are stored. Must be assigned before the server is activated. |
TsgcWebSocketHTTPServer1.SessionState := True;
TsgcWebSocketHTTPServer1.SessionTimeout := 600000;
TsgcWebSocketHTTPServer1.AutoStartSession := False;
To create a new session, you must create a new session ID that is unique. You can use any value. Example: if the client is authenticating, you can use user + password + remoteip as the session ID.
Then, search the session list to check if it already exists. If it does not exist, create a new one.
When a new session is created OnSessionStart event is called and when session is closed, OnSessionEnd event is raised.
procedure OnCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
var
vID: String;
oSession: TIdHTTPSession;
begin
if ARequestInfo.Document = '/' then
AResponseInfo.ServeFile(AContext, 'yourpathhere\index.html')
else
begin
// check if user is valid
if not ((ARequestInfo.AuthUsername = 'user') and (ARequestInfo.AuthPassword = 'pass')) then
AResponseInfo.AuthRealm := 'Authenticate'
else
begin
// create a new session id with authentication data
vID := ARequestInfo.AuthUsername + '_' + ARequestInfo.AuthPassword + '_' + ARequestInfo.RemoteIP;
// search session
oSession := TsgcWebSocketHTTPServer1.SessionList.GetSession(vID, ARequestInfo.RemoteIP);
// create new session if not exists
if not Assigned(oSession) then
oSession := TsgcWebSocketHTTPServer1.SessionList.CreateSession(ARequestInfo.RemoteIP, vID);
AResponseInfo.ContentText := '<html><head></head><body>Authenticated</body></html>';
AResponseInfo.ResponseNo := 200;
end;
end;
end;
Once a session exists, the server attaches it to every request that carries the session cookie. Read it from ARequestInfo.Session, which is nil when the request has no session.
procedure OnCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
begin
if Assigned(ARequestInfo.Session) then
begin
// Content is a TStrings you can use to store your own values
ARequestInfo.Session.Content.Values['visits'] :=
IntToStr(StrToIntDef(ARequestInfo.Session.Content.Values['visits'], 0) + 1);
AResponseInfo.ContentText := 'Session ' + ARequestInfo.Session.SessionID +
' visits: ' + ARequestInfo.Session.Content.Values['visits'];
end
else
AResponseInfo.ContentText := 'No session';
end;
If you want to keep your own fields inside every session, instead of using the Content string list, create a descendant of TIdHTTPSession and tell the server to use it through the SessionClass property. Set it before the server is activated.
The server keeps taking care of everything else: it generates the unique session ID, it sends the session cookie, it applies SessionTimeout and it removes stale sessions. Override the virtual constructor CreateInitialized if you want to initialize your own fields when the session is created.
type
TMySession = class(TIdHTTPSession)
private
FUserName: String;
FLoginTime: TDateTime;
public
constructor CreateInitialized(AOwner: TIdHTTPCustomSessionList;
const SessionID, RemoteIP: string); override;
property UserName: String read FUserName write FUserName;
property LoginTime: TDateTime read FLoginTime write FLoginTime;
end;
constructor TMySession.CreateInitialized(AOwner: TIdHTTPCustomSessionList;
const SessionID, RemoteIP: string);
begin
inherited CreateInitialized(AOwner, SessionID, RemoteIP);
FLoginTime := Now;
end;
// configure the server before it starts
TsgcWebSocketHTTPServer1.SessionState := True;
TsgcWebSocketHTTPServer1.SessionTimeout := 600000;
TsgcWebSocketHTTPServer1.SessionClass := TMySession;
TsgcWebSocketHTTPServer1.Active := True;
// and read it back in any request
procedure OnCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Session is TMySession then
AResponseInfo.ContentText := TMySession(ARequestInfo.Session).UserName;
end;
If you need full control over how sessions are stored, for example keeping them in a database or sharing them between several servers, assign your own list to the SessionList property, before the server is activated. Descend from TIdHTTPDefaultSessionList and override the virtual CreateSession method. The server calls it internally from CreateUniqueSession, so the unique session ID is still generated for you.
When you assign your own SessionList, the SessionClass property no longer applies, because your list decides which class to create.
type
TMySessionList = class(TIdHTTPDefaultSessionList)
public
function CreateSession(const RemoteIP, SessionID: string)
: TIdHTTPSession; override;
end;
function TMySessionList.CreateSession(const RemoteIP, SessionID: string)
: TIdHTTPSession;
begin
Result := TMySession.CreateInitialized(Self, SessionID, RemoteIP);
SessionList.Add(Result);
end;
// assign it before the server starts
TsgcWebSocketHTTPServer1.SessionList := TMySessionList.Create(nil);
TsgcWebSocketHTTPServer1.Active := True;
The OnCreateSession event lets you return a session instance yourself, but it does not assign a session ID to it. If you create the session with a plain constructor, the session ID and the session cookie are empty and the session can never be found again on the next request. Use SessionClass instead, which is simpler and handles all of that for you.
Also, do not call SessionList.CreateUniqueSession inside OnCreateSession. That method already adds the new session to the list, and the server adds it again when your handler returns, so the same session ends up twice in the list.