TsgcHTTPServer | Sessioni

HTTP è un protocollo senza stato (almeno fino a HTTP 1.1), quindi il client richiede un file, il server invia una risposta e la connessione viene chiusa (è possibile abilitare il keep-alive in modo che la connessione non venga chiusa immediatamente, ma ciò va oltre lo scopo di questo articolo). Le sessioni consentono di memorizzare informazioni sul client, che possono essere utilizzate durante un login del client, ad esempio. È possibile utilizzare qualsiasi ID sessione univoco, cercare nell'elenco delle sessioni se ne esiste già una e, in caso contrario, crearne una nuova. Una sessione può essere distrutta dopo un periodo di inattività o manualmente dopo il logout del client.

Configurazione

Esistono alcune proprietà in TsgcHTTPServer che abilitano/disabilitano le sessioni nel componente server. Le più importanti sono:

Property Description
SessionState Questa è la prima proprietà che deve essere abilitata per poter utilizzare le Sessioni. Senza questa proprietà abilitata, le sessioni non funzioneranno

SessionTimeout

Qui è necessario impostare un valore maggiore di zero (in millisecondi) per il tempo massimo di attività di una sessione.
AutoStartSession Le sessioni possono essere create automaticamente (AutoStartSession = true) o manualmente (AutoStartSession = false). Se le sessioni vengono create automaticamente, il server utilizzerà RemoteIP come identificatore univoco per verificare se è presente una sessione attiva memorizzata.
SessionClass Facoltativo. La classe utilizzata dal server quando crea una nuova sessione. Impostarla su una propria discendente di TIdHTTPSession per memorizzare dati personalizzati all'interno di ogni sessione. Deve essere impostata prima che il server venga attivato.
SessionList L'elenco che contiene le sessioni attive. Leggerlo per cercare, creare o rimuovere sessioni via codice. È anche possibile assegnare un proprio elenco se è necessario il pieno controllo su dove vengono memorizzate le sessioni. Deve essere assegnato prima che il server venga attivato.


TsgcHTTPServer1.SessionState := True;
TsgcHTTPServer1.SessionTimeout := 600000;
TsgcHTTPServer1.AutoStartSession := False;

Crea sessione

Per creare una nuova sessione, è necessario creare un nuovo ID sessione che sia univoco. È possibile utilizzare qualsiasi valore. Esempio: se il client si sta autenticando, è possibile utilizzare utente + password + IP remoto come ID sessione.

Quindi, cercare nell'elenco delle sessioni se esiste già. Se non esiste, crearne una nuova.

Quando viene creata una nuova sessione viene chiamato l'evento OnSessionStart e quando la sessione viene chiusa viene generato l'evento 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 := TsgcHTTPServer1.SessionList.GetSession(vID, ARequestInfo.RemoteIP);

      // create new session if not exists
      if not Assigned(oSession) then
        oSession := TsgcHTTPServer1.SessionList.CreateSession(ARequestInfo.RemoteIP, vID);

      AResponseInfo.ContentText := '<html><head></head><body>Authenticated</body></html>';
      AResponseInfo.ResponseNo := 200;
    end;
  end;
end;

Leggere la sessione corrente

Una volta che una sessione esiste, il server la collega a ogni richiesta che porta il cookie di sessione. Leggerla da ARequestInfo.Session, che è nil quando la richiesta non ha alcuna sessione.


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;

Utilizzare una propria classe di sessione

Se si desidera mantenere i propri campi all'interno di ogni sessione, invece di utilizzare la string list Content, creare una discendente di TIdHTTPSession e indicare al server di utilizzarla tramite la proprietà SessionClass. Impostarla prima che il server venga attivato.

Il server continua a occuparsi di tutto il resto: genera l'ID di sessione univoco, invia il cookie di sessione, applica SessionTimeout e rimuove le sessioni obsolete. Sovrascrivere il costruttore virtuale CreateInitialized se si desidera inizializzare i propri campi quando la sessione viene creata.


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
TsgcHTTPServer1.SessionState := True;
TsgcHTTPServer1.SessionTimeout := 600000;
TsgcHTTPServer1.SessionClass := TMySession;
TsgcHTTPServer1.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;

Utilizzare un proprio elenco di sessioni

Se è necessario il pieno controllo su come vengono memorizzate le sessioni, ad esempio conservandole in un database o condividendole tra più server, assegnare un proprio elenco alla proprietà SessionList, prima che il server venga attivato. Discendere da TIdHTTPDefaultSessionList e sovrascrivere il metodo virtuale CreateSession. Il server lo richiama internamente da CreateUniqueSession, quindi l'ID di sessione univoco viene comunque generato automaticamente.

Quando si assegna un proprio SessionList, la proprietà SessionClass non si applica più, perché è il proprio elenco a decidere quale classe creare.


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
TsgcHTTPServer1.SessionList := TMySessionList.Create(nil);
TsgcHTTPServer1.Active := True;

Una nota su OnCreateSession

L'evento OnCreateSession consente di restituire da soli un'istanza di sessione, ma non le assegna un ID di sessione. Se si crea la sessione con un costruttore semplice, l'ID di sessione e il cookie di sessione risultano vuoti e la sessione non potrà più essere trovata alla richiesta successiva. Utilizzare invece SessionClass, che è più semplice e gestisce tutto questo automaticamente.

Inoltre, non chiamare SessionList.CreateUniqueSession all'interno di OnCreateSession. Quel metodo aggiunge già la nuova sessione all'elenco, e il server la aggiunge nuovamente quando il gestore restituisce il controllo, per cui la stessa sessione finisce per comparire due volte nell'elenco.