no.kodeworks.kvarg.actor.SessionService.scala Maven / Gradle / Ivy
package no.kodeworks.kvarg.actor
import akka.actor.{ActorRef, ActorRefFactory, Cancellable}
import akka.event.LoggingAdapter
import akka.http.scaladsl.server.Directive1
import akka.http.scaladsl.server.Directives._
import akka.util.Timeout
import no.kodeworks.kvarg.actor.DbService._
import no.kodeworks.kvarg.message.{InitFailure, InitSuccess}
import no.kodeworks.kvarg.model.Session
import no.kodeworks.kvarg.util.AtLeastOnceDelivery.pattern
import no.kodeworks.kvarg.util.{AtLeastOnceDeliveryDefault, IdGen, _}
import no.kodeworks.kvarg.actor.SessionService.{Scavenge, SessionTimeout}
import com.softwaremill.session.SessionOptions._
import com.softwaremill.session._
import shapeless.{::, HNil}
import scala.collection.mutable
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
class SessionService
(
bootService: ActorRef,
dbService: ActorRef,
listeners: List[ActorRef],
sessionConfig: SessionConfig,
timeout: Timeout = 5 seconds
)
extends AtLeastOnceDeliveryDefault {
val sessionMaxAgeMillis = sessionConfig.sessionMaxAgeSeconds.map(1000L *).getOrElse(Long.MaxValue)
implicit def ec = context.dispatcher
val sessions = mutable.Map[String, Long]()
var scavenger: Cancellable = emptyCancellable
override def preStart {
log.info("Born")
if (sessionConfig.sessionMaxAgeSeconds.isEmpty) log.warning("No session max age set")
log.debug(s"Listeners: ${listeners.map(_.path.name).mkString(", ")}")
bootService ! InitSuccess
}
override def receive = (super.receive orElse {
case session: String =>
log.debug("Set or touch session {}", session)
sessions(session) = System.currentTimeMillis
if (1 == sessions.size) scavenge()
case Scavenge =>
scavenge()
case x =>
log.error("Unknown " + x)
})
def scavenge() {
log.debug("Scavenge")
val now = System.currentTimeMillis
val expires = now - sessionMaxAgeMillis
val (tooOld, stillGoingStrong) = sessions.partition(_._2 <= expires)
sessions.retain { case (s, _) => stillGoingStrong.contains(s) }
for {
s <- tooOld.keys
l <- listeners
} l ! SessionTimeout(s)
scavenger.cancel()
if (sessions.nonEmpty) {
val nextTimeout = (sessions.values.min + sessionMaxAgeMillis - now) millis
log.debug("Next scavenge in {} seconds", nextTimeout.toSeconds)
scavenger = context.system.scheduler.scheduleOnce(nextTimeout, self, Scavenge)
}
}
override def postStop {
log.info("Died")
}
}
object SessionService {
type R = Session :: HNil
case object Scavenge
case class SessionTimeout(session: String)
def ensureSession(
sessionManager: SessionManager[String],
sessionService: ActorRef,
ec: ExecutionContext,
ac: ActorRefFactory,
timeout: Timeout = 5 seconds
): Directive1[String] = {
import CsrfDirectives._
import CsrfOptions._
import SessionDirectives._
import SessionOptions._
implicit def _sessionManager = sessionManager
implicit def _ec = ec
implicit def _ac = ac
implicit def to = timeout
val session = SessionService.createSession
//TODO after login, remember to call setNewCsrfToken(checkHeader)
randomTokenCsrfProtection(checkHeader) &
(SessionService.touchRequiredSession |
setSession(oneOff[String], usingCookies, session).tflatMap(_ => provide(session))
).map { session =>
sessionService !! session
session
}
}
def touchRequiredSession(implicit ec: ExecutionContext,
sm: SessionManager[String]
): Directive1[String] =
SessionDirectives.touchRequiredSession(oneOff[String], usingCookies)
def requiredSession(implicit ec: ExecutionContext,
sm: SessionManager[String],
log: LoggingAdapter): Directive1[String] = {
log.debug("Requiring session")
SessionDirectives.requiredSession(oneOff[String], usingCookies)
}
def createSession: String = SessionUtil.randomString(64)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy