{*******************************************************************} { } { ExpressWeb Framework by Developer Express } { Web Sessions } { } { Copyright (c) 2000-2009 Developer Express Inc. } { ALL RIGHTS RESERVED } { } { The entire contents of this file is protected by U.S. and } { International Copyright Laws. Unauthorized reproduction, } { reverse-engineering, and distribution of all or any portion of } { the code contained in this file is strictly prohibited and may } { result in severe civil and criminal penalties and will be } { prosecuted to the maximum extent possible under the law. } { } { RESTRICTIONS } { } { THIS SOURCE CODE AND ALL RESULTING INTERMEDIATE FILES } { (DCU, OBJ, DLL, ETC.) ARE CONFIDENTIAL AND PROPRIETARY TRADE } { SECRETS OF DEVELOPER EXPRESS INC. THE REGISTERED DEVELOPER IS } { LICENSED TO DISTRIBUTE THE EXPRESSWEB FRAMEWORK AND ALL } { ACCOMPANYING VCL CLASSES AS PART OF AN EXECUTABLE WEB } { APPLICATION ONLY. } { } { THE SOURCE CODE CONTAINED WITHIN THIS FILE AND ALL RELATED } { FILES OR ANY PORTION OF ITS CONTENTS SHALL AT NO TIME BE } { COPIED, TRANSFERRED, SOLD, DISTRIBUTED, OR OTHERWISE MADE } { AVAILABLE TO OTHER INDIVIDUALS WITHOUT EXPRESS WRITTEN CONSENT } { AND PERMISSION FROM DEVELOPER EXPRESS INC. } { } { CONSULT THE END USER LICENSE AGREEMENT FOR INFORMATION ON } { ADDITIONAL RESTRICTIONS. } { } {*******************************************************************} unit cxWebSess; interface {$I cxVer.inc} uses Classes, SysUtils, HTTPApp, cxWebIntf, cxWebVariants; type EcxWebSessionsException = class(EWebBrokerException); TcxWebSessionStatus = (wssStarting, wssStarted, wssTerminating, wssTerminated); TcxWebSessionItem = class(TPersistent) private FExpired: Boolean; FID: string; FItems: TcxWebNamedVariants; FLastTouchTime: TDateTime; FStartTime: TDateTime; FStatus: TcxWebSessionStatus; FTerminated: Boolean; FTimeoutMinutes: Integer; function GetExpTime: TDateTime; function GetIsActive: Boolean; procedure SetExpired(Value: Boolean); procedure SetItems(Value: TcxWebNamedVariants); procedure SetTerminated(Value: Boolean); public constructor Create(AID: string); destructor Destroy; override; property Expired: Boolean read FExpired write SetExpired; property ExpTime: TDateTime read GetExpTime; property ID: string read FID; property IsActive: Boolean read GetIsActive; property Items: TcxWebNamedVariants read FItems write SetItems; property LastTouchTime: TDateTime read FLastTouchTime write FLastTouchTime; property StartTime: TDateTime read FStartTime write FStartTime; property Status: TcxWebSessionStatus read FStatus write FStatus; property Terminated: Boolean read FTerminated write SetTerminated; property TimeoutMinutes: Integer read FTimeoutMinutes write FTimeoutMinutes; end; TcxWebSessions = class(TPersistent) private FLock: TMultiReadExclusiveWriteSynchronizer; FMaxSessions: Integer; FSessionItems: TList; function GetCount: Integer; function GetItem(Index: Integer): TcxWebSessionItem; function FindSession(const ASessionID: string): TcxWebSessionItem; function GetActiveCount: Integer; function GetMaxSessions: Integer; procedure ReadLock; procedure ReadUnlock; procedure SetMaxSessions(const Value: Integer); procedure WriteLock; procedure WriteUnlock; protected function GenerateSessionID: string; virtual; public constructor Create; destructor Destroy; override; function ConnectTo(const ASessionID: string): Boolean; function ConnectToActive(const ASessionID: string): Boolean; procedure GetContents(const ASessionID: string; var AContents: TcxWebNamedVariants); function GetContentValue(const ASessionID, AName: string): Variant; function GetIsActive(const ASessionID: string): Boolean; function GetTimeoutMinutes(const ASessionID: string): Integer; procedure SetContentValue(const ASessionID, AName: string; const AValue: Variant); procedure SetTimeoutMinutes(const ASessionID: string; const AValue: Integer); function StartSession(var ASessionID: string): TcxWebSessionItem; function Terminate(const ASessionID: string): Boolean; function CheckStatus: Boolean; procedure NotifyStatusChange(AOwner: TObject); procedure LoadFromStream(Stream: TStream); procedure SaveToStream(Stream: TStream); property ActiveCount: Integer read GetActiveCount; property Count: Integer read GetCount; property Items[Index: Integer]: TcxWebSessionItem read GetItem; property MaxSessions: Integer read GetMaxSessions write SetMaxSessions; end; TcxWebSession = class(TAbstractWebSession) private FSessionsService: IcxWebSessionsService; FSessionID: string; function ReadFromCookie(ARequest: TWebRequest; AResponse: TWebResponse): string; function ReadFromPathInfo(ARequest: TWebRequest; AResponse: TWebResponse): string; procedure WriteToCookie(AResponse: TWebResponse; const AValue: string); protected function GetValue(const AName: string): Variant; override; procedure SetValue(const AName: string; const AValue: Variant); override; function GetTimeoutMinutes: Integer; override; procedure SetTimeoutMinutes(AValue: Integer); override; function GetSessionID: string; override; procedure ReadFrom(ARequest: TWebRequest; AResponse: TWebResponse); virtual; procedure WriteTo(AResponse: TWebResponse; const AValue: string); virtual; public constructor Create(ASessionsService: IcxWebSessionsService; ARequest: TWebRequest; AResponse: TWebResponse); overload; constructor Create(ASessionsService: IcxWebSessionsService; ASessionID: string); overload; procedure UpdateResponse(AResponse: TWebResponse); override; procedure Terminate; override; end; TcxWebErrorSession = class(TAbstractWebSession) private FErrorMessage: string; procedure RaiseError; protected function GetValue(const AName: string): Variant; override; procedure SetValue(const AName: string; const AValue: Variant); override; function GetTimeoutMinutes: Integer; override; procedure SetTimeoutMinutes(AValue: Integer); override; function GetSessionID: string; override; public procedure UpdateResponse(AResponse: TWebResponse); override; procedure Terminate; override; constructor Create(const AMessage: string); end; var cxWebSessions: TcxWebSessions; implementation uses DateUtils, Contnrs, Variants, StrUtils, {$IFDEF VCL} ComObj, {$ENDIF} cxWebUtils, cxWebStrs; const scxSessionCookieName = 'EWOSessionID'; { TcxWebSessionItem } constructor TcxWebSessionItem.Create(AID: string); begin inherited Create; FItems := TcxWebNamedVariants.Create; FID := AID; FStartTime := Now; FLastTouchTime := FStartTime; FTimeoutMinutes := -1; FStatus := wssStarting; end; destructor TcxWebSessionItem.Destroy; begin FItems.Free; inherited; end; function TcxWebSessionItem.GetExpTime: TDateTime; begin if FTimeoutMinutes <> -1 then Result := IncMinute(FLastTouchTime, FTimeoutMinutes) else Result := 0; end; function TcxWebSessionItem.GetIsActive: Boolean; begin Result := not (Expired or Terminated); end; procedure TcxWebSessionItem.SetExpired(Value: Boolean); begin FExpired := Value; if Value and (FStatus <> wssTerminated) then FStatus := wssTerminating; end; procedure TcxWebSessionItem.SetItems(Value: TcxWebNamedVariants); begin FItems.Assign(Value); end; procedure TcxWebSessionItem.SetTerminated(Value: Boolean); begin FTerminated := Value; if Value and (FStatus <> wssTerminated) then FStatus := wssTerminating; end; { TcxWebSessions } constructor TcxWebSessions.Create; begin FLock := TMultiReadExclusiveWriteSynchronizer.Create; FSessionItems := TObjectList.Create(True); FMaxSessions := -1; end; destructor TcxWebSessions.Destroy; begin FreeAndNil(FSessionItems); inherited Destroy; FreeAndNil(FLock); end; function TcxWebSessions.ConnectTo(const ASessionID: string): Boolean; begin ReadLock; try Result := FindSession(ASessionID) <> nil; finally ReadUnlock; end; end; function TcxWebSessions.ConnectToActive(const ASessionID: string): Boolean; var Item: TcxWebSessionItem; begin WriteLock; try Item := FindSession(ASessionID); Result := (Item <> nil) and Item.IsActive; if Result then begin Item.LastTouchTime := Now; end; finally WriteUnlock; end; end; function TcxWebSessions.StartSession(var ASessionID: string): TcxWebSessionItem; begin Result := nil; WriteLock; try if (FMaxSessions <> -1) and (ActiveCount >= FMaxSessions) then raise EcxWebSessionsException.Create(scxWebSessionsLimitExceeded); if (ASessionID = '') or not StringIsGuid(ASessionID) then ASessionID := GenerateSessionID; Result := TcxWebSessionItem.Create(ASessionID); FSessionItems.Add(Result); finally WriteUnlock; end; end; function TcxWebSessions.Terminate(const ASessionID: string): Boolean; var Item: TcxWebSessionItem; begin WriteLock; try Item := FindSession(ASessionID); Result := Item <> nil; if Result then Item.Terminated := True; finally WriteUnlock; end; end; function TcxWebSessions.CheckStatus: Boolean; var Item: TcxWebSessionItem; I: Integer; begin Result := False; WriteLock; try for I := 0 to FSessionItems.Count - 1 do begin Item := TcxWebSessionItem(FSessionItems[I]); if Item.IsActive then Item.Expired := (Item.TimeoutMinutes <> -1) and (Item.ExpTime < Now); Result := Result or Item.Expired or (Item.Status = wssStarting) or (Item.Status = wssTerminating); end; finally WriteUnlock; end; end; procedure TcxWebSessions.NotifyStatusChange(AOwner: TObject); var I: Integer; Item: TcxWebSessionItem; SessionsNotify: IcxWebSessionsNotify; begin Supports(AOwner, IcxWebSessionsNotify, SessionsNotify); WriteLock; try for I := 0 to FSessionItems.Count - 1 do begin Item := TcxWebSessionItem(FSessionItems[I]); if Item.IsActive then Item.Expired := (Item.TimeoutMinutes <> -1) and (Item.ExpTime < Now); if Item.Status = wssStarting then begin if SessionsNotify <> nil then SessionsNotify.SessionStarted(Item.ID); Item.Status := wssStarted; end; if Item.Status = wssTerminating then begin if SessionsNotify <> nil then SessionsNotify.SessionTerminated(Item.ID); Item.Status := wssTerminated; end; end; for I := FSessionItems.Count - 1 downto 0 do begin Item := TcxWebSessionItem(FSessionItems[I]); if (not Item.IsActive) and (Item.Status <> wssStarted) then FSessionItems.Delete(I); end; finally WriteUnlock; end; end; procedure TcxWebSessions.LoadFromStream(Stream: TStream); var I, L, Count: Integer; S: string; Item: TcxWebSessionItem; begin ReadLock; try FSessionItems.Clear; Stream.ReadBuffer(Count, SizeOf(Count)); for I := 0 to Count - 1 do begin Stream.ReadBuffer(L, SizeOf(L)); SetLength(S, L); Stream.ReadBuffer(S[1], L); Item := TcxWebSessionItem.Create(S); Stream.ReadBuffer(Item.FStartTime, SizeOf(Item.StartTime)); Stream.ReadBuffer(Item.FLastTouchTime, SizeOf(Item.LastTouchTime)); Stream.ReadBuffer(Item.FExpired, SizeOf(Item.Expired)); Stream.ReadBuffer(Item.FTimeoutMinutes, SizeOf(Item.TimeoutMinutes)); Stream.ReadBuffer(Item.FTerminated, SizeOf(Item.Terminated)); Stream.ReadBuffer(Item.FStatus, SizeOf(Item.Status)); Item.Items.LoadFromStream(Stream); FSessionItems.Add(Item); end; finally ReadUnlock; end; end; procedure TcxWebSessions.SaveToStream(Stream: TStream); var I, L: Integer; Item: TcxWebSessionItem; begin ReadLock; try Stream.WriteBuffer(FSessionItems.Count, SizeOf(FSessionItems.Count)); for I := 0 to FSessionItems.Count - 1 do begin Item := TcxWebSessionItem(FSessionItems[I]); L := Length(Item.ID); Stream.WriteBuffer(L, SizeOf(L)); Stream.WriteBuffer(Item.ID[1], L); Stream.WriteBuffer(Item.StartTime, SizeOf(Item.StartTime)); Stream.WriteBuffer(Item.LastTouchTime, SizeOf(Item.LastTouchTime)); Stream.WriteBuffer(Item.Expired, SizeOf(Item.Expired)); Stream.WriteBuffer(Item.TimeoutMinutes, SizeOf(Item.TimeoutMinutes)); Stream.WriteBuffer(Item.Terminated, SizeOf(Item.Terminated)); Stream.WriteBuffer(Item.Status, SizeOf(Item.Status)); Item.Items.SaveToStream(Stream); end; finally ReadUnlock; end; end; procedure TcxWebSessions.GetContents(const ASessionID: string; var AContents: TcxWebNamedVariants); var Item: TcxWebSessionItem; begin WriteLock; try Item := FindSession(ASessionID); if Item <> nil then AContents.Assign(Item.Items) else AContents.Clear; finally WriteUnlock; end; end; function TcxWebSessions.GetContentValue(const ASessionID, AName: string): Variant; var Item: TcxWebSessionItem; begin WriteLock; try Item := FindSession(ASessionID); if Item <> nil then Result := Item.Items.Values[AName] else Result := Unassigned; finally WriteUnlock; end; end; procedure TcxWebSessions.SetContentValue(const ASessionID, AName: string; const AValue: Variant); var Item: TcxWebSessionItem; begin WriteLock; try Item := FindSession(ASessionID); if Item <> nil then Item.Items.Values[AName] := AValue; finally WriteUnlock; end; end; function TcxWebSessions.GetIsActive(const ASessionID: string): Boolean; var Item: TcxWebSessionItem; begin ReadLock; try Item := FindSession(ASessionID); Result := (Item <> nil) and Item.IsActive; finally ReadUnlock; end; end; function TcxWebSessions.GetTimeoutMinutes(const ASessionID: string): Integer; var Item: TcxWebSessionItem; begin ReadLock; try Item := FindSession(ASessionID); if Item <> nil then Result := Item.TimeoutMinutes else Result := -1; finally ReadUnlock; end; end; procedure TcxWebSessions.SetTimeoutMinutes(const ASessionID: string; const AValue: Integer); var Item: TcxWebSessionItem; begin WriteLock; try Item := FindSession(ASessionID); if Item <> nil then Item.TimeoutMinutes := AValue; finally WriteUnlock; end; end; function TcxWebSessions.GenerateSessionID: string; var AGUID: TGUID; {$IFDEF VCL} hr: HRESULT; {$ENDIF} begin {$IFDEF VCL} hr := CreateGUID(AGUID); OLECHECK(hr); {$ELSE} CreateGUID(AGUID); {$ENDIF} Result := GUIDToString(AGUID); end; function TcxWebSessions.FindSession(const ASessionID: string): TcxWebSessionItem; var I: Integer; begin for I := 0 to FSessionItems.Count - 1 do begin Result := TcxWebSessionItem(FSessionItems[I]); if (Result.ID = ASessionID) and (Result.Status <> wssTerminated) then Exit; end; Result := nil; end; function TcxWebSessions.GetActiveCount: Integer; var I: Integer; begin Result := 0; ReadLock; try for I := 0 to FSessionItems.Count - 1 do if TcxWebSessionItem(FSessionItems[I]).IsActive then Inc(Result); finally ReadUnlock; end; end; function TcxWebSessions.GetMaxSessions: Integer; begin ReadLock; try Result := FMaxSessions; finally ReadUnlock; end; end; procedure TcxWebSessions.ReadLock; begin FLock.BeginRead; end; procedure TcxWebSessions.ReadUnlock; begin FLock.EndRead; end; procedure TcxWebSessions.SetMaxSessions(const Value: Integer); begin if GetMaxSessions = Value then Exit; WriteLock; try FMaxSessions := Value; finally WriteUnlock; end; end; procedure TcxWebSessions.WriteLock; begin FLock.BeginWrite; end; procedure TcxWebSessions.WriteUnlock; begin FLock.EndWrite; end; function TcxWebSessions.GetCount: Integer; begin Result := FSessionItems.Count; end; function TcxWebSessions.GetItem(Index: Integer): TcxWebSessionItem; begin Result := TcxWebSessionItem(FSessionItems.Items[Index]); end; { TcxWebSession } constructor TcxWebSession.Create(ASessionsService: IcxWebSessionsService; ARequest: TWebRequest; AResponse: TWebResponse); begin inherited Create; FSessionsService := ASessionsService; ReadFrom(ARequest, AResponse) end; constructor TcxWebSession.Create(ASessionsService: IcxWebSessionsService; ASessionID: string); begin inherited Create; FSessionsService := ASessionsService; if cxWebSessions.ConnectTo(ASessionID) then FSessionID := ASessionID else FSessionID := ''; end; function TcxWebSession.GetSessionID: string; begin Result := FSessionID; end; function TcxWebSession.GetTimeoutMinutes: Integer; begin if FSessionID <> '' then Result := cxWebSessions.GetTimeoutMinutes(FSessionID) else Result := 0; end; function TcxWebSession.GetValue(const AName: string): Variant; begin if FSessionID <> '' then Result := cxWebSessions.GetContentValue(FSessionID, AName) else VarClear(Result); end; procedure TcxWebSession.SetTimeoutMinutes(AValue: Integer); begin if FSessionID = '' then Exit; cxWebSessions.SetTimeoutMinutes(FSessionID, AValue); end; procedure TcxWebSession.SetValue(const AName: string; const AValue: Variant); begin if FSessionID = '' then Exit; cxWebSessions.SetContentValue(FSessionID, AName, AValue); end; procedure TcxWebSession.Terminate; begin if FSessionID = '' then Exit; cxWebSessions.Terminate(FSessionID); end; procedure TcxWebSession.UpdateResponse(AResponse: TWebResponse); begin if FSessionID = '' then Exit; if cxWebSessions.GetIsActive(FSessionID) then WriteTo(AResponse, FSessionID) else WriteTo(AResponse, ''); end; procedure TcxWebSession.ReadFrom(ARequest: TWebRequest; AResponse: TWebResponse); var Connected: Boolean; begin if FSessionsService = nil then Exit; if FSessionsService.UseCookie then FSessionID := ReadFromCookie(ARequest, AResponse) else FSessionID := ReadFromPathInfo(ARequest, AResponse); Connected := False; if FSessionID <> '' then Connected := cxWebSessions.ConnectToActive(FSessionID); if not Connected then cxWebSessions.StartSession(FSessionID).TimeoutMinutes := FSessionsService.DefaultTimeout; end; function TcxWebSession.ReadFromCookie(ARequest: TWebRequest; AResponse: TWebResponse): string; begin Result := ARequest.CookieFields.Values[scxSessionCookieName]; WriteToCookie(AResponse, ''); end; function TcxWebSession.ReadFromPathInfo(ARequest: TWebRequest; AResponse: TWebResponse): string; begin Result := ARequest.InternalPathInfo; if AnsiStartsText('/', Result) then Delete(Result, 1, 1); Delete(Result, Pos('/', Result), MaxInt); if not StringIsGuid(Result) then Result := ''; end; procedure TcxWebSession.WriteTo(AResponse: TWebResponse; const AValue: string); begin if (FSessionsService <> nil) and FSessionsService.UseCookie then WriteToCookie(AResponse, AValue); end; procedure TcxWebSession.WriteToCookie(AResponse: TWebResponse; const AValue: string); var I: Integer; begin for I := 0 to AResponse.Cookies.Count - 1 do // Update existing with AResponse.Cookies[I] do if SameText(Name, scxSessionCookieName) then begin Value := AValue; Exit; end; // Add new with AResponse.Cookies.Add do begin Name := scxSessionCookieName; Path := AResponse.HTTPRequest.InternalScriptName; Value := AValue; Expires := -1; end; end; { TcxWebErrorSession } constructor TcxWebErrorSession.Create(const AMessage: string); begin inherited Create; FErrorMessage := AMessage; end; function TcxWebErrorSession.GetSessionID: string; begin Result := ''; RaiseError; end; function TcxWebErrorSession.GetTimeoutMinutes: Integer; begin Result := 0; RaiseError; end; function TcxWebErrorSession.GetValue(const AName: string): Variant; begin VarClear(Result); RaiseError; end; procedure TcxWebErrorSession.RaiseError; begin raise EcxWebSessionsException.Create(FErrorMessage); end; procedure TcxWebErrorSession.SetTimeoutMinutes(AValue: Integer); begin RaiseError; end; procedure TcxWebErrorSession.SetValue(const AName: string; const AValue: Variant); begin RaiseError; end; procedure TcxWebErrorSession.Terminate; begin RaiseError; end; procedure TcxWebErrorSession.UpdateResponse(AResponse: TWebResponse); begin // do nothing end; initialization RegisterClasses([TcxWebSessionItem, TcxWebSessions]); if cxWebSessions = nil then cxWebSessions := TcxWebSessions.Create; finalization FreeAndNil(cxWebSessions); end.