Wicketアプリケーションを外部公開する際の注意点

弊社インフォマートではサービス開発時、WebアプリケーションフレームワークとしてWicketを使用しています。　　　                               
ほとんどのサービスが会員向けですが、一部外部公開しているサービスがあり、外部公開時にいくつか注意点があることが分かりました。     
この文書には、Wicketを外部公開する際に気を付けるべき注意点を記載します。      　　　

------------------------------------------------------------
## まとめ 

* Wicketで作ったアプリケーションを外部公開する場合の注意点がいくつかあり、そのポイントを気にしないと作り手側、ユーザー側が予期しない結果となる。           
* 外部公開するページには、canonicalタグを付与して、正規のURLを検索エンジンのボットに認識させる。
* リンクはBookmarkableLinkを使用する。                   
* 不要なリダイレクトはRenderStrategyの変更で対応できる。
* Formは一時的なURLのほうがセキュリティ的に都合が良い。FormのURLはrobots.txtでdisalowを設定する。    
* 外部公開前提の場合は、Statelessなページになるようなコーディングを心掛ける。   

------------------------------------------------------------
## そもそもWicketとは？                             

[Apache Wicket - Wikipedia](https://ja.wikipedia.org/wiki/Apache_Wicket) を引用します。    
>Apache Wicket（アパッチ・ウィケット）は、Apacheソフトウェア財団のApache Wicketプロジェクトにて開発されているオープンソースのJava Webアプリケーションフレームワークである。

コンポーネント指向のWebフレームワークで、似たような設計思想のフレームワークとして以下があります。

* JSF     
* Vaadin     
* Tapestry            

Genericな解説は他の記事や、[公式ドキュメント](https://ci.apache.org/projects/wicket/guide/9.x/single.html)に記載があるのでここではとくに言及しませんが、個人的には以下のような感想を持っています。   

* Androidと似ている。どちらもSwingの設計思想をベースにしている。
* Spring 、Struts等のアクションベースのフレームワークを触った後だと、フレームワークの挙動の意味を理解するのに時間がかかる。
* Reactとコンポーネントの考え方は変わらないので、WicketからReactに行くのはそこまで違和感ない気がする。              

-----------------------------------------------------------
## 動作確認に使用したWicketのVersionについて

WicketはVersionごとで公開APIのIFに結構な差異があります。         
動作確認には、以下のVersionのWicketを使用しました。    

```xml
<!-- https://mvnrepository.com/artifact/org.apache.wicket/wicket-core -->
<dependency>
    <groupId>org.apache.wicket</groupId>
    <artifactId>wicket-core</artifactId>
    <version>1.5.5</version>
</dependency>
```

Wicket の Version が `1.4x - 1.5x` は当文書で記載の動作をするかと思いますが、最新Versionでは動作が異なる可能性があります。    
別途動作確認をすることをお勧めします。    

-----------------------------------------------------------
## Wicketを外部公開する上で意識すべき前提動作
以下、Wicketで作成したWebアプリケーションを外部公開するうえで意識すべき前提動作について記載します。

### デフォルト動作だとページ表示時にリダイレクトが走る。           
Wicketはブラウザからのリクエストを受け取ると、デフォルト動作で以下のような挙動をします。   
1. リクエストURLに対応したPageクラスをインスタンス化し、メモリ上に格納する。
2. リクエストURLにVersion番号を付与し、ブラウザにHTTPコード `302`でリダイレクトさせる。
3. ブラウザがVersion番号付きURLにアクセスすると、メモリ上に格納したPageクラスを取り出しHTTPコード `200`でブラウザ描画したHTMLを返す。      

この挙動により、Wicketの生成するFormのURL、aタグのURLは一時的なURLとなり、CSRF対策にもなります。          
しかしながら、この動作が、SEO対策としては `✘` で、以下のような問題が発生します。    

* Google Search Consoleでは不要なリダイレクトを繰り返しているページがあると検知される。     
* Google Adsense設置ページでは不要なリダイレクトをしていると検知される。              

また、1度リダイレクトするということは、DNS Lookupが多く走ることになり、ページパフォーマンス観点でも `✘` かなと思われます。[^1]                 

[^1]リダイレクト後のアクセスは、メモリCacheに乗っている扱いになるので、レスポンスタイムの問題になることはありません。                            

Referrence Guideにも、この挙動についての記載があります。
[Wicket 8.x Reference Guide](https://ci.apache.org/projects/wicket/guide/8.x/single.html#_lost_in_redirection_with_apache_wicket)                 
デフォルト動作は `REDIRECT_TO_BUFFER `と呼ばれ、これ以外に他2種類モードがあります。       
Referrence Guideには、それぞれのモードのメリット、デメリットが記載されています。          

### URLのバージョン番号                                      
`デフォルト動作 だと ページ表示時にリダイレクトが走る `　に記載しましたが、`302` のリダイレクト時に、`?134` のようなバージョン番号がURLに付与されます。         
検索エンジンは基本的にパラメーター付きページを、別々ページであると認識します。   
実際のところは、上手くやってくれるのかもしれませんが、検索結果として悪影響がある可能性もあります。            

### Link、FormのURLの問題                                   
WicketでBookmarkableではないLink、Formを使用すると、以下のようなURLが生成されます。       
これはブラウザ単位に割り当てられる一時的なURLとなり、セキュリティ対策としては`◯`ですが、検索エンジンのボットのアクセスの場合、検索エンジンがこの一時的なURLをCacheしてしまいます。     

* Form     

```html
<form id="id2" method="post" action="./xxxxxxxxxxxxxx?0-2.IFormSubmitListener-headerPanel-resizePanel-formFont">
```

* Link　　　　　　

```html
<a href="./xxxxxxxxxxxxxx?0-3.ILinkListener-headerPanel-loginPanel-loginMenu-liLog_in_1-linkLogin">     
```

この結果、検索エンジンの検索結果には表示されるが、アクセスしても期待するページは表示されない状態になります。           

-----------------------------------------------------------
## 注意点                                       

気を付けるべきポイントは以下になります。               

### canonicalタグを付与する                          
`?123`とURLに付与されるVersion番号に対する対策です。                           
canonicalタグというタグをhead内に記載すると、コンテンツの正規URLを検索エンジンに明示できます。      
[canonicalタグとは？設置方法やよくある間違いも解説！｜アクセス解析ツール「AIアナリスト」ブログ](https://wacul-ai.com/blog/seo/internal-seo/canoical-tag/)    

付与の仕方は、WicketのVersionにより異なりますが、Wicket 8 であれば、`MetaDataHeaderItem#forLinkTag` を使用することで、付与可能です。    
[Wicket 8.x Reference Guide](https://ci.apache.org/projects/wicket/guide/8.x/single.html#_adding_resources_to_page_header_section)

### リンクはBookmarkableLinkを使用する。                   

ページにLinkを追加する際、Linkではなく、BookmarkableLinkを使うと、`@MountPath`で指定したURLをリンク文字列として使用できます。    

* **Pageクラスの`@MountPath`の指定例**        
* 
```java
    @MountPath("/news/search/top.page")
    public class NewsSearchTopPage extends Webpage {
```

* **BookmarkablePageLinkの使用例**      
* 
```java
    BookmarkablePageLink<NewsSearchTopPage> lnkTop = new BookmarkablePageLink<>("lnkTop", NewsSearchTopPage.class);
    addOrReplace(lnkTop);
```

`@MountPath` の使用方法は、[Wicket-Stuff Annotation の使い方 - Qiita](https://qiita.com/gishi_yama/items/80753ad8a39dd661b0fc) が参考になります。    

### RenderStrategyを変更して、リダイレクトが発生しないようにする                    
Wicketはデフォルトでページ表示時に必ずリダイレクトをする動作をしますが、`RenderStrategy` を設定することでこの挙動を変更できます。      
`ONE_PASS_RENDER`にすると、リダイレクトせずページを表示する動作になることから、検索エンジン対策のために対象ページを決定し挙動を変更しました。     

`RenderStrategy`の指定は、基本的にWicketのApplicationクラスで行い、war単位でのコントロールになります。                
弊社で実施したかったことは、一部のページのみの`RenderStrategy`の変更でしたので、以下Stack OverflowのQAを参考に一部のページのみの`RenderStrategy`を変更しました。    
[Is RenderStrategy.ONE_PASS_RENDER a reasonable way to get rid of page version parameters like ?1 in a Wicket application? - Stack Overflow](https://stackoverflow.com/questions/14585858/is-renderstrategy-one-pass-render-a-reasonable-way-to-get-rid-of-page-version-pa)                


### robots.txtでWicket生成する一時URLをdisalow設定する。    
単純なサイト内リンク等は、`BookmarkablePageLink` で検索エンジンに拾ってもらえばよいのですが、Submit時のURL等、一時リンクで問題URL、そもそも拾って欲しくないURLもあります。     
これら検索結果に表示されなくてもよいURLについては、robots.txtで以下のようにdisallow指定をしました。

```console
# disallow auto generate url in wicket
Disallow: /*IFormSubmitListener
Disallow: /*ILinkListener
Disallow: /*IBehaviorListener
```

### そもそも、Statelessにできないか検討する                           
外部公開前提のサイトで、Wicketを使用する場合は、Statelessにできないか検討するのもよいかと思います。               
Stalelessにすることができれば、バージョン管理のためのバージョン番号がURLに付与されることはなくなります。                     
[Wicketで真のステートレスページ - argius note](http://argius.hatenablog.jp/entry/20120204/1328332391)                             

ページがStatelessかどうかを確認するためのUtilityが提供されていますので、Stateless前提でコンポーネントを作る際は組み込んでもいいかもしれません。     
[wicket/wicket-devutils at master · apache/wicket](https://github.com/apache/wicket/tree/master/wicket-devutils)

-----------------------------------------------------------
## WicketのコミニティのSEOに対する認識    
WicketのコミニティもSEOとの相性の悪さを認識しているものと思われます。     
`Better SEO for stateful pages` というアイデアが記載されています。   
[Ideas for Wicket 8.0 - Apache Wicket - Apache Software Foundation](https://cwiki.apache.org/confluence/display/WICKET/Ideas+for+Wicket+8.0)

今後のリリースで、SEOに良い影響を与えるがリリースされる可能性もあるので、バージョンアップ情報は定期的に見ていくのが良いかと思われます。     

-----------------------------------------------------------
## 参考

* [Wicket での Redirect After Post](https://gside.org/blog/2008/05/20/lazyblorg-entry/)      
* [SEO - Search Engine Optimization - Apache Wicket - Apache Software Foundation](https://cwiki.apache.org/confluence/display/WICKET/SEO+-+Search+Engine+Optimization)                  
* [java - Wicket SEO: removing "?wicket:interface=..." from AjaxFallbackLinks - Stack Overflow](https://stackoverflow.com/questions/7152547/wicket-seo-removing-wicketinterface-from-ajaxfallbacklinks)          
* [Wicketのtipsメモ - public static void main](https://kishi.hatenadiary.org/entry/20080530/1212162092)