我可以在Play Framework 2.x(Scala)中进行异步表单验证吗?

Dre*_*ter 31 asynchronous playframework-2.0

我正在努力理解Play的异步功能,但是在异步调用适合的地方发现了很多冲突,并且框架似乎与它的使用密谋.

我的例子涉及表单验证.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 
  })
)
Run Code Online (Sandbox Code Playgroud)

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

宣传异步方法一切都很好,但我对框架的某些部分不能很好地发挥作用感到沮丧.如果验证必须同步进行,那么它似乎打败了异步方法.我在使用Action合成时遇到了类似的问题- 例如Action,与ReactiveMongo调用相关的安全性.

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

kol*_*len 9

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

验证是建立在play.api.data.validation.Constraint存储函数上的,从验证值到ValidationResult(Valid或者Invalid,没有放在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)
}
Run Code Online (Sandbox Code Playgroud)

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

所以我认为Play中的数据绑定不是为验证时进行I/O而设计的.使它变得异步将使它更复杂和更难使用,因此它保持简单.使框架中的每一段代码都处理包含在Futures中的数据是过度的.

如果您需要对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")
  }
}
Run Code Online (Sandbox Code Playgroud)

也许有办法不通过使用延续来浪费线程,而不是尝试它.

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


小智 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.")
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)