Has anybody written a session example for Lazarus web? lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- egsuh · 2023-07-23 08:28 I know that there are examples on the FPC wiki written by Michael, but this is little bit tricky to use. Has anybody written a session management codes? ---------------------------------------------------------------------- Thaddy · 2023-07-23 08:49 There is an example in the fcl-web directory examples directories ---------------------------------------------------------------------- egsuh · 2023-07-23 10:18 Thank you for your information. I could find the example files and run it. But the unit websession says, unit websession deprecated; Any problem with this or are there any new approaches? ---------------------------------------------------------------------- Warfley · 2023-07-23 10:42 I usually build a simplified session system myself. Generate a random session ID, set it as a cookie and store it in a database together with all the session data you want to associate with it. If you use a database anyway this is not too much to add the session to it. If you need a bit more security you can add additional data to the session data, e.g. form a hash over Session ID (Cookie) + IP + UserAgent, etc. But if it's not such a critical system a simple cookie should suffice ---------------------------------------------------------------------- egsuh · 2023-07-23 23:56 Yes. I don’t think treating session myself is too difficult. Just checking ways to use existing ones. Using ini files does not seem too bad. Or I may change writing/reading part of inissession unit. ---------------------------------------------------------------------- Leledumbo · 2023-07-24 05:15 If you use fpWeb, go here. Otherwise, here's a purer fcl-web example: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$mode objfpc} uses  classes,  fphttpapp,  httpdefs,  httproute,  fphttp,  iniwebsession; var  LSession: TCustomSession; procedure Session(ARequest: TRequest; AResponse: TResponse);var  LSessionValue: String;begin  if not Assigned(LSession) then begin    LSession := SessionFactory.CreateSession(ARequest);    if Assigned(LSession) then      LSession.InitSession(ARequest, nil, nil);  end;   if UpCase(ARequest.Method) = 'POST' then begin    if ARequest.ContentFields.Values['delete'] <> '' then begin // workaround since HTML forms don't accept methods other than GET or POST      SessionFactory.DoneSession(LSession);    end else if ARequest.ContentFields.Values['delete_value'] <> '' then begin      LSession.RemoveVariable('SessionValue')    end else      LSession.Variables['SessionValue'] := ARequest.ContentFields.Values['val'];  end;   AResponse.SendRedirect('/');end; procedure Home(ARequest: TRequest; AResponse: TResponse);var  LSessionValue: String;begin  if not Assigned(LSession) then begin    LSession := SessionFactory.CreateSession(ARequest);    if Assigned(LSession) then      LSession.InitSession(ARequest, nil, nil); // the nils are event handlers, which is not needed in this example  end;   LSessionValue := LSession.Variables['SessionValue'];  if LSessionValue = '' then    LSessionValue := '

You have no session value stored on this server

'  else    LSessionValue := '

Your session value: ' + LSessionValue + '

';  AResponse.Content := '

Your session ID: ' + LSession.SessionID + '

