How should I use the cache API with futures - scala

I want to use the built in playframework cache API but not sure how to cache this web service request when it has futures.
def getUser(username: String): Future[Option[User]] = {
ws.url("...").get.map { response =>
response.json.validate[User] match {
case JsSuccess(user, _) => Some(user)
case JsError(errors) => {
// log errors
None
}
}
}
}
The cache key will just be the username for now.
How would I use the cache API here?
I wanted to use the getOrElse pattern:
val user: User = cache.getOrElse[User]("item.key") {
User.findById(connectedUser)
}
But the Future and Option is confusing me on how to go about using it.

I would do it like this:
def fromWebservice(username: String): Future[Option[User]] =
ws.url("..").get.map(validate) // validate does the json validate thing
def getUser(username: String): Future[Option[User]] =
cache.getAs[User](username) match {
case Some(x) =>
Future.successful(Some(x))
case None =>
fromWebservice(username).map(_.map { user =>
cache.set(username, user)
x
})
}
As an aside, the Play cache API does not have an API that returns Futures, but they're working on it: https://github.com/playframework/playframework/issues/5912

Related

How to write an asynchronous code that Awaited concisely?

I'm a beginner in Scala.
Please let me know if there is a more concise part in the code below.
To supplement, I'd like to call each Future method synchronously.
◆getUser method:
def getUser: Option[User] = {
Await.ready(
twitterService.getUser(configService.getString(TWITTER_USERNAME_CONF)),
Duration.Inf)
.value
.flatMap(x => Option(x.getOrElse(null)))
}
◆ process method:
def process : Unit =
for {
user <- getUser
} yield {
Await.ready(
twitterService.delete(user.id, configService.getString(TWITTER_SEARCH_KEYWORD)),
Duration.Inf)
.value
.foreach {
case Success(tweets) => tweets.foreach(tweet => println(s"Delete Successfully!!. $tweet"))
case Failure(exception) => println(s"Failed Delete.... Exception:[$exception]")
}
}
I made some assumptions on user and tweet data types but I would rewrite that to:
def maybeDeleteUser(userName: String, maybeUser: Option[User]): Future[String] =
maybeUser match {
case Some(user) =>
twitterService.delete(user.id, configService.getString(TWITTER_SEARCH_KEYWORD)).map {
case Failure(exception) => s"Failed Delete.... Exception:[${exception.getMessage}]"
case Success(tweets) => tweets.map(tweet => s"Delete Successfully!!. $tweet").mkString(System.lineSeparator())
}
case _ => Future.successful(s"Failed to find user $userName")
}
def getStatusLogMessage: Future[String] = {
val userName = configService.getString(TWITTER_USERNAME_CONF)
for {
maybeUser <- twitterService.getUser(configService.getString(TWITTER_USERNAME_CONF))
statusLogMessage <- maybeDeleteUser(userName, maybeUser)
} yield statusLogMessage
}
def process: Unit = {
val message = Await.result(getStatusLogMessage, Duration.Inf)
println(message)
}
That way your side effect, i.e. println is isolated and other methods can be unit tested. If you need to block the execution, do it only at the end and use map and flatMap to chain Futures if you need to order the execution of those. Also be careful with Duration.Inf, if you really need to block, then you'd want to have some defined timeout.

rendering a page after Future is done with Play2

Using Play2 framework with Slick DB manager, I have a class that encapsulates DB access, and a controller in charge of returning that information to the user. There are roughly like this:
Class with DB access:
override def getAllAnswersFuture(userID: Int): Future[List[UserAnswer]] = {
(DB QUERY CREATION AS allAnswersQuery)
val myquery : Future[Seq[(Int, String)]] =myDB.run(allAnswersQuery.result)
myquery.map{x => x.map(elem =>(
UserAnswer(elem._1, elem._2).toList}
}
Controller:
def index = Action { request =>
request.session.get("userID").map { userID =>
val myUser = userStorage.getUser(userID.toInt)
val allAnswersFuture = questionStorage.getAllAnswersFuture(myUser.id)
allAnswersFuture onComplete {case _ => Ok(views.html.app.stats())}
Ok(views.html.app.index("Future did not work"))
}.getOrElse {
Unauthorized("Oops, you are not connected")
}
}
For some reason, the content of onComplete is never shown, returning the "future did not work" instead.
My question is, How do I tell Play2 to wait until the DB query has been completed and then print the results?
Use Action.async for dealing with futures and try doing something like:
def index = Action.async { request =>
allAnswersFuture.map{_ => Ok(views.html.app.stats())}.recover {
case ex: Exception =>
Ok(views.html.app.index("Future did not work"))
}
}
Instead of using onComplete on future use Map and recover, If you want to return anything on future callback. OnComplete on Future won't return anything, Its return type is Unit.
In case of Future success, your stats page will be populated and in failure index page will be populated from recover block.
And in your getOrElse block use:
Future.successful(Unauthorized("Oops, you are not connected"))
This will do the needful

playframework scala returning post request

in my scala playframework application I want to return the via Post submitted and then with slick stored object back to frontend as json
I tried this:
def createClient = Action.async { implicit request =>
request.body.asJson.map(_.validate[ClientModel] match {
case JsSuccess(client) =>
clientDTO.createClient(client).map { clients =>
Ok(Json.toJson(clients))
}
})
}
but I get this error:
what could be my problem?
NEW ERROR
Try with something along these lines:
def createClient = Action.async { implicit request =>
request.body.asJson match {
case None => // do something that returns a Future[Result] ~ such as NotFound or
case Some(js) =>
js.validate[ClientModel] match {
case client: JsSuccess[ClientModel] =>
clientDTO.createClient(client).map { clients =>
Ok(Json.toJson(clients))
}
case e: JsError => // do something that returns a Future[Result] ~ such as InternalServerError
}
}
}
the .async, as the name suggests require a Future type.
You have 2 options:
Remove the .async, this will make your def synchronous (deprecated)
Leave the .async but return a Future result
def createClient = Action.async { implicit request =>
request.body.asJson.map(_.validate[ClientModel] match {
case JsSuccess(client) =>
clientDTO.createClient(client).map { clients =>
Future(Ok(Json.toJson(clients)))
}
})
}
But you still need to add the case of validation error:
case JsSuccess(client) =>
{
clientDTO.createClient(client).map
{
clients => Future(Ok(Json.toJson(clients)))
}
}
case _ => Future(BadRequest(""))
This should work and, in all the cases apart JsSuccess, the function will return a future BadRequest response.
A better solution is to change the _ with JsError:
case e: JsError =>
{
Println(e)
Future(BadRequest(.....))
}
This will also print the error.
You can read more here: https://www.playframework.com/documentation/2.6.x/ScalaJson
(Using validation chapter)
More about future in scala: https://docs.scala-lang.org/overviews/core/futures.html

Play Framework Authentication in a single page app

I am trying to add authentication to my Play Framework single page app.
What I would like to have is something like:
def unsecured = Action {
Ok("This action is not secured")
}
def secured = AuthorizedAction {
// get the authenticated user's ID somehow
Ok("This action is secured")
}
For a traditional web app, I had previously done this, following Play Framework docs:
def authenticate = Action { implicit request =>
loginForm.bindFromRequest.fold(
formWithErrors => BadRequest(views.html.login(formWithErrors)),
user => {
Redirect(routes.Application.home).withSession(Security.username -> user._1)
}
)
}
def logout = Action {
Redirect(routes.Auth.login).withNewSession.flashing(
"success" -> "You are now logged out."
)
}
and the Authorized Action is extending ActionBuilder as follows:
object AuthorizedAction extends ActionBuilder[Request] with Results {
/**
* on auth success: proceed with the request
* on auth failure: redirect to login page with flash
*/
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// TODO: is "isDefined" enough to determine that user is logged in?
if(request.session.get("username").isDefined) {
block(request)
}
else {
Future.successful(Redirect(routes.Auth.login).flashing(
"failure" -> "You must be logged in to access this page."
))
}
}
}
For single page applications however, this approach doesn't exactly work anymore.
This article by James Ward explains how the new approach is to be designed, and includes a Java implementation:
Securing SPA and rest services
The implementation was redone in Scala by Marius Soutier: Securing SPA in Scala
In his example, he implements a Security trait:
trait Security { self: Controller =>
val cache: CacheApi
val AuthTokenHeader = "X-XSRF-TOKEN"
val AuthTokenCookieKey = "XSRF-TOKEN"
val AuthTokenUrlKey = "auth"
/** Checks that a token is either in the header or in the query string */
def HasToken[A](p: BodyParser[A] = parse.anyContent)(f: String => Long => Request[A] => Result): Action[A] =
Action(p) { implicit request =>
val maybeToken = request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey))
maybeToken flatMap { token =>
cache.get[Long](token) map { userid =>
f(token)(userid)(request)
}
} getOrElse Unauthorized(Json.obj("err" -> "No Token"))
}
}
Functions are now secured like this instead of a plain Action:
def ping() = HasToken() { token => userId => implicit request =>
user.findByID (userId) map { user =>
Ok(Json.obj("userId" -> userId)).withToken(token -> userId)
} getOrElse NotFound (Json.obj("err" -> "User Not Found"))
}
where .withToken is defined as:
implicit class ResultWithToken(result: Result) {
def withToken(token: (String, Long)): Result = {
cache.set(token._1, token._2, CacheExpiration)
result.withCookies(Cookie(AuthTokenCookieKey, token._1, None, httpOnly = false))
}
def discardingToken(token: String): Result = {
cache.remove(token)
result.discardingCookies(DiscardingCookie(name = AuthTokenCookieKey))
}
}
I am not liking how complex the "ping" function above has become, and would have preferred to use an Action Builder (like the first example), where auth failure is caught and dealt with at a single point. (as of now, if I want to secure functions ping2 and ping3, each one has to check whether the user is found and deal with the "not found" case)
I have tried to put together an action builder, inspired by Marius' implementation, most particularly his use of the cacheApi which is necessary.
However the AuthorizedAction is an object, and cacheApi needs to be injected (so need to change the object to singleton class), or cannot be declared in an object without being defined.
I also feel like the AuthorizedAction needs to remain an object, in order to be used as:
def secured = AuthorizedAction {
Would anyone please clear up the confusion, and possibly help with some implementation details?
Thanks a lot
The simplest way in my opinion is to go with ActionBuilder. You can define an action builder as a class (and pass it some dependencies) or as an object.
First you'll need to define a type a request that will contain the information about the user:
// You can add other useful information here
case class AuthorizedRequest[A](request: Request[A], user: User) extends WrappedRequest(request)
Now define your ActionBuilder
class AuthorizedAction(userService: UserService) extends ActionBuilder[AuthorizedRequest] {
override def invokeBlock[A](request: Request[A], block: (AuthorizedRequest[A]) ⇒ Future[Result]): Future[Result] = {
request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey)) match {
case Some(token) => userService.findByToken(token).map {
case Some(user) =>
val req = AuthorizedRequest(request, user)
block(req)
case None => Future.successful(Results.Unauthorized)
}
case None => Future.successful(Results.Unauthorized)
}
}
}
Now you can use it in your controller:
val authorizedAction = new AuthorizedAction(userService)
def ping = authorizedAction { request =>
Ok(Json.obj("userId" -> request.user.id))
}

How can I use a Future inside an Akka HTTP Directive?

I currently have a directive that I'm using to secure resources in an Akka HTTP app, like so:
def authenticate: Directive1[Login] =
optionalHeaderValueByName("Authorization") flatMap {
val accessToken = authz.split(' ').last
case Some(authz) =>
LoggedInUser findByAccessToken accessToken match {
case Some(user) => provide(user)
case None => reject(AuthorizationFailedRejection)
}
case None => reject(AuthorizationFailedRejection)
}
where LoggedInUser.findByAccessToken() makes a blocking query against a database, I would like to switch this for an asynchronous ask to an actor which which can provide the same data, I'm OK with passing in the ActorRef as a parameter to the directive but I cannot work out how to handle the Future that the ask returns.
None of the Directive1 examples that come with Akka HTTP seem to do this (at least I could;t find any) although there are examples of directives returning Route which do.
Is what I want to do even possible? Is a possible approach to create a StandardRoute subclass with a field for the user credentials and return that somehow?
Yes, it is possible. As far as I understand you need something like this:
def authenticate: Directive1[Login] = {
def findByAccessToken(accessToken:String): Future[Option[Login]] = ???
optionalHeaderValueByName("Authorization").flatMap {
case Some(authz) =>
val accessToken = authz.split(' ').last
onSuccess(findByAccessToken(accessToken)).flatMap {
case Some(user) => provide(user)
case None => reject(AuthorizationFailedRejection)
}
case None => reject(AuthorizationFailedRejection)
}
}