Scala: How to get the result of a Future

13,410

Solution 1

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
    }
  }
}

Solution 2

One solution would be to make future current by literally waiting for the result forever:

Await.result(futureResult, Duration.Inf)

Waiting forever transforms a Future[Value] into a Value which is what you want.

That's how I love to comment this move in my code...

// wait forever to make future current (?)

Obs. I infinite duration only in my ScalaTest. For production, the waiting period should be tuned as done by @EECOLOR.

Share:
13,410

Related videos on Youtube

j3d
Author by

j3d

Updated on June 30, 2022

Comments

  • j3d
    j3d almost 2 years

    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?

  • j3d
    j3d about 10 years
    Yes... I know... but in my case I need a Boolean, not a Future[Boolean]. Is Await.result so bad in production code?
  • om-nom-nom
    om-nom-nom about 10 years
    and I love to comment such code as // this is how deadlocks are born
  • user3504561
    user3504561 about 10 years
    You can tune it as @EECOLOR did but then it gets less suspense.
  • user3504561
    user3504561 about 10 years
    @om-nom-nom I have edited my answer. Thank you for your note which brought me back to reality.
  • Ryan
    Ryan about 10 years
    Using Await.result or its cousin Await.ready in production code is likely to lead to deadlocks.
  • j3d
    j3d about 10 years
    I've just added more info to my post so that you can understand why I need a Boolean instead of a Future[Boolean]. Thanks.
  • j3d
    j3d about 10 years
    OK, I see and just confirm my vote to your answer ;-) Thanks a lot.
  • user3504561
    user3504561 about 10 years
    @Ryan. What's wrong in using Await.result in a ScalaTest FunSuite? @EECOLOR sees it as handy.
  • EECOLOR
    EECOLOR over 8 years
    @user3504561 You should never use Await.result in production code. Especially in web frameworks, this quickly fills up the thread pool. On top of that, it opens up the possibility for the elusive deadlock problem. This is a state where one side is waiting for the other side to finish waiting. As I stated in my answer, it's fine to use in a test suite, because generally you are using it only to extract and test the result.
  • Rakshith
    Rakshith almost 8 years
    Iam not sure if Await.result is the right way to do it