2013-02-16 60 views
30

我正在努力了解Play的异步功能,但发现与异步调用适合的位置以及框架似乎与其使用密切相关的地方存在很多冲突。我可以在Play Framework 2.x(Scala)中进行异步表单验证吗?

我已经涉及到表单验证的例子。 Play允许定义临时约束 - 请参阅文档:

val loginForm = Form(
    tuple(
    "email" -> email, 
    "password" -> text 
) verifying("Invalid user name or password", fields => fields match { 
     case (e, p) => User.authenticate(e,p).isDefined 
    }) 
) 

干净而干净。但是,如果我使用的是完全异步数据访问层(例如ReactiveMongo),则对User.authenticate(...)的这种调用将返回Future,因此我在黑暗中如何利用内置的表单绑定功能和异步工具。

公布异步方法都很好,但我对框架的某些部分不能很好地使用它感到沮丧。如果验证必须同步完成,它似乎击败了异步方法的重点。当使用Action组合物时,我遇到过类似的问题 - 例如一个安全相关的Action,它将打电话给ReactiveMongo。

任何人都可以阐明我的理解力不足的地方吗?

回答

9

是的,Play中的验证是同步设计的。我认为这是因为假设大多数时候在表单验证中没有I/O:只检查字段值的大小,长度,与正则表达式的匹配等。

验证建立在play.api.data.validation.Constraint上,验证函数从验证值到ValidationResultValidInvalid,这里没有地方可以放Future)。

/** 
* A form constraint. 
* 
* @tparam T type of values handled by this constraint 
* @param name the constraint name, to be displayed to final user 
* @param args the message arguments, to format the constraint name 
* @param f the validation function 
*/ 
case class Constraint[-T](name: Option[String], args: Seq[Any])(f: (T => ValidationResult)) { 

    /** 
    * Run the constraint validation. 
    * 
    * @param t the value to validate 
    * @return the validation result 
    */ 
    def apply(t: T): ValidationResult = f(t) 
} 

verifying只是增加了用户定义函数的另一个约束。

所以我认为Play中的数据绑定并不是为了在验证时进行I/O而设计的。使其异步将使它更复杂,更难以使用,所以它保持简单。使框架中的每一段代码都适用于Future中包装的数据是过度的。

如果您需要使用ReactiveMongo验证,则可以使用Await.result。 ReactiveMongo无处不在地返回期货,并且您可以阻止这些期货完成以获得verifying函数内的结果。是的,它会在MongoDB查询运行时浪费一个线程。

object Application extends Controller { 
    def checkUser(e:String, p:String):Boolean = { 
    // ... construct cursor, etc 
    val result = cursor.toList().map(_.length != 0) 

    Await.result(result, 5 seconds) 
    } 

    val loginForm = Form(
    tuple(
     "email" -> email, 
     "password" -> text 
    ) verifying("Invalid user name or password", fields => fields match { 
     case (e, p) => checkUser(e, p) 
    }) 
) 

    def index = Action { implicit request => 
    if (loginForm.bindFromRequest.hasErrors) 
     Ok("Invalid user name") 
    else 
     Ok("Login ok") 
    } 
} 

也许有办法不浪费线程通过使用continuations,没有尝试过。

我认为在Play邮件列表中讨论这个问题很好,也许很多人想要在Play数据绑定中执行异步I/O(例如,用于检查数据库的值),所以有人可能会为未来的版本玩。

+0

如何设置验证信息动态地:

摘自?例如,消息可能是“无效的用户名或密码”或“现在服务不可用”。 第二个问题是我可以在没有重复认证请求的情况下获取User对象吗? – Artem 2014-07-28 07:41:05

6

我一直在为此而苦苦挣扎。真实的应用程序通常会有某种用户帐户和身份验证。而不是阻塞线程的,另一种是拿到参数的表格和处理控制器方法本身的认证通话,是这样的:由

def authenticate = Action { implicit request => 
    Async { 
    val (username, password) = loginForm.bindFromRequest.get 
    User.authenticate(username, password).map { user => 
     user match { 
     case Some(u: User) => Redirect(routes.Application.index).withSession("username" -> username) 
     case None => Redirect(routes.Application.login).withNewSession.flashing("Login Failed" -> "Invalid username or password.") 
     } 
    } 
    } 
} 
3