'                    + LSessionValue                   + '
'                    + '
'                    + '
'                    ;end; begin  HTTPRouter.RegisterRoute('/',@Home);  HTTPRouter.RegisterRoute('/session',@Session);  Application.Port := 9000;  Application.Initialize;  Application.Run;end. ---------------------------------------------------------------------- egsuh · 2023-07-25 11:03 I see the overall logic is quite simple. But there are difficulties in details. I made a simple program myself. bdbSessions is a TBufDataSet. I'd like to save session data in this file. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TSessionModule.DataModuleCreate(Sender: TObject);begin   bdbSessions.LoadFromFile;   bdbSessions.Active := True;   TimeOut := 5;  // seconds, for testing   Randomize;   OutText := '';end; procedure TSessionModule.DataModuleDestroy(Sender: TObject);begin   bdbSessions.SaveToFile;   bdbSessions.Close;end; procedure TSessionModule.DataModuleBeforeRequest(Sender: TObject;   ARequest: TRequest);var   V : string;   C: TCookie;   T: TDateTime; begin   V := trim(ARequest.CookieFields.Values[SessionIDName]);   if V <> '' then begin      if bdbSessions.Locate('SessionID', VarArrayOf([V]), []) then      begin         LA := bdbSessions.FieldByName('LastAccess').AsDateTime;         TimeOut := bdbSessions.FieldByName('TimeOut').AsInteger;         Gap := SecondsBetween(Now, LA);          if Gap >= TimeOut then         begin            bdbSessions.Delete;            bdbSessions.Post;          V := '';         end         else begin  // In Session --- Update current time            bdbSessions.Edit;            bdbSessions.FieldByName('LastAccess').AsDateTime := Now;            bdbSessions.Post;         end;      end      else V := '';   end;    if V = '' then begin       Randomize;       SessionID := IntToStr(Random(10000)); // CreateNewSessionID;       with bdbsessions do begin // Save New Session          Insert;          FieldByName('SessionId').AsString := SessionID;          FieldByName('LastAccess').AsDateTime := Now;          FieldByName('TimeOut').AsInteger := TimeOut;          Post;       end;       // TODO:  Process New Session   end   else begin      SessionID := V;      // TODO:  ProcessInSession;   end;end; procedure TSessionModule.DataModuleRequest(Sender: TObject; ARequest: TRequest;  AResponse: TResponse; var Handled: Boolean);var   C: TCookie;begin   // TimeOut := 60; // 1 minute    AResponse.Cookies.Clear;   with AResponse.Cookies.Add do begin      Name := SessionIDName;      Value:= SessionID;      SameSite:= ssStrict;      Expires:= Now + (TimeOut * OneSecond);   end;    AResponse.Content := Format('SessionID:%s, LastAccess:%s, Time gap: %d seconds',                             [SessionID, DateTimeToStr(LA), Gap]);   Handled := True;end;  Each module does not have any problem when run in Windows application mode, but difficult to check in CGI mode. Having difficulty in reading BufDataset file, and LastAccess time does not seem to be read correctly. But generally a little fine tuning would do, I expect. ---------------------------------------------------------------------- Leledumbo · 2023-07-25 12:02 Quote from: egsuh on July 25, 2023, 01:03:15 pmbdbSessions is a TBufDataSet. I'd like to save session data in this file. This, as well as other approaches, are indeed possible. Since session is no weirder than associating a cookie as a key with a bunch of items you save on the server, only creativity seems to be the limitation, as well as providing a way to CRUD both the key and data. Like iniwebsession implements websession interface, you can make something like bufdatasetwebsession and use TBufDataSet as the backend storage instead of an ini file, the remaining code I wrote doesn't have to change apart from the iniwebsession in uses clause. The way you do it without abstraction ties the implementation making it hard to switch to a different backend, but of course, you choose your own way. I object using any db as a session storage, as it's slow as hell. I sped up my current company's application when I joined them by moving the session handling from db to redis. This increases the rps significantly as redis does everything in memory, and session, by definition is a perfect entity to be stored in memory. It should always be OK for a session to be expired even before it's determined time. If it matters, something is not right in the application design. ---------------------------------------------------------------------- egsuh · 2023-07-26 02:09 Quote redis does everything in memory, and session, by definition is a perfect entity to be stored in memory. But can I use Redis in FPC/Lazarus? Actually I'm having some trouble with BufDataSet(writing to and reading from file). Not sure whether the table structure is stored securely.  Instead I'm thinking using SQLite.. but this requires another installation of something, however simple it is. ---------------------------------------------------------------------- PierceNg · 2023-07-26 02:44 Quote from: egsuh on July 26, 2023, 04:09:56 amQuote redis does everything in memory, and session, by definition is a perfect entity to be stored in memory. But can I use Redis in FPC/Lazarus? Actually I'm having some trouble with BufDataSet(writing to and reading from file). Not sure whether the table structure is stored securely.  Instead I'm thinking using SQLite.. but this requires another installation of something, however simple it is. There are several recent-ish Redis for Delphi repos on Github. Searching for 'redis fpc pascal' yields one result: https://github.com/ik5/redis_client.fpc, 12 years old, but if it works it works. SQLite is always worth learning for a programmer. Ultimately, depends on your objectives: If you are doing this to learn and improve your skills, then try as many things as possible. If you need to get something done, then using some existing framework should be quicker. OTOH usually the more comprehensive and powerful a framework is, the longer it takes to learn to use it well. ---------------------------------------------------------------------- egsuh · 2023-07-26 03:22 Definitely I’m not learning anything. ---------------------------------------------------------------------- Leledumbo · 2023-07-26 04:36 Quote from: egsuh on July 26, 2023, 04:09:56 amBut can I use Redis in FPC/Lazarus? There are existing clients, but the relatively new one sucks in licensing. I'm currently writing a new one I will contribute and will use the same license as other core FPC team contributed units, which should be LGPL with static linking exception. However, if you don't care about licensing, the one I linked will do (just needs a little command line parameter -k'-l' to link to the library it wraps, as all of the external declarations didn't state the library name). ---------------------------------------------------------------------- Thaddy · 2023-07-26 08:45 I wonder why Michael deprecated the unit? It may be just because new features were added and he did not have the time to rewrite that example. I can't see anything wrong with it at first glance. ---------------------------------------------------------------------- Leledumbo · 2023-07-26 08:53 Quote from: Thaddy on July 26, 2023, 10:45:41 amI wonder why Michael deprecated the unit? It may be just because new features were added and he did not have the time to rewrite that example. I can't see anything wrong with it at first glance. No idea, the commit log is as mysterious as it can be: Quote$ git blame websession.pp ... 9214ef8c961 packages/fcl-web/src/base/websession.pp (michael 2011-05-13 12:08:59 +0000  14) unit websession deprecated; ... $ git log websession.pp commit 9214ef8c9616982d36857365d4994756d510f049 Author: michael Date:   Fri May 13 12:08:59 2011 +0000     * Reworked session management.         git-svn-id: trunk@17442 - Perhaps this unit was an implementation, but now it's an abstraction instead, where implementations are provided by other units such as iniwebsession. ---------------------------------------------------------------------- Thaddy · 2023-07-26 09:14 Same observation as is clear from my answer. Only Michael can provide the correct answer. ---------------------------------------------------------------------- egsuh · 2023-07-26 09:15 I'm looking into unit iniwebsession, and SQLwebsession long ago. For me at least, the difficulty lies in the retrieving and saving of session statuses. So, I'm thinking over exposing methods of CRUD of session records --- let users write them. For example, Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type    TSessionCreate = function (SessionID:string; TimeOut:Integer): boolean of object;    TSessionUpdate = function (SessionID:string; SessionStatus: TSessionStatus) : boolean of object;    TSessionDelete = .... ;     TFPWebSession = class (TCustomSession)       // ..................        property CreateSession : TSessionCreate read FCreateSession write FCreateSession;   // will property do this?      property UpdateSession : TSessionUpdate read FUpdateSession write FUpdateSession;        property DeleteSession: .....        ...   end;   Within TFPWebSession, the functions will be used. Uses have to define methods of CRUDing sessions, and assign the methods to the function-type variable. Currently actions of InSession, NewSession, etc. Handling each with case statement would be more intuitive (at least for me). This is just a quick idea --- thinking over how to implement. ---------------------------------------------------------------------- Leledumbo · 2023-07-27 05:04 Quote from: egsuh on July 26, 2023, 11:15:46 amFor me at least, the difficulty lies in the retrieving and saving of session statuses. Hmm? What's so difficult about: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // initialize the session, can even be done once for all requests by overriding TWebHandler.HandleRequest, then override TCustomWebApplication.InitializeWebHandler to return the overriden TWebHandlerLSession := SessionFactory.CreateSession(ARequest);if Assigned(LSession) then  LSession.InitSession(ARequest, nil, nil); // retrieveSomeVar := LSession.Variables['SessionValue']; // storeLSession.Variables['SessionValue'] := SomeValue; // removeLSession.RemoveVariable('SessionValue'); // enforce expirationSessionFactory.DoneSession(LSession); All are one liners. Quote from: egsuh on July 26, 2023, 11:15:46 amSo, I'm thinking over exposing methods of CRUD of session records --- let users write them. For example, This is just a non-OOP style overriding, I don't see it as any easier than subclassing an abstract class.