Wicket はステートフルなフレームワークで、ページの状態を version 番号をふって
管理しています。
ログインを求める会員制のサイトなどの場合この性質が良かったりしますが、
誰でも閲覧が可能な公開ページの場合は、URL にごみがついていたり、
状態を保持していることが、SEO 観点で災いをもたらすと考えます。
個人的に作成しているアプリケーションは公開アプリケーションのため、
認証な必要なページ以外は、Stateless にしたいと考えています。
Stateless にするのに必要なことを調べつつ、
Stateful になっていたページを Stateless にしていく過程を記載します。
前提 Wicket の Version
7.6.0です。
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-core</artifactId>
<version>7.6.0</version>
</dependency>
参考記事
Wicket の コンポーネント についての個人的な理解
-
基本的に大概のコンポーネントは ステートレス だが、子のコンポーネントが ステートフル だと
ステートフル になる。 -
Link
と、Form
は ステートフル なので、Link
と、Form
を持つコンポーネントは ステートフルになる。 -
上記、1. 、2. により、何にも考えないで作っていると、結構 ステートフル になりやすい。
Link と Form をステートレスにするには
-
Link には、
StatelessLink
、BookmarkablePageLink
を使うと、ステートレスになる。1
[1] 当たり前かもですが、 ExternalLink の ステートレスです。 -
Form には、
StatelessForm
を用いると ステートレスになる。 -
上記、1.、2.より
Form
と、Link
をそれぞれ、StatelessForm
、BookmarkablePageLink
に変えていくと、結構 ステートレスになりやすい。
ステートレスなページかどうかを確認するには
wicket/wicket-devutils at master · apache/wicket
の StatelessChecker
を使うと、ステートレスなページでないページを検知できます。
前に、こちら記事にしましたので、そちらのリンクを貼り付けておきます。
StatelessChecker
は デフォルトで例外をスローするので、以下の通り、
例外がスローされたら、ログに書き出すようにしました。
// add StatelessChecker
getComponentPostOnBeforeRenderListeners().add(new StatelessChecker() {
public void onBeforeRender(final Component component) {
try {
super.onBeforeRender(component);
} catch (IllegalArgumentException | IllegalStateException e) {
log.warn("Exception occurred..", e);
}
}
});
ステートレスな拡張コンポーネント
wicketstuff に stateless な コンポーネントのライブラリがあります。
Wicket 8 では 本体に含まれる? のか @deprecated
がついていますが、
Wicket 7 に含まれてはなさそうなので、こちらを使用していきます。
以下、ライブラリの github のリンクを貼り付けておきます。
pom.xml
には、以下のように記載しました。
<!-- https://mvnrepository.com/artifact/org.wicketstuff/wicketstuff-stateless -->
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-stateless</artifactId>
<version>7.6.0</version>
</dependency>
ステートフルな Page を、 ステートレスなページに書き換えていく
以下のような手順で書き換えていきました。
-
Link
、Form
は、BookmarkablePageLink
、StatelessForm
に書き換える。 -
AjaxButton
は、StatelessAjaxButton
に書き換える -
StatelessChecker
をComponentPostOnBeforeRenderListener
として追加。 -
動作確認して、
StatelessChecker
のログが出力された実装の書き換えを実施。
4.
でログが出力され実装の書き換えをした内容について以下記載します。
AjaxPreventSubmitBehavior がステートフル
AjaxPreventSubmitBehavior
がステートフルだったため、
以下の通り、変更しました。
StatelessAjaxSubmitBehavior
を継承して、以下のようなクラスを作成しました。
ですが、AjaxCallListener
が ステートフルのようで?、
相変わらずエラーログが消えません。
AjaxCallListener
を使わずに、AttributeModifier
を使って以下の通り実装しました。
private AttributeModifier getOnkeydownPreventSubmitAttributeModifer() {
return AttributeModifier.replace("onkeydown", "if(event.keyCode==13 || window.event.keyCode==13){return false;} else {return true;}");
}
AjaxFormValidatingBehavior が ステートフル
AjaxFormValidatingBehavior
が、ステートフルのため、以下のログが出力されました。
java.lang.IllegalStateException: '[SynchTokenField [Component id = token]]' claims to be stateless but isn't. Stateful behaviors: org.apache.wicket.ajax.form.AjaxFormSubmitBehavior
at org.apache.wicket.devutils.stateless.StatelessChecker.onBeforeRender(StatelessChecker.java:114) ~[festivals4partypeople-web-0.0.1.jar:0.0.1]
AjaxFormSubmitBehavior
とログに出ているのですが、
インナークラス AjaxFormValidatingBehavior.FormValidateVisitor
の実装内で、AjaxFormSubmitBehavior
を使用しているのが、
原因のようです。
- コード抜粋
public void component(final FormComponent component, IVisit<Void> visit) {
AjaxFormSubmitBehavior behavior = new AjaxFormSubmitBehavior(AjaxFormValidatingBehavior.this.form, AjaxFormValidatingBehavior.this.event) {
//............略
AjaxFormSubmitBehavior
の ステートレス版の、StatelessAjaxFormSubmitBehavior
というクラスがありましたので、
StatelessAjaxFormSubmitBehavior
に書き換えて StatelessAjaxFormValidatingBehavior
を作成してみます。
StatelessAjaxFormSubmitBehavior
には、Formを引数に持つコンストラクタないため、単純な置き換えができず、event
を引数に取る
コンストラスタに置き換えましたが、
それが影響しているのか、FeedbackPanel
が動作しなくなりました。
MarkupId
の定義がないという javascript
エラーだったので、試しに、wicket:id
と同じ id を付与するようにしたところ、
上手く動作しました。2
[2] 上手く動作している理由は現状よくわかっておりません。
/**
* Constructor
*
* @param id
*/
public ApplicationFeedbackPanel(String id) {
super(id);
setMarkupId(id);
}
Transaction token 発行用に作成した Hidden コンポーネントが動かない。
上記の対応で、作成していたページは ステートレスになりましたが、
Transaction token 発行に以下のような Class を作成して、
2度押しチェックを行っていた箇所が、 StatelessForm に変更後、
通常送信でも2度押しチェックエラーが発生するようになりました。3
[3] Form が Post 送信のたびに、再生成されて Token が変わってしまっているのが原因かと思われます。
以下が対象のクラスになります。
Wicket 6から CsrfPreventionRequestCycleListener
という RequestCycleListener が追加されています。
Origin
と Referer
によるチェックであれば、これを使うことで実施できるので、
Application クラスに追加します。
CsrfPreventionRequestCycleListener csrfPreventionListener = new CsrfPreventionRequestCycleListener() {
@Override
protected boolean isLocalOrigin(HttpServletRequest containerRequest, String originHeader) {
return false;
}
};
csrfPreventionListener.addAcceptedOrigin("www.yourdomain.com");
getRequestCycleListeners().add(csrfPreventionListener);
これで Referer
が、www.yourdomain.com
の場合は、デフォルト設定では、リクエストは破棄され、400エラーが返るようになります。
-
説明1
isLocalOrigin()
でfalseを返すのは、testのためです。プロダクション環境で使用する場合は、設定は不要です。 -
説明2
csrfPreventionListener.addAcceptedOrigin("www.yourdomain.com");
でアクセスを認める、hostを設定します。
まとめ
Stateful な Page を Stateless にしてみました。 以下まとめます。
-
Form が存在しないページのStateless化は容易だが、Form が存在して、且つ、Ajax を使用していると途端にStateless にするのが難しくなる。
-
Stateful だとWicket 側の もともと用意されている部品群でいろいろできるが、Sateless にすると自前で実装する必要が出てくる。4
[4]stateless-parent
と使うと、いい感じでできるが、それでも多少不便 -
SEO 対策が必要なページは Stateless だが、それ以外のページは、実装が簡単なのでStateful にしておきたい。
-
思わぬところに、Stateful な コンポーネント、ビヘイビアがいる。 StatelessChecker は使ったほうが、作業が早く進む。
-
CsrfPreventionRequestCycleListener のだけでなく、同期tokenを用いたチェックもしたい。
まだ実装しきったとは言えず、手探りですが理解はできた気がします。
以上です。
コメント