Fan

 

class

web::WebSession

sys::Obj
  web::WebSession
//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   19 Mar 08  Brian Frank  Creation
//

**
** WebSession provides a name/value map associated with
** a specific browser "connection" to the web server.  Any
** values stored in a WebSession must be serializable.
** Get the current WebSession via `WebReq.session`.
**
** See [docLib::Web]`docLib::Web#sessions`
**
@serializable
class WebSession
{

  **
  ** Internal make
  **
  internal new make(Str? id := null)
  {
    if (id != null) this.id = id
  }

  **
  ** Get the unique id used to identify this session.
  **
  const Str id

  **
  ** Return `id`.
  **
  override Str toStr() { return id }

  **
  ** Convenience for 'map.get(name, def)'.
  **
  Obj? get(Str name, Obj? def := null) { return map.get(name, def) }

  **
  ** Convenience for 'map.set(name, val)'.
  **
  Void set(Str name, Obj? val) { map[name] = val }

  **
  ** Application name/value pairs which are persisted
  ** between HTTP requests.  The values stored in this
  ** map must be serializable.
  **
  readonly Str:Obj? map := Str:Obj[:]

  **
  ** Delete this web session which clears both the user
  ** agent cookie and the server side session instance.
  ** This method must be called before the WebRes is
  ** committed - if not the server side instance is cleared,
  ** but the user agent cookie will remain uncleared.
  **
  Void delete()
  {
    isDeleted = true
    WebRes res := Thread.locals["web.res"]
    res.cookies.add(Cookie { name="fanws"; value=id; maxAge=0sec })
  }

  internal Bool isDeleted := false
  internal Duration lastAccess

}

**************************************************************************
** WebSessionMgr
**************************************************************************

**
** WebSessionMgr is a pod internal class which is a background
** thread used to manage WebSession storage and cleanup.
**
internal const class WebSessionMgr : Thread
{
  new make() : super(null) {}

  WebSession load(WebReq req)
  {
    WebSession? ws := null

    // try to lookup existing session via cookie
    cookie := req.cookies["fanws"]
    if (cookie != null)
    {
      ws = sendSync(cookie)
    }

    // if we still don't have a session, we need to
    // create one and add the cookie to the response
    if (ws == null)
    {
      ws = sendSync("_new")
      WebRes res := Thread.locals["web.res"]
      res.cookies.add(Cookie { name="fanws"; value=ws.id })
    }

    Thread.locals["web.session"] = ws
    return ws
  }

  Void save()
  {
    try
    {
      WebSession? ws := Thread.locals.remove("web.session")
      if (ws != null) sendAsync(ws)
    }
    catch (Err e)
    {
      WebService.log.error("WebSession save", e)
    }
  }

  override Obj? run()
  {
    sessions := Str:WebSession[:]
    sendLater(13min, "_houseKeeping", true)
    loop(&process(sessions))
    return null
  }

  Obj? process(Str:WebSession sessions, Obj? msg)
  {
    // generate new session
    if (msg === "_new")
    {
      while (true)
      {
        id := Buf.random(12).toHex
        if (sessions[id] != null) continue
        ws := WebSession(id)
        sessions[id] = ws
        return ws
      }
    }

    // for now clean-up old sessions after 24 hours
    if (msg === "_houseKeeping")
    {
      now := Duration.now
      old := sessions.findAll |WebSession s->Bool|
      {
        return now - s.lastAccess > 24hr
      }
      old.each |WebSession s| { sessions.remove(s.id) }
      return null
    }

    // str message is load
    id := msg as Str
    if (id != null)
    {
      return sessions[id]
    }

    // session message is save
    ws := msg as WebSession
    if (ws != null)
    {
      ws.lastAccess = Duration.now
      if (ws.isDeleted)
        sessions.remove(ws.id)
      else
        sessions[ws.id] = ws
      return null
    }

    echo("WebSessionMgr.unknown msg: $msg")
    return null
  }

}