31

私は、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 
  })
)

素敵できれい。ただし、完全に非同期のデータ アクセス レイヤー (ReactiveMongo など) を使用している場合、そのような呼び出しUser.authenticate(...)は a を返すためFuture、組み込みのフォーム バインディング機能と非同期ツール。

非同期アプローチを宣伝するのは良いことですが、フレームワークの特定の部分がうまく機能しないことに不満を感じています。検証を同期的に行う必要がある場合、非同期アプローチのポイントを無効にしているようです。Actionコンポジションを使用しているときに同様の問題に遭遇しました。たとえば、 ActionReactiveMongo を呼び出すセキュリティ関連の問題です。

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

4

5 に答える 5

9

はい、Play の検証は同期的に設計されています。ほとんどの場合、フォーム検証に I/O がないと仮定したためだと思います。フィールド値は、サイズ、長さ、正規表現との一致などについてチェックされるだけです。

play.api.data.validation.Constraint検証は、検証された値からValidationResult( または のいずれValidInvalid、ここに配置する場所はありません) までのストア関数に基づいて構築され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 の Data Binding は、検証中に I/O を実行するようには設計されていないと思います。非同期にすると複雑で使いにくくなるので、シンプルに保ちました。フレームワーク内のすべてのコードをFutures でラップされたデータで動作させるのはやり過ぎです。

ReactiveMongo で検証を使用する必要がある場合は、Await.result. ReactiveMongo はどこでも Future を返します。これらの Future が完了するまでブロックして、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 を実行したいと考えているので (たとえば、データベースに対して値をチェックするため)、誰かが Play の将来のバージョンでそれを実装するかもしれません。

于 2013-02-18T07:38:05.530 に答える
3

フォームの検証とは、フィールドを 1 つずつ構文的に検証することです。フィールドが検証に合格しない場合は、マークを付けることができます (例: メッセージ付きの赤いバー)。

認証は、Async ブロックにある可能性があるアクションの本体に配置する必要があります。それはbindFromRequest呼び出しの後でなければならないので、検証後に私がいる必要があるので、各フィールドが空でないなど.

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

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

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

Play インストールの ZenTasks の例をご覧ください。

于 2013-06-03T19:45:21.303 に答える
2

同じ質問がPlayのメーリング リストに寄せられ、Johan Andrén は次のように答えています。

実際の認証をフォーム検証から移動し、代わりにアクションで実行し、必須フィールドなどの検証にのみ検証を使用します。次のようなもの:

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 ...)
      }
    }
  )
}
于 2015-02-03T22:05:23.080 に答える