Wicket URLからバージョン番号 [?0] を消したい


Wicket で作っているWebページを外部公開するのに、
Wicket のPage の version number 1が検索エンジンにindexされるのが嫌なので、
外す方法を調べた結果を記載します。

結果は個人的には、外すのを諦めて、
canonical タグ を使う方向で考えます。。


使用しているWicket Version

  • Wicket 7.5
    <dependency>
        <groupId>org.apache.wicket</groupId>
        <artifactId>wicket-core</artifactId>
        <version>7.5.0</version>
    </dependency>

同じような内容で困った人がいないのか

StackOverFlowで以下の記事を見つけました。

内容のざっくり理解だと、

  • Wicket 1.5 であれば、
    MountedMapper を 継承したMountedMapperWithoutPageComponentInfo を作ると
    Version Numberは消える。

  • Wicket 6.13以上だと、
    MountedMapperWithoutPageComponentInfo を作って動かすだけだと、
    ページのリロードが走り続けるので、RenderStrategy を ONE_PASS_RENDER に変更する。

というような話が書いてあったので、これを試してみます。


RenderStrategyを変えてみる

何が変わるものなのか

WicketでページのURLにアクセスすると、最初のアクセスは302リダイレクトされ、その後にverion番号付きのページに遷移していますが、
あの動作が変わるようです。

RenderStrategy に、3種類あり、
正直どう動くのかRender strategies を読んでもあんまりわからなかったので、
日本語で書かれた文書を探したところ、
以下PPT資料を見つけました。

内容としては、以下のように記載されています。

  • ONE_PASS_RENDER
    いわゆるforward

  • REDIRECT_TO_BUFFER
    PRG (バッファに描画しリダイレクト時に返す )
    sessionId + queryStringでbuffering
    default動作

  • REDIRECT_TO_RENDER
    PRG (リダイレクト時に描画する )
    refreshしても大丈夫

ONE_PASS_RENDER を設定してみる

Applicationクラスに以下の記述を追加します。

    getRequestCycleSettings().
            setRenderStrategy(RequestCycleSettings.RenderStrategy.ONE_PASS_RENDER);

この設定でアプリケーションを再起動し、Pageを表示したところ、
version number は付与されなくなりました。
302リダイレクトされなくなり、最初にアクセスしたURLのまま画面が描画されます。2

REDIRECT_TO_BUFFER を設定してみる

Applicationクラスに以下の記述を追加します。

    getRequestCycleSettings().
            setRenderStrategy(RequestCycleSettings.RenderStrategy.REDIRECT_TO_BUFFER);

302リダイレクトされるようになります。

REDIRECT_TO_RENDER を設定してみる

    getRequestCycleSettings().
            setRenderStrategy(RequestCycleSettings.RenderStrategy.REDIRECT_TO_RENDER);

Document読んだ限りだと、version number が付与されると思いましたが、
Pageを表示したところ、
version number は付与されなくなりました。
Pageを表示しただけの場合は、ONE_PASS_RENDER と同様の動作をしています。
POST送信後の挙動がONE_PASS_RENDERとは違ってきそうな気がしましたので、
そちらの動作確認をしてみます。

ONE_PASS_RENDER、REDIRECT_TO_RENDER の POST時の動作の違い

POSTで画面遷移させた時の、URLを比べてみました。
動作確認には、Wicket とりあえず画面遷移とフォームデータの受け取りをする - @//メモ
に記載されているものを拝借して使わせて頂きました。

Form から POST して画面遷移した際のURLは以下のようになりました。

  • ONE_PASS_RENDER
    http://127.0.0.1:18080/festivals?25-1.IFormSubmitListener-f

  • REDIRECT_TO_RENDER
    http://127.0.0.1:18080/festivals/2?103

ONE_PASS_RENDER は、
Render strategies に記載している通りの、の動作になっており、
REDIRECT_TO_RENDER は、version number がつく挙動になっておりました。。4


Wicket の document の 付録 にあるクラスタリング環境下での、RenderStrategyの動作から

Wicket の document の 付録に以下の記事があります。
28 Lost In Redirection With Apache Wicket (Appendix) 6.x