表单验证意味着领域的语法验证,一个一。 如果一个字段没有通过验证,它可以被标记(例如带有消息的红色条)。

身份验证应放在操作的主体中,该操作可能位于异步块中。 应该是bindFromRequest电话后,所以必须有我的验证后,打完基于异步调用的结果,每个字段不为空,等

(如ReactiveMongo调用)的作用的结果可以是BadRequest或Ok。

如果身份验证失败,BadRequest和Ok都可以重新显示带有错误消息的表单。这些助手只指定响应的HTTP状态代码,独立于响应主体。

这将是一个优雅的解决方案,使用play.api.mvc.Security.Authenticated(或编写类似的自定义操作合成器)进行身份验证,并使用Flash范围消息。因此,如果用户未通过身份验证,用户总是会被重定向到登录页面,但如果她提交的登录表单提供的凭据不正确,则会显示除重定向之外的错误消息。

请看看您的游戏安装的ZenTasks示例。

1

同样的问题在与约翰Andrén播放邮件列表asked回答:

我会在你的动作移动的实际验证表单验证,做它,而不是和使用验证只是为了验证必填字段等等。事情是这样的:

val loginForm = Form(
    tuple(
    "email" -> email, 
    "password" -> text 
) 
) 

def authenticate = Action { implicit request => 
    loginForm.bindFromRequest.fold(
    formWithErrors => BadRequest(html.login(formWithErrors)), 
    auth => Async { 
     User.authenticate(auth._1, auth._2).map { maybeUser => 
     maybeUser.map(user => gotoLoginSucceeded(user.get.id)) 
     .getOrElse(... failed login page ...) 
     } 
    } 
) 
} 
0

我已经看到了卫报的GH回购他们在一个异步的方式如何处理同时还具有从游戏形式错误佣工的支持这一情况。从快速浏览中,似乎他们将表单错误存储在加密的cookie中,以便在用户下次登录页面时将错误显示回用户。 https://github.com/guardian/facia-tool/blob/9ec455804edbd104861117d477de9a0565776767/identity/app/controllers/ReauthenticationController.scala

def processForm = authenticatedActions.authActionWithUser.async { implicit request => 
    val idRequest = idRequestParser(request) 
    val boundForm = formWithConstraints.bindFromRequest 
    val verifiedReturnUrlAsOpt = returnUrlVerifier.getVerifiedReturnUrl(request) 

    def onError(formWithErrors: Form[String]): Future[Result] = { 
    logger.info("Invalid reauthentication form submission") 
    Future.successful { 
     redirectToSigninPage(formWithErrors, verifiedReturnUrlAsOpt) 
    } 
    } 

    def onSuccess(password: String): Future[Result] = { 
     logger.trace("reauthenticating with ID API") 
     val persistent = request.user.auth match { 
     case ScGuU(_, v) => v.isPersistent 
     case _ => false 
     } 
     val auth = EmailPassword(request.user.primaryEmailAddress, password, idRequest.clientIp) 
     val authResponse = api.authBrowser(auth, idRequest.trackingData, Some(persistent)) 

     signInService.getCookies(authResponse, persistent) map { 
     case Left(errors) => 
      logger.error(errors.toString()) 
      logger.info(s"Reauthentication failed for user, ${errors.toString()}") 
      val formWithErrors = errors.foldLeft(boundForm) { (formFold, error) => 
      val errorMessage = 
       if ("Invalid email or password" == error.message) Messages("error.login") 
       else error.description 
      formFold.withError(error.context.getOrElse(""), errorMessage) 
      } 

      redirectToSigninPage(formWithErrors, verifiedReturnUrlAsOpt) 

     case Right(responseCookies) => 
      logger.trace("Logging user in") 
      SeeOther(verifiedReturnUrlAsOpt.getOrElse(returnUrlVerifier.defaultReturnUrl)) 
      .withCookies(responseCookies:_*) 
     } 
    } 

    boundForm.fold[Future[Result]](onError, onSuccess) 
} 

def redirectToSigninPage(formWithErrors: Form[String], returnUrl: Option[String]): Result = { 
    NoCache(SeeOther(routes.ReauthenticationController.renderForm(returnUrl).url).flashing(clearPassword(formWithErrors).toFlash)) 
} 
+0

加密的东西进入“toFlash”隐式方法,可以在它们的文件中找到implicits.Forms.scala – dzv3 2015-09-23 06:04:39

相关问题