SAStrutsでRuby on Railsのようなルーティングを実現するためのライブラリです。
以下のようなルーティング用のXMLを用意します。
<routes>
<root to="index#index"/>
<match path="/user/list/:pageNo" controller="admin.User" action="index"/>
<match path="/user/:id" controller="admin.User" action="show"/>
<controller name="blog">
<match path="post" to="post"/>
</controller>
<match path="/:controller/:action/:id"/>
</routes>
コントローラ(sastrutsでいうところのAction)は、アクションパッケージ以降の名前で書きます。サフィックスのActionは不要です。 すなわち、com.exampleがルートパッケージの場合は、admin.Userと書くとcom.example.action.admin.UserActionのアクションクラスを 指すことになります。
match は基本のルーティング設定で、path属性にマッチすると、そのコントローラ、アクションにforwardされます。 controllerタグでコントローラごとの設定をまとめることができます。
ルーティングをグループ化するために、namespaceを使うことができます。
<namespace name="admin">
<match path="posts" to="Posts#index"/>
</namespace>
と書くと、/admin/posts のパスが admin.PostsAction にマッチするようになります。
また、パスには/adminを付けたくないけれども、admin.PostsActionにマッチさせたい場合は、
<scope module="admin">
<match path="posts" to="Posts#index"/>
</scope>
とscopeディレクティブとmodule属性を使ってActionに付加するパッケージ名を指定することができます。
逆に、パスに/adminを付けるが、Actionにパッケージ名を付加したくない場合、すなわちコンテキストルートや リバースプロキシのパスプリフィックスに対応させるようなケースでは、
<scope name="admin">
<match path="posts" to="Posts#index"/>
</scope>
と書きます。
:controller と :action は特別なパラメータで、:controllerはアプリケーション内のActionクラスにマッピングされ、 :actionはActionクラスのメソッドにマッピングされます。 したがって、デフォルトのSAStrutsのルーティングは以下の1行で表現されます。
<match path=":controller/:action" />
任意のパラメータをURLに付け加えることができます。
<match path=":controller/:action/:id/:userId"/>
というルーティングでは、例えば /photos/show/1/2 というリクエストが来ると、PhotosActionのshowメソッドに転送され、 Actionのプロパティ、またはActionFormのid属性に"1"が、userId属性に"2"が設定されます。
namespaceの中で:controllerセグメントを使うことはできません。そうする必要があれば、:controllerを
固定のパスを途中に挟み込むことができます。
<match path=":controller/:action/:id/with_user/:userId"/>
というルーティングは、 /photos/show/1/with_user/2 のようなURLに対応し、
{ :controller => "Photos", :action => "show", :id => "1", userId => "2" }
というパラメータ構成になります。
当然ながら任意のパラメータをクエリ文字列から取得することもできます。
<match path=":controller/:action/:id"/>
というルーティングは、 /photos/show/1?userId=2 のようなURLに対応し、
{ :controller => "Photos", :action => "show", :id => "1", userId => "2" }
というパラメータ構成になります。
パラメータが特定の形式の場合だけ、ルーティングさせたいという場合は、以下のように正規表現によるマッチングが可能です。
<match path="posts/:year/:month" to="Posts#index">
<requirements>
<requirement name="year" value="\d{4}" />
</requirements>
</match>
この場合、yearパラメータが数字4桁の場合だけ、Posts#indexにルーティングされるようになります。
また、マッチングパラメータは1つのパスセグメントの中に複数持つことができます。その場合はパラメータを括弧で囲ってください。
<match path="images/(:width)x(:height)" to="Images#show">
<requirements>
<requirement name="width" value="\d+"/>
<requirement name="height" value="\d+"/>
</requirements>
</match>
パラメータがパス要素として渡されない場合に、パラメータにデフォルト値を設定できます。
<match path="prefecture/:name" to="Prefecture#show"/>
<defaults>
<default name="name" value="tokyo"/>
</defaults>
</match>
のように定義されていると、 /prefecture のようなURLに対応し、
{ :controller => "Prefecture", :action => "show", :name => "tokyo" }
というパラメータ構成になります。
HTTPメソッドによってルーティングを分けたい場合には
<get path="photos" to="Photos#list"/>
<post path="photos" to="Photos#update"/>
のように書いておくと、/photos へGETでアクセスすると Photos#list へ、POSTでアクセスすると Photos#update へ転送されます。
<match path="photos/*other" to="Photos#unknown"/>
というルーティングは、photos/12 や /photos/long/path/to/12 というパスにマッチし、otherパラメータに "12" または "long/path/to/12" がセットされます。
ワイルドカードはルートのどこにあっても構いません。
<match path="books/*section/:title" to="Books#show"/>
は、books/some/section/last-words-a-memoir のパスにマッチし、sectionパラメータに"some/section"が、 titleパラメータに"last-words-a-memoir" がセットされます。
リンクをJSPなどのViewで出力する場合、urlForのヘルパーメソッドを使って以下のように指定できます。
${ar:urlFor("admin.User#index?pageNo=2")}
こう書いておくと、ルーティング設定にしたがって、
/user/list/2
というURLを出力してくれます。
以上の設定をしたうえで、sastrutsのRoutingFilterの代わりにAdvancedRoutingFilterを使うと、上記ルーティング定義に したがって、リクエストをアクションメソッドに転送してくれるようになります。
web.xmlにて、次のようにroutingfilterを設定されているところを、
<filter>
<filter-name>routingfilter</filter-name>
<filter-class>org.seasar.struts.filter.RoutingFilter</filter-class>
<init-param>
<param-name>jspDirectAccess</param-name>
<param-value>false</param-value>
</init-param>
</filter>
次のようにAdvancedRoutingFilterを使うように書き換えてください。
<filter>
<filter-name>routingfilter</filter-name>
<filter-class>net.unit8.sastruts.AdvancedRoutingFilter</filter-class>
<init-param>
<param-name>jspDirectAccess</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>routes</param-name>
<param-value>/WEB-INF/routes.xml</param-value>
</init-param>
<init-param>
<param-name>checkInterval</param-name>
<param-value>-1</param-value>
</init-param>
<init-param>
<param-name>contextSensitive</param-name>
<param-value>false</param-value>
</init-param>
</filter>
追加されているパラメータについて説明します。
routes は、ルート定義ファイルのパスを設定します。webapp以下からのパスで記述してください。
checkInterval は、ルート定義ファイルの更新をチェックしにいく間隔(秒数)を設定します。この間隔でチェックし更新があれば 定義ファイルをリロードします。0を設定すると常に更新チェックするようになりますが、これは負荷が高いため本番環境では 避けるようにしてください。このパラメータを設定しない、またはマイナスの値を設定すると更新チェック自体がおこなわれず、 アプリケーションを再起動しない限りルート定義はリロードされません。
contextSensitive は、コンテキストパスを意識したマッチング/パス生成するかどうかのフラグです。これがtrueの場合は、 コンテキストパスより後ろのパスを使ってルートのマッチングをし、UrlRewriterを使ってパスを生成する際に自動的に コンテキストパスが補われるようになります。デフォルトはfalseです。
SAStruts標準のルーティング以上のことを、このAdvanced Routesを使って実現したいということは、コンテキストパス自身も邪魔になることが多いでしょう。 したがってcontextSensitive パラメータのデフォルトもfalse になっている訳ですが、さらに言うと大抵はApache - Tomcat の構成となっており、 ApacheでURLをリライトした後にTomcatにリクエスト転送するので、Tomcatで受け取るURLなんて意識せず、Apacheで受けたまんまのURLでルート定義を 書けると素敵です。
しかし、Tomcat側からは転送されてきたURLしか分からないので、Apache側から当初のリクエストURLも送ってもらう必要があります。これは、mod_rewrite の設定で、以下のようにREQUEST_URIをリクエストヘッダに入れて、Tomcat側に転送することで実現できます。
RewriteEngine On
RewriteRule .* - [E=X_REQUEST_URI:%{REQUEST_URI}]
RequestHeader set X-Request-URI "%{X_REQUEST_URI}e"
Advanced Routesでは、以下のような設定をしておくと、このヘッダからREQUEST_URIを取得するようになります。
<init-param>
<param-name>requestUriHeader</param-name>
<param-value>X-Request-URI</param-value>
</init-param>
SAStruts Advanced Routes はApache License 2.0 の元に配布されます。