I have the following actor as defined below meant to "login" a user.
object AuthenticationActor {
def props = Props[AuthenticationActor]
case class LoginUser(id: UUID)
}
class AuthenticationActor #Inject()(cache: CacheApi, userService: UserService) extends Actor{
import AuthenticationActor._
def receive = {
case LoginEmployee(id: UUID) => {
userService.getUserById(id).foreach {
case Some(e) => {
println("Logged user in")
val sessionId = UUID.randomUUID()
cache.set(sessionId.toString, e)
sender() ! Some(e, sessionId)
}
case None => println("No user was found")
}
}
}
}
Note: userService.getUserById returns a Future[Option[User]]
And the following very simplistic API cal to it
class EmployeeController #Inject()(#Named("authentication-actor") authActor: ActorRef)(implicit ec: ExecutionContext) extends Controller {
override implicit val timeout: Timeout = 5.seconds
def login(id: UUID) = Action.async { implicit request =>
(authActor ? LoginUser(id)).mapTo[Option[(User, UUID)]].map {
case Some(authInfo) => Ok("Authenticated").withSession(request.session + ("auth" -> authInfo._2.toString))
case None => Forbidden("Not Authenticated")
}
}
}
Both println calls will execute, but login call will always fail saying that the ask has time out. Any suggestions?
When you do such thing (accessing sender within Futures callback) you need to store sender in a val in outter scope when you receive request because it is very likely change before Future completes.
def receive = {
case LoginEmployee(id: UUID) => {
val recipient = sender
userService.getUserById(id).foreach {
case Some(e) => {
...
recipient ! Some(e, sessionId)
}
...
}
}
}
You also never send a result when user wasn't found.
What you actually should do here is pipe the Future result to the sender
def receive = {
case LoginEmployee(id: UUID) => {
userService.getUserById(id) map { _.map { e =>
val sessionId = UUID.randomUUID()
cache.set(sessionId.toString, e)
(e, sessionId)
}
} pipeTo sender
}
}
or with prints
def receive = {
case LoginEmployee(id: UUID) => {
userService.getUserById(id) map {
case Some(e) =>
println("logged user in")
val sessionId = UUID.randomUUID()
cache.set(sessionId.toString, e)
Some(e, sessionId)
case None =>
println("user not found")
None
} pipeTo sender
}
}
Related
I want to refactor by update action below to look a little more readable and also handle the failure case better
The userService has the following functions:
class UserService {
def getUserByUsername: Future[Option[Int]] // which is the UserId
def getUserById: Future[User]
}
My action looks like:
def update(userId: Int) = Action.async { implicit request =>
request.body.validate[User] match {
case JsSuccess(user, _) => {
userService.getUserByUsername(user.username).map { userId =>
userService.getUserById(userId.get).map { existingUser =>
userService.update(user.username)
Ok
}
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
How do I handle the situation where getUserByUsername returns a None?
Would this look cleaner if it was in a for comprehension, is it better style?
You have some missing data in your questions such as case classes for the User model, userService class.
also better to attach the original function.
Anyways, I will do something as follows:
def update(userId: Int) = Action { implicit request =>
request.body.validate[User] match {
case JsSuccess(user: User, _) => {
val userId = getUserByUsername(user.username)
userId match {
case Some(userId) => {
for {
_ <- userService.getUserById(userId)
_ <- userService.update(user.username)
} yield Ok
}.recover {
case t: Throwable =>
Metrics.errOnUpdate.increment() // Some metric to monitor
logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error
InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
}
case None => Future.successful(NotFound(s"No such user with ${user.username}"))
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
Note: If .update returns Future, you actually not waiting to update before returning Ok to the user, thus, if its fails, its still returns Ok.
To fix that, use flatMap and then map the value of update response.
You can also separate the recovering for the getUserById and update if you prefer.
Edit:
def update(userId: Int) = Action { implicit request =>
request.body.validate[User] match {
case JsSuccess(user: User, _) => {
getUserByUsername(user.username).flatMap {
case Some(userId) => for {
_ <- userService.getUserById(userId)
_ <- userService.update(user.username)
} yield Ok
case None => Future.successful(NotFound(s"No such user with ${user.username}"))
}
}.recover {
case t: Throwable =>
Metrics.errOnUpdate.increment() // Some metric to monitor
logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error
InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
First, you probably need to use Option.fold:
#inline final def fold[B](ifEmpty: => B)(f: A => B)
Then you can do something like this:
def update(userId: Int) = Action.async { implicit request =>
def handleJsonErrors(errors: Seq[(JsPath, collection.Seq[JsonValidationError])]): Future[Result] = ???
def updateUser(userWithoutId: User): Future[Result] = {
for {
userId <- userService.getUserByUsername(userWithoutId.username)
_ <- userService.getUserById(userId.get)
_ <- userService.update(userWithoutId.username)
} yield {
Ok
}
}
request.body.asJson.fold {
Future.successful(BadRequest("Bad json"))
} {
_.validate[User].fold(handleJsonErrors, updateUser).recover {
case NonFatal(ex) =>
InternalServerError
}
}
}
I am trying to get the time spend for each request to send it to a remote system in my Play application.
The case class TimeAction which works well with asynchronous action doesn't work with synchronous action
//Helper
case class TimeAction(label: String] = Map()) = {
def logging[A](action: Action[A])(implicit ec: ExecutionContext) =
Action.async(action.parser) { request =>
MetricLogger.timeFuture(s"controllers.action.$label") {
action(request)
}
}
}
//controller
def test =
TimeAction("toto.test.sync").logging {
Action { _ =>
Thread.sleep(4000)
Ok
}
}
// expected output 4003 milliseconds but got 3 milliseconds
def testAsync =
TimeAction("toto.test.async").logging {
Action.async { _ =>
Future {
Thread.sleep(4000)
Ok
}
}
}
// go 4003 milliseconds as expected
//calculate time elapsed during a future
def timeImpl[T](f: Future[T],
onError: (Duration, Throwable) => Unit,
onSuccess: (Duration, T) => Unit)(implicit ec: ExecutionContext): Future[T] = {
val start = System.nanoTime
f.onComplete {
case Success(d) => onSuccess(calculateTimeDiff(start, System.nanoTime()), d)
case Failure(e) => onError(calculateTimeDiff(start, System.nanoTime()), e)
}
f
}
I suppose the action is executed before it got to my TimeAction and is only wrapped in a instant Future. Do you know where ? What am I missing ?
I am using Play 2.3
I have Play websockets action:
def socket = WebSocket.acceptWithActor[String, Array[Byte]] { request => out =>
Props(new WebSocketInActor(out))
}
Generally I need to send to browser large raw arrays of data. But sometimes I need to send some small string data. In browser I can detect is data in text format or raw ArrayBuffer.
If I create actor that sends String, I can send string messages, If I create actor that sends with Array[Byte], I can send raw arrays. Both situations I don't need to change client code. So, how can I force Play to use both sending methods with one out actor?
Ah, those answers that comes just after you post question on SO. Looking through reference and sourcecode, I found that there is mixedFrame FrameFromatter: https://github.com/playframework/playframework/blob/2.4.x/framework/src/play/src/main/scala/play/api/mvc/WebSocket.scala#L75
So you just need to say that you will respond with Either[String, Array[Byte]] and if you want to send string use Left(somestring) or else use Right[somearray].
class WebSocketInActor(out: ActorRef) extends Actor {
override def preStart() = {
println("User connected")
val s = "Hello"
out ! Left(s)
out ! Right(s.getBytes("utf8"))
}
override def postStop() = {
println("User discconnected")
}
def receive = {
case msg: String => {
}
case _ =>
}
}
def socket = WebSocket.acceptWithActor[String, Either[String, Array[Byte]]] { request => out =>
Props(new WebSocketInActor(out))
}
UPDATE:
Or you can go one step further and create your own frame formatter
sealed trait WSMessage
case class StringMessage(s: String) extends WSMessage
case class BinaryMessage(a: Array[Byte]) extends WSMessage
case class JsonMessage(js: JsValue) extends WSMessage
implicit object myFrameFormatter extends BasicFrameFormatter[WSMessage] {
private val textFrameClass = classOf[TextFrame]
private val binaryFrameClass = classOf[BinaryFrame]
def toFrame(message: WSMessage): BasicFrame = message match {
case StringMessage(s) => TextFrame(s)
case BinaryMessage(a) => BinaryFrame(a)
case JsonMessage(js) => TextFrame(Json.stringify(js))
}
def fromFrame(frame: BasicFrame): WSMessage = frame match {
case TextFrame(s) => StringMessage(s)
case BinaryFrame(a) => BinaryMessage(a)
}
def fromFrameDefined(clazz: Class[_]): Boolean = clazz match {
case `textFrameClass` => true
case `binaryFrameClass` => true
case _ => false // shouldn't be reachable
}
}
class WebSocketInActor(out: ActorRef) extends Actor {
override def preStart() = {
println("User connected")
val s = "Hello"
val a:Array[Byte] = Array(100, 50, 30).map(_.toByte)
out ! StringMessage(s)
out ! JsonMessage(Json.obj("txt" -> s, "array" -> a))
out ! BinaryMessage(a)
}
override def postStop() = {
println("User discconnected")
}
def receive = {
case msg: String => {
}
case _ =>
}
}
def socket = WebSocket.acceptWithActor[String, WSMessage] { request => out =>
Props(new WebSocketInActor(out))
}
I am pretty new using PersistentActor ,
when I try to call updateState from a future onComplete, fails , nothing happanes , tried to debug it and I do get to the persist call but not into the updateState
trait Event
case class Cmd(data: String)
case class Evt(data: String) extends Event
class BarActor extends PersistentActor{
implicit val system = context.system
implicit val executionContext = system.dispatcher
def updateState(event: Evt): Unit ={
println("Updating state")
state = state.updated(event)
sender() ! state
}
def timeout(implicit ec: ExecutionContext) =
akka.pattern.after(duration = 2 seconds, using = system.scheduler)(Future.failed(new TimeoutException("Got timed out!")))
val receiveCommand: Receive = {
case Cmd(data) =>
def anotherFuture(i: Int)(implicit system: ActorSystem) = {
val realF = Future {
i % 2 match {
case 0 =>
Thread.sleep(100)
case _ =>
Thread.sleep(500)
}
i
}
Future.firstCompletedOf(Seq(realF, timeout))
.recover {
case _ => -1
}
}
val res = (1 to 10).map(anotherFuture(_))
val list = Future.sequence(res)
list.onComplete{
case _ =>
persist(Evt("testing"))(updateState)
}
}
}
You could try this:
list.onComplete {
case _ => self ! Evt("testing")
}
And add this to receiveCommand
case evt: Evt =>
persist(Evt("testing"))(updateStates)
I want TestHttp class to be able to receive http requests or messages from other actors. How can I do it?
Code:
object Main extends App with SimpleRoutingApp {
implicit val system = ActorSystem("system")
import system.dispatcher
implicit val timeout = Timeout(240.seconds)
startServer(interface = "localhost", port = 3000) {
get {
path("register" / IntNumber) { n =>
respondWithMediaType(MediaTypes.`application/json`) { ctx =>
val future = IO(Http) ? Bind(system.actorOf(Props[TestHttp]), interface = "localhost", port = 3000 + n)
future onSuccess {
case Http.Bound(msg) => ctx.complete(s"Ok:"+msg)
case _ => ctx.complete("...")
}
}
} // : Route == RequestContext => Unit
} // : Route
}
}
trait TestHttpService extends HttpService {
val oneRoute = {
path("test") {
complete("test")
}
}
}
class TestHttp extends Actor with TestHttpService {
def actorRefFactory = context
val sealedRoute = sealRoute(oneRoute)
def receive = {
// case HttpRequest(GET, Uri.Path("/ping"), _, _, _) => //not working
// sender ! HttpResponse(entity = "PONG")
case ctx: RequestContext => sealedRoute(ctx) //not working
}
// def receive = runRoute(oneRoute) //it works
}
Actor.Receive is a partial function that takes Any value and returns Unit (PartialFunction[Any, Unit]), so you can do it by regular PF composition.
HttpService.runRoute returns Actor.Receive (see https://github.com/spray/spray/blob/master/spray-routing/src/main/scala/spray/routing/HttpService.scala#L31)
So, your solution would be:
class TestHttp extends Actor with TestHttpService {
def actorRefFactory = context
val sealedRoute = sealRoute(oneRoute)
def receive = {
case s: String => println(s"Just got string $s")
} orElse runRoute(oneRoute)
}