HTTP jest protokołem bezstanowym (przynajmniej do wersji HTTP 1.1), więc klient żąda pliku, serwer wysyła odpowiedź i połączenie jest zamykane (można włączyć keep-alive, aby połączenie nie było zamykane natychmiast, ale wykracza to poza zakres tego artykułu). Sesje umożliwiają przechowywanie informacji o kliencie, które mogą być używane na przykład podczas logowania klienta. Można użyć dowolnego unikalnego identyfikatora sesji, przeszukać listę sesji w celu sprawdzenia, czy już istnieje, a jeśli nie, utworzyć nową sesję. Sesja może zostać zniszczona po okresie bezczynności lub ręcznie po wylogowaniu klienta.
W komponencie TsgcWebSocketHTTPServer istnieje kilka właściwości włączających/wyłączających sesje. Najważniejsze z nich to:
| Właściwość | Opis |
| SessionState | To pierwsza właściwość, która musi zostać włączona, aby korzystać z sesji. Bez włączenia tej właściwości sesje nie będą działać |
|
SessionTimeout |
Należy ustawić wartość większą od zera (w milisekundach) określającą maksymalny czas aktywności sesji. |
| AutoStartSession | Sesje mogą być tworzone automatycznie (AutoStartSession = true) lub ręcznie (AutoStartSession = false). Jeśli sesje są tworzone automatycznie, serwer użyje RemoteIP jako unikalnego identyfikatora do sprawdzenia, czy istnieje aktywna sesja zapisana. |
| SessionClass | Opcjonalne. Klasa, której serwer używa przy tworzeniu nowej sesji. Ustaw ją na własnego potomka TIdHTTPSession, aby przechowywać własne dane w każdej sesji. Musi zostać ustawiona przed aktywacją serwera. |
| SessionList | Lista przechowująca aktywne sesje. Odczytuj ją, aby wyszukiwać, tworzyć lub usuwać sesje z poziomu kodu. Możesz też przypisać własną listę, jeśli potrzebujesz pełnej kontroli nad tym, gdzie sesje są przechowywane. Musi zostać przypisana przed aktywacją serwera. |
TsgcWebSocketHTTPServer1.SessionState := True;
TsgcWebSocketHTTPServer1.SessionTimeout := 600000;
TsgcWebSocketHTTPServer1.AutoStartSession := False;
Aby utworzyć nową sesję, należy utworzyć nowy identyfikator sesji, który jest unikalny. Można użyć dowolnej wartości. Przykład: jeśli klient się uwierzytelnia, jako identyfikator sesji można użyć użytkownik + hasło + remoteip.
Następnie przeszukaj listę sesji, aby sprawdzić, czy sesja już istnieje. Jeśli nie istnieje, utwórz nową.
Gdy tworzona jest nowa sesja, wywoływane jest zdarzenie OnSessionStart, a gdy sesja jest zamykana, wywoływane jest zdarzenie OnSessionEnd.
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;
Gdy sesja już istnieje, serwer dołącza ją do każdego żądania niosącego ciasteczko sesji. Odczytaj ją z ARequestInfo.Session, które jest nil, gdy żądanie nie ma sesji.
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;
Jeśli chcesz przechowywać własne pola w każdej sesji, zamiast korzystać z listy ciągów Content, utwórz potomka TIdHTTPSession i każ serwerowi go używać za pomocą właściwości SessionClass. Ustaw ją przed aktywacją serwera.
Serwer nadal zajmuje się całą resztą: generuje unikalny identyfikator sesji, wysyła ciasteczko sesji, stosuje SessionTimeout i usuwa nieaktualne sesje. Przesłoń wirtualny konstruktor CreateInitialized, jeśli chcesz zainicjować własne pola przy tworzeniu sesji.
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;
Jeśli potrzebujesz pełnej kontroli nad sposobem przechowywania sesji, na przykład przechowywania ich w bazie danych lub współdzielenia między kilkoma serwerami, przypisz własną listę do właściwości SessionList przed aktywacją serwera. Utwórz klasę pochodną od TIdHTTPDefaultSessionList i przesłoń wirtualną metodę CreateSession. Serwer wywołuje ją wewnętrznie z CreateUniqueSession, więc unikalny identyfikator sesji jest nadal generowany automatycznie.
Gdy przypiszesz własną listę SessionList, właściwość SessionClass przestaje obowiązywać, ponieważ to twoja lista decyduje, którą klasę utworzyć.
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;
Zdarzenie OnCreateSession pozwala samodzielnie zwrócić instancję sesji, ale nie przypisuje jej identyfikatora sesji. Jeśli utworzysz sesję zwykłym konstruktorem, identyfikator sesji i ciasteczko sesji są puste, a sesji nie da się już odnaleźć przy kolejnym żądaniu. Użyj zamiast tego SessionClass, co jest prostsze i załatwia to wszystko za ciebie.
Ponadto nie wywołuj SessionList.CreateUniqueSession wewnątrz OnCreateSession. Ta metoda już dodaje nową sesję do listy, a serwer dodaje ją ponownie, gdy twoja procedura obsługi zwróci sterowanie, więc ta sama sesja trafia na listę dwukrotnie.