2013-02-16 4 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の構成を使用して同様の問題に遭遇しました。 ReactiveMongoを呼び出すセキュリティ関連のActionがあります。

私の理解が不足しているところに誰でも光を当てることはできますか?

答えて

9

はい、Playの検証は同期的に設計されています。ほとんどの場合、フォーム検証でI/Oがないと仮定していると考えられます。フィールド値はサイズ、長さ、regexpとのマッチングなどがチェックされます。

妥当性検査は妥当性検証済みの値を格納するplay.api.data.validation.ConstraintValidationResultValidまたは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) 
} 

verifyingは、ユーザー定義関数で別の制約を追加するだけです。

私は、Playのデータバインディングは検証中にI/Oを行うためのものではないと思います。非同期にすると、より複雑で使いにくくなり、シンプルに保たれます。 Futureでラップされたデータを処理するためのフレームワーク内のすべてのコードを作ることは過度です。

ReactiveMongoで検証を使用する必要がある場合は、Await.resultを使用できます。 ReactiveMongoはFuturesをどこにも返すので、verifyingの中に結果を得るために、これらのFuturesが完了するまでブロックすることができます。はい、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メーリングリストで議論することは良いことだと思うかもしれませんが、プレイデータバインディングで非同期I/Oを(たとえば、データベースに対する値をチェックするために)したい人は多分います。遊びます。

+0

:から抽出

?メッセージは「無効なユーザー名またはパスワード」または「現在利用できないサービス」などです。 2番目の質問は、重複した認証要求なしで、UserオブジェクトをActionで取得できますか? – Artem

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

フォーム検証はフィールドの構文検証を意味し、1:代わりにスレッドをブロックの代替は、フォームの外のパラメータを取得し、コントローラメソッド自体に認証呼び出し、このようなものを扱うようになります1。 ファイルが検証に合格しなかった場合は、それをマークすることができます(例えば、メッセージ付きの赤いバー)。

認証はアクションの本体に配置する必要があります。アクションの本体は非同期ブロックに配置する必要があります。 bindFromRequestコールの後でなければなりません。したがって、検証後に各フィールドが空でない場合などには必要です。

非同期呼び出し(ReactiveMongo呼び出しなど)の結果に基づいて、 BadRequestまたはOkのいずれかになります。

BadRequestとOkの両方が、認証に失敗した場合は、エラーメッセージとともにフォームを再表示できます。これらのヘルパーは、応答本体とは独立して、応答のHTTPステータスコードのみを指定します。

play.api.mvc.Security.Authenticatedで認証を行う(または同様のカスタマイズされたアクションコンポジットを作成する)と、Flashスコープのメッセージを使用するのは、洗練された解決策です。したがって、ユーザーが認証されていない場合は常にログインページにリダイレクトされますが、誤った資格情報を使用してログインフォームを送信すると、リダイレクトのほかにエラーメッセージが表示されます。

プレイインストールのZenTasksの例を見てください。私は、フォーム検証のうち、実際の認証を移動し、代わりにあなたの行動でそれを行うだけの妥当性の検証を使用したい

を:

1

同じ質問はヨハンがandrenが返信でプレイメーリングリストで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私はdynamicly検証メッセージを設定するにはどうすればよい

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

暗号化の内容は、そのファイルimplicits.Forms.scalaにある "toFlash"暗黙のメソッドに入ります – dzv3

関連する問題