TsgcHTTPServer | セッション

HTTPはステートレスプロトコルです(少なくともHTTP 1.1まで)。クライアントはファイルをリクエストし、サーバーはレスポンスを送信し、接続が閉じられます(keep-aliveを有効にすることで接続が即座に閉じられないようにできますが、これはこの記事の範囲外です)。セッションを使用すると、クライアントに関する情報を保存できます。例えば、クライアントのログイン時に使用できます。任意の一意のセッションIDを使用し、セッションリストを検索して既存のセッションがあるかどうかを確認し、なければ新しいセッションを作成できます。セッションは一定期間の非アクティブ後に破棄するか、クライアントのログアウト後に手動で破棄できます。

設定

TsgcHTTPServerには、サーバーコンポーネントでセッションを有効/無効にするいくつかのプロパティがあります。最も重要なものは次のとおりです。

プロパティ 説明
SessionState Tこれはセッションを使用するために最初に有効にする必要があるプロパティです。このプロパティが有効になっていない場合、セッションは機能しません。

SessionTimeout

ここでは、セッションがアクティブになる最大時間(ミリ秒)としてゼロより大きい値を設定する必要があります。
AutoStartSession セッションは自動的に作成(AutoStartSession = true)または手動で作成(AutoStartSession = false)できます。セッションが自動的に作成される場合、サーバーはアクティブなセッションが格納されているかどうかを確認するために RemoteIP を一意識別子として使用します。
SessionClass オプションです。サーバーが新しいセッションを作成する際に使用するクラスです。独自の TIdHTTPSession の子孫クラスを設定して、各セッション内に独自のデータを保存できます。サーバーが有効化される前に設定する必要があります。
SessionList アクティブなセッションを保持するリスト。コードでセッションを検索、作成、削除するにはこれを読み取ります。セッションの保存場所を完全に制御する必要がある場合は、独自のリストを割り当てることもできます。サーバーが起動される前に割り当てる必要があります。


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

セッションを作成します

新しいセッションを作成するには、一意の新しいセッション ID を作成する必要があります。任意の値を使用できます。使用例: クライアントが認証する場合、セッション ID としてユーザー名 + パスワード + リモート IP を使用できます。

次に、セッションリストを検索してすでに存在するか確認します。存在しない場合は新しいセッションを作成します。

新しいセッションが作成されると OnSessionStart イベントが呼び出され、セッションが閉じられると 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;

現在のセッションを読み取る

セッションが存在する場合、サーバーはセッションクッキーを伴うすべてのリクエストにそのセッションを関連付けます。ARequestInfo.Session から読み取ってください。リクエストにセッションがない場合は nil になります。


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;

独自のセッションクラスを使用する

Content の文字列リストを使う代わりに、独自のフィールドを各セッション内に保持したい場合は、TIdHTTPSession の子孫クラスを作成し、SessionClass プロパティを通じてサーバーにそれを使うよう指示してください。これはサーバーが有効化される前に設定してください。

サーバーはその他すべての処理を引き続き担当します。一意のセッションIDを生成し、セッションクッキーを送信し、SessionTimeoutを適用し、古くなったセッションを削除します。セッション作成時に独自のフィールドを初期化したい場合は、仮想コンストラクタCreateInitializedをオーバーライドしてください。


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;

独自のセッションリストを使用する

セッションの保存方法を完全に制御する必要がある場合(たとえばデータベースに保存する、または複数のサーバー間で共有するなど)、サーバーが有効化される前に独自のリストを SessionList プロパティに割り当ててください。TIdHTTPDefaultSessionList を継承し、仮想メソッド CreateSession をオーバーライドしてください。サーバーは内部で CreateUniqueSession からこれを呼び出すため、一意のセッション ID は引き続き自動的に生成されます。

独自の SessionList を割り当てると、どのクラスを作成するかはそのリストが決定するため、SessionClass プロパティはもはや適用されません。


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;

OnCreateSession に関する注意

OnCreateSession イベントを使うと、セッションインスタンスを自分で返すことができますが、そのインスタンスにセッション ID は割り当てられません。単純なコンストラクタでセッションを作成すると、セッション ID とセッションクッキーは空になり、次のリクエストでそのセッションを二度と見つけられなくなります。代わりに SessionClass を使ってください。こちらの方がシンプルで、すべてを自動的に処理してくれます。

また、OnCreateSession 内で SessionList.CreateUniqueSession を呼び出さないでください。このメソッドはすでに新しいセッションをリストに追加しており、ハンドラーが戻るときにサーバーが再度追加するため、同じセッションがリストに二重に登録されてしまいます。