こういうパターンはどうだろう、というメモ以外のなにものでもない。
諸元
ユーザーが画面で指定した条件に基づいてデータ検索を行い、結果をCSVでダウンロードさせるWebアプリケーションの機能を作ることになったんだ。
え、めっちゃよくあるやつじゃん。朝飯前じゃん。
ああそうさ、めっちゃよくあるやつさ。ピースオブケークさ。
だけどジョニィ?ちょっと待ってくれよ。
「CSVダウンロード」か「バリデーションエラー」かでサーバーのレスポンスを切り替えなきゃいけない。
おっと、オーケーオーケー、ジョニィ、君の言いたいことはわかる。わかるよ。
それだけならちょっと複雑でサーバー側の挙動がわかりにくくなるけど出来ないことはないさ。
問題は***「画面に表示しているけどサーバー側に送らない情報が存在」しているうえ、「それを共通機能で取得しているから独自で追加対応が必要」***なんだ。
例えばユーザーIDだけをサーバーに送っているけど名称は表示だけに使っている、そんな情報を想像してくれ。
それを(無駄だけど)リクエストに含めたとしても、バリデーションエラー時に状態復元をフックしなきゃいけない。
面倒くさい。
ジョニィ、きみはせっかちすぎる。君の悪い癖だぜ。
そう、表示だけ情報にまつわる部分、3パターンもあるんだ…いい加減面倒くさすぎる。
どうするか
2段階に分けちゃえ。
- Ajaxで事前チェック
- エラーがない場合にformをsubmit
問題は
- その気になればチェックした値をユーザーが書き換えてダウンロード要求だせちゃう
- Springを使ってるけどバリデーション(HibernateValidator)結果をどう反映するか
- JSPなら
<form:errors>
で自動的にやってくれるけど…
- JSPなら
前者はパラメーターとソルト値からハッシュを生成し、トークンとして返すことでパラメーター改竄を検出可能にして対応としました。
ダウンロードリクエスト時にトークンが無かったり、再生成したハッシュと異なっていたらNG。
後者は数年前に実装したけど使ってなかった共通部品を使い、SpringのBindingResult
からi18nの解決を行った上、パラメーター名と紐づけたJSONとして返却し、JavaScript側でゴリゴリDOM書き換えて対応しました:(
どうも<form:errors path="target">
は<span id="target.errors"/>
を生成するようなので同様のものをdisplay:noneのうえ用意し書き換える…。
このあたりはもっといいやり方がある気がする。
今日知った小ネタ
lombokの@Dataで特定項目をhashCode
(とequals
)から除きたい!
@EqualsAndHashCode(exclude = {"token"})
Ajaxリクエスト時はトークンがなく、ダウンロードリクエスト時はトークンがあるので差が出ちゃう。
jQueryのセレクタはドットが含まれていると動かない!
このようにエスケープする
$('#target\\.errors').text('〜');
なお、トークンをリクエストヘッダーにくっつけられないかな、と思ったけどどうもHTMLだけじゃ実現できない様子:(
<meta>
タグにありそうだけどね。