Scala:如何获得未来的结果

时间:2014-04-06 21:20:16

标签: scala future

我有一个像这样返回Future的方法......

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

...然后我又调用isTokenExpired的另一种方法,它返回Boolean,如下所示:

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

有没有更好的方法来编写isExpired方法?

修改

根据EECOLOR的要求,让我向您提供更多详细信息。对于我的Play应用程序,我实现了基于JSON Web Token(jwt)的授权机制。所有声明都包含在jwt中,但到期时间除外,它存储在MongoDB集合中。以下是我的Token课程的总结:

class Token {
  ...

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

如您所见,除了到期信息之外,所有jwt属性都是自包含的。方法isExpired使用ReactiveMongo,它始终返回Future。为了使事情变得更复杂,我将这个jwt用于这样的自定义Action

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

正如您所看到的,如果我使用Future[play.mvc.results.Result],我需要返回Future[Boolean],而不是返回isExpired Future.map。你明白了吗?

2 个答案:

答案 0 :(得分:6)

你写的函数不会像你想象的那样工作。它(可能)首先返回true,然后设置result变量。

通常你会做这样的事情:

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

在像Play这样的框架中,您可以将Future映射到http响应,然后播放一个Future[SimpleResult]。 Play知道如何处理Future结果。

一般情况下,建议您不要等待Future在生产代码中完成,而是使用Future中的值并让您使用的框架处理结果

在测试中,等待结果可能会派上用场,你可以这样做:

Await.result(someFuture, 5.seconds)

修改

我可能会提取令牌的构造,以便最终得到Future[Token]。这让我更容易创作。它还允许我创建具有更好架构并且更容易测试的代码。

我可能会将代码分解为更小的方法,但下面的示例让您了解我将采取的方向。

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

答案 1 :(得分:0)

一种解决方案是通过字面来永久等待结果:

Await.result(futureResult, Duration.Inf)

等待永远将Future[Value]转换为您想要的Value

我喜欢在我的代码中评论这一举动...

// wait forever to make future current (?)

Obs。我的ScalaTest中的无限期。对于生产,等待期应该按照@EECOLOR进行调整。