Scala:如何获得未来的结果 [英] Scala: How to get the result of a Future

查看:55
本文介绍了Scala:如何获得未来的结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

...然后我有另一个方法调用 isTokenExpired ,该方法返回如下的 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
}

是否有更好的方式编写 isExpired 方法?

Is there a better way to write the isExpired method?

编辑

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

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

如您所见,除到期信息外,所有jwt属性都是独立的.方法 isExpired 使用ReactiveMongo,该方法始终返回 Future .为了使事情变得更加复杂,我在自定义的 Action 中使用此jwt,如下所示:

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

如您所见,我需要返回一个 Future [play.mvc.results.Result] ,而不是一个 Future [Boolean] ,因为它会返回如果我使用的是 Future.map ,则为isExpired .你能指出吗?

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?

推荐答案

您编写的函数将无法正常运行.(可能)首先返回 true ,然后设置 result 变量.

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
}

在类似Play的框架中,您会将 Future 映射到http响应,并给Play播放一个 Future [SimpleResult] .Play知道如何处理 Future 结果.

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.

通常,建议您不要等待 Future 完成生产代码,而是使用 Future 中的值并让您使用的框架处理结果.

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)

修改

我可能会提取令牌的构造,以便最终得到 Future [Token] .这使我可以更轻松地编写内容.它还使我可以创建具有更好的体系结构并且更易于测试的代码.

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

这篇关于Scala:如何获得未来的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