Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: How to get the result of a Future

Tags:

scala

future

I've a method that returns a Future like this...

def isTokenExpired(token: String): Future[Boolean] = {
  ...
}

... and then I've another method that invokes isTokenExpired that returns a Boolean like this:

def isExpired(token: String): Boolean = {
  var result = true
  isTokenExpired(token).onComplete {
    case Success(r) => result = r
    case Failure(_) => result = true
  }
  result
}

Is there a better way to write the isExpired method?

EDIT

As requested by EECOLOR, let me provide you with more details. For my Play application I've implemented an authorization mechanism based on JSON Web Token (jwt). All the claims are contained in the jwt except the expiration time, which is stored in a MongoDB collection. Here below is a summary of how my Token class looks like:

class Token {
  ...

  def id: String = { ... }
  def issueTime: LocalDateTime = { ... }
  def issuer: String = { ... }
  ...
  def isValid: Boolean = { ... }
  def isExpired: Boolean = { /* uses ReactiveMongo to access MongoDB  */ }
}

As you can see, all the jwt properties are self-contained except expiration info. Method isExpired uses ReactiveMongo, which always returns a Future. To make things even more complex, I use this jwt in a customized Action like this:

class SecuredAction[T <: Controller] private(private val methodName: String)
  extends ActionBuilder[ApiRequest] {

  ...

  def invokeBlock[A](request: Request[A], block: (ApiRequest[A]) => Future[SimpleResult]) = {{
    request.headers.get(HeaderNames.AUTHORIZATION) match {
      case Some(header) => s"""$AuthType (.*)""".r.unapplySeq(header).map(_.head.trim)
      case _ => None
    }} match {
      case Some(tokenString) => {
        val token = Token(tokenString)

        if (!token.isValid) {
          Logger.warn(s"request ${request.uri} not authorized: token ${token.id} has been tampered")
          Future.successful(Unauthorized(AuthErrors.authenticationViolated(token.subject)(request).asJson))
        } else if (token.isExpired) {
          Logger.debug(s"request ${request.uri} not authorized: token ${token.id} has expired")
          Future.successful(Unauthorized(AuthErrors.authenticationExpired(token.subject)(request).asJson))
        } else if (!isAuthorized(token)) {
          Logger.info(s"request ${request.uri} not authorized: required claims not defined for account ${token.subject}")
          Future.successful(Forbidden(AuthErrors.requestNotAuthorized(token.subject)(request).asJson))
        } else {
          Logger.debug(s"request ${request.uri} authorized for account ${token.subject}")
          block(new ApiRequest(token, request))
        }
      }
      case _ => {
        Logger.debug(s"request ${request.uri} not authenticated")
        Future.successful(Unauthorized(
          AuthErrors.requestNotAuthenticated()(request).asJson
        ).withHeaders(HeaderNames.WWW_AUTHENTICATE -> AuthType))
      }
    }
  }

As you can see, I need to return a Future[play.mvc.results.Result], not a Future[Boolean]as would return isExpired if I used Future.map. Do you get to point?

like image 968
j3d Avatar asked Apr 06 '14 21:04

j3d


People also ask

How does Scala Future work?

Future represents a result of an asynchronous computation that may or may not be available yet. When we create a new Future, Scala spawns a new thread and executes its code. Once the execution is finished, the result of the computation (value or exception) will be assigned to the Future.

What is Future sequence in Scala?

sequence takes a list of futures and transforms it into a single future of list in an asynchronous manner. For instance, assume that you have a list of independent jobs to be run simultaneously. In such a case, the list of futures can be composed into a single future of list using Future.

What is await result in Scala?

Await. result tries to return the Future result as soon as possible and throws an exception if the Future fails with an exception while Await. ready returns the completed Future from which the result (Success or Failure) can safely be extracted.


1 Answers

The function you wrote will not work as you would think. It would (likely) first return true and later set the result variable.

Normally you would do something like this:

isTokenExpired(token).map { result =>
   // do stuff
}

In a framework like Play you would map the Future to an http response and give Play back a Future[SimpleResult]. Play knows how to handle Future results.

In general it's recommended you do not wait for a Future to complete in production code, but work with the values in the Future and let the framework you are using handle the result.

In tests it might come in handy to wait for a result, you can do that like this:

Await.result(someFuture, 5.seconds)

Edit

I would probably extract the construction of a token so that I end up with a Future[Token]. That allows me to more easily compose things. It also allows me to create code that has a better architecture and is easier to test.

I would probably break down the code into more smaller methods, but the example below gives you an idea of the direction I would take.

class TokenService(connection: MongoConnection) {

  def tokenFor(tokenString: String): Future[Token] = ???
}

class SecuredAction(tokenService: TokenService) extends 
  ActionBuilder[ApiRequest] {

  import play.api.libs.concurrent.Execution.Implicits._

  def invokeBlock[A](request: Request[A], block: (ApiRequest[A]) => Future[SimpleResult]) =
    extractTokenFrom(request) match {
      case Some(tokenString) => {

        tokenService.tokenFor(tokenString) flatMap {
          case token if (!token.isValid) =>
            Logger.warn(s"request ${request.uri} not authorized: token ${token.id} has been tampered")
            Future.successful(Unauthorized(AuthErrors.authenticationViolated(token.subject)(request).asJson))
          case token if (token.isExpired) =>
            Logger.debug(s"request ${request.uri} not authorized: token ${token.id} has expired")
            Future.successful(Unauthorized(AuthErrors.authenticationExpired(token.subject)(request).asJson))
          case token if (!token.isAuthorized) =>
            Logger.info(s"request ${request.uri} not authorized: required claims not defined for account ${token.subject}")
            Future.successful(Forbidden(AuthErrors.requestNotAuthorized(token.subject)(request).asJson))
          case token =>
            Logger.debug(s"request ${request.uri} authorized for account ${token.subject}")
            block(new ApiRequest(token, request))
        }
      }
      case _ =>
        Logger.debug(s"request ${request.uri} not authenticated")
        Future.successful(Unauthorized(
          AuthErrors.requestNotAuthenticated()(request).asJson).withHeaders(HeaderNames.WWW_AUTHENTICATE -> AuthType))
    }

  val AuthType = "MyAuthType"
  val TokenHeader = s"""$AuthType (.*)""".r

  def extractTokenFrom(request: RequestHeader) = {
    val authorizationHeader = request.headers.get(HeaderNames.AUTHORIZATION)
    authorizationHeader flatMap {
      case TokenHeader(token) => Some(token.trim)
      case _ => None
    }
  }
}
like image 163
EECOLOR Avatar answered Oct 14 '22 07:10

EECOLOR