ざっくり以下のようなことが書いてありました。

  • クラスリング環境下での、REDIRECT_TO_BUFFERの動作の留意点
    Application サーバーが複数台ある場合、ラウンドロビンでリクエストを割り当てていると、
    REDIRECT_TO_BUFFER で処理をしていると、
    BufferしているAPサーバーに処理が割り当てられず、
    Buffer後のResponseが取得できない場合がある。

  • 留意点の解決策
    ラウンドロビンで処理を振り分けたい場合は、ONE_PASS_RENDER を使う必要があるが、
    Formの2重送信問題等、別の問題が発生する。

REDIRECT_TO_BUFFER でもスティッキーセッションを有効にして、おけばうまく処理が
できるようになる。5


どれを使うかの個人的な見解

以下の4つの選択肢があるとして、

  1. REDIRECT_TO_BUFFER にする。
    Page Version管理ができなくなるのを許容して、
    MountedMapperWithoutPageComponentInfo を 使い、
    且つ、
    クラスタリング環境下では、スティッキーセッションを使う。

  2. REDIRECT_TO_RENDER にする。
    Form の Post後のリダイレクトのバージョン番号を消すため、
    MountedMapperWithoutPageComponentInfoも使う。
    且つ、
    クラスタリング環境下では、スティッキーセッションを使う。

  3. ONE_PASS_RENDER にする。
    Form の 二重送信問題は、明示的にリダイレクトさせることで回避する。
    クラスタリング設定は、
    スティッキーセッションを使うでもラウンドロビンでもどちらでもOK。

  4. Version番号を消すことは、諦める。
    canonical タグ を使って、URLは正規化する。
    且つ、
    クラスタリング環境下では、スティッキーセッションを使う。

個人的には、3、4、かで、迷い、

  • 3で、実装する場合も、jsessionId は別途対策しないといけない。

  • クラスタリング設定については、スティッキーセッションで良いと思っている。

  • ONE_PASS_RENDER で、Form以外にも、対応しないといけないパターンがどれくらいあるのかで読めない。

ところから、4で実装しようかと思いました。

追記

基本的に、REDIRECT_TO_BUFFERだけど、一部ページだけ、
ONE_PASS_RENDER したいという時に、MountedMapperWithoutPageComponentInfo が使えるようです。
全体がきりかわるとばっかり思っていたので、勉強になりました。8

Wicket 6.13以上だと、NoVersionMapper というclass 作りなさいと、StackOverFlowに記載があったので、
試しに作ってみたらうまくいきました。
Classを作り、以下のようにページマウントすると、

getRootRequestMapperAsCompound().add(new NoVersionMapper("/festivals/${id}", FestivalDetailPage.class));

REDIRECT_TO_BUFFER の設定をしていても、対象指定ページが[?0]付かなくなります。


canonical タグの設定方法について

Url (Wicket Parent 6.26.0-SNAPSHOT API) に、
org.apache.wicket.request.Url#canonical() を使うと、JessionId等の要素を削ってくれそうなので、
こちらを使用して、URLを取得、linkタグの埋め込みをやってみます。

アプリケーションのPage基底クラスに以下、実装を追加しました。

  • Page基底クラスの追加記述
        @Override
        public void renderHead(IHeaderResponse response) {
            // Meta canonical
            response.render(MetaDataHeaderItem.forLinkTag("canonical", getCanonicalUrl()));
        }
    
        /**
         * getCanonicalUrl
         *
         * @return
         */
        protected String getCanonicalUrl() {
            Url url = getRequestCycle().getRequest().getUrl();
            System.out.println("url.toString()=" + url.toString());
            System.out.println("url.getPath() =" + url.getPath());
            System.out.println("url.toString(Url.StringMode.FULL) =" + url.toString(Url.StringMode.FULL));
            System.out.println("url.canonical().getPath() =" + url.getPath());
            return url.canonical().getPath();
        }
    

URL `http://127.0.0.1:18080/festivals;jsessionid=1d1cz1rff0kc917o6sna0tv22x?0 が、
画面上表示されている状態で、
HTMLの記述と、consoleへのOUTPUTは以下のようになりました。

  • HTML

    <link rel="canonical" href="festivals" />
    

  • OUTPUT

    url.toString()=festivals
    url.getPath() =festivals
    url.toString(Url.StringMode.FULL) =http://127.0.0.1:18080/festivals
    url.canonical().getPath() =festivals
    

HTMLは相対URLが出力されています。
org.apache.wicket.request.Urlのその他のURL取得メソッドにも、
jsessionidは出力されていないため、基本つかないのかもしれません。
URLの出力が相対URLなのと、URL#toString(Url.StringMode.FULL) で取得すると、
HTTPサーバーから、Proxyする際に、Domain名が安定しなさそうに思われたので、
domain名はResourceBundleから取得、
Urlも、pageクラスのmount URLから取得するように修正しました。

  • getCanonicalUrl
        /**
         * getCanonicalUrl
         *
         * @return
         */
        protected String getCanonicalUrl() {
            return ResourceBundle.getBundle("xyz.monotalk.festivals4partypeople.web.WicketApplication").getString("domainName")
                    + "/" +
                    RequestCycle.get()
                            .mapUrlFor(getClass(), null)
                            .toString();
        }
    

Behaviorクラスを作って、PageにADDする

BehaviorクラスのrenderHead で、canonicalタグを追加している記事がありましたので、
リンクを貼っておきます。


補足. ONE_PASS_RENDER で、Form Submit時に別ページへリダイレクトさせる

ONE_PASS_RENDER で うまくバージョン番号を消す方法は、検証しきれないですが、
Form をSubmitした後、
http://127.0.0.1:18080/festivals?25-1.IFormSubmitListener-f となるのを、http://127.0.0.1:18080/festivals/2 のバージョン番号なしの形式で、
遷移させることはできましたので、
その実装方を記載します。

  • RequestCycle#setResponsePage(Class<? extends IRequestablePage> pageClass, PageParameters parameters) を使って遷移させる。

以下の通り記載したところ、
ブラウザにURLが、http://127.0.0.1:18080/festivals?25-1.IFormSubmitListener-f で返されず、
http://127.0.0.1:18080/festivals/2 で返されるようになりました。

  • 修正前

            @Override
            protected void onSubmit() {
                PageParameters param = new PageParameters();
                param.add("id", 2);
                setResponsePage(new FestivalDetailPage(param));
            }
    

  • 修正後

            @Override
            protected void onSubmit() {
                PageParameters param = new PageParameters();
                param.add("id", 2);
                setResponsePage(FestivalDetailPage.class, param);
            }
    

何故URLの描画のされ方が変わるかというと、それぞれのメソッド内で指定している
RedirectPolicy が違うためでした。

  • RequestCycle#setResponsePage(IRequestablePage page)

        /**
         * Convenience method for setting next page to be rendered.
         * 
         * @param page
         */
        public void setResponsePage(IRequestablePage page)
        {
            if (page instanceof Page)
            {
                ((Page) page).setStatelessHint(false);
            }
            // RenderPageRequestHandler.RedirectPolicy.AUTO_REDIRECT を指定  
            scheduleRequestHandlerAfterCurrent(new RenderPageRequestHandler(new PageProvider(page),
                RenderPageRequestHandler.RedirectPolicy.AUTO_REDIRECT));
        }
    

  • RequestCycle#setResponsePagesetResponsePage(Class<? extends IRequestablePage> pageClass, PageParameters parameters)

        /**
         * Convenience method for setting next page to be rendered.
         * 
         * @param pageClass
         *              The class of the page to render
         * @param parameters
         *              The query parameters for the page to be rendered
         */
        public void setResponsePage(Class<? extends IRequestablePage> pageClass,
            PageParameters parameters)
        {
            //RenderPageRequestHandler.RedirectPolicy.ALWAYS_REDIRECT を指定
            setResponsePage(pageClass, parameters, RenderPageRequestHandler.RedirectPolicy.ALWAYS_REDIRECT);
        }
    

AUTO_REDIRECT だと、条件に合致すれば、リダイレクトする。しなければフォワード。6
ALWAYS_REDIRECT だと 必ずリダイレクトされるようになります。

長くなりましたが、以上です。7

[1] URLの後ろに付与されるの?0、とか?23 のことです。
[2] MountedMapperWithoutPageComponentInfo 作成しなくても期待している動作をします。
[3] http://127.0.0.1:18080/festivals?25-1.IFormSubmitListener-fから302リダイレクトされてます。
[4] 個人の勝手な予想に対して、想定外の動きになっているという意味で。。
[5] アプリケーションデプロイ時のLB切り離しのケースを除くと思います。
[6] ONE_PASS_RENDER の場合は、リダイレクトの対象外になります。
[7] 似たようなメソッドで遷移の仕方が異なるのは少し解せませんが、勉強にはなりました。

コメント


カテゴリー