たとえば生サーブレットで作ったオレオレフレームワークで、掲示板のひとつも作ろうと思ったとする。
オレオレフレームワークは、すべてのリクエストをひとつのサーブレットで受け付け、その先は独自のオレオレルータがルーティングして、各種処理を行って、HTMLを返す、ということをやりたいとする。
その場合、すべてのリクエストをOreOreFrameworkServletに渡せばいいんだな、ということでweb.xmlに以下のように書くと、
<servlet> <servlet-name>OreOreFramework</servlet-name> <servlet-class>com.kmaebashi.framework.OreOreFrameworkServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>OreOreFramework</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
cssとかアイコン画像とかの静的ファイルへのリクエストまでOreOreFrameworkServletに流れてしまう。これは困る。
たとえば拡張子で判定して、*.cssだけ除外する、といった書き方は、web.xmlの
「"web.xml" "url-pattern"」でGoogle検索しようとしたら、「除外」ってサジェストが出たぐらいで、普通に需要はありそうなのだけど。
で、いろいろ検索すると、Stack overflowのページがいくつか見つかって、サーブレットフィルタを使えば行けるらしい……のだが。
https://stackoverflow.com/questions/8658949/in-a-web-xml-url-pattern-matcher-is-there-a-way-to-exclude-urlsstackoverflow.com
https://stackoverflow.com/questions/50056316/how-to-exclude-specific-path-from-web-xmlstackoverflow.com
サーブレットフィルタで、拡張子なりディレクトリなりで静的ファイルを判定した後どうすればよいか、書いてない。上のふたつの質問の解答はどちらも肝心のところが「// do something」とか「//do something else」になってる。しかもこのふたつの解答は、if文の条件判定がそれぞれ逆になっている。なんだこりゃ。
利用者から見えるパスに影響を与えてよいのなら、たとえば掲示板のアプリケーション名がbbsとして、一覧表示のURLを本来はこうしたいところ、
https://<ホスト>/bbs/list
サブディレクトリを挟んで
https://<ホスト>/bbs/app/list
とか、拡張子.doを付けて
https://<ホスト>/bbs/list.do
とかに変えれば、url-patternに"/app/*"や"*.do"を書いてそういうリクエストだけをOreOreFrameworkServletに渡せばよい。
その上で、webapps/bbs直下にbbs.cssを置けば、https://<ホスト>/bbs/bbs.cssでCSSは取得できる。
でも、利用者から見えるところなのでやっぱり避けたい。/appとか入れるとCSSとかとの相対パスが狂うし、.doなんて懐かしのStrutsアプリみたいだ。
サーブレットフィルタは、サーブレットとかJSPとかの手前に挟まる。サーブレットコンテナ(Tomcatとか)が最初のサーブレットフィルタのdoFilter()を呼び出すので、次のフィルタとかサーブレットとかに処理を渡すなら、引数として与えられたchainのdoFilter()を呼べばよい。ではdoFilter()を呼ばなかったらどうなるかと言うと、そこでそのリクエストに対する処理は終わってしまう。拡張子が.cssでなければdoFilter()を呼んで(OreOreFrameworkServletに処理を渡して)、.cssだったら何もしない、というフィルタにすると、CSSは取得できない。戻り値か何かで「このurl-patternにマッチしなかったことにする」という指定ができればよいのだが、あいにくそんな機能はないし、doFilter()の戻り値はvoidだ。静的ファイルだと判定したら、自分でそのファイルを開いて、拡張子からContent-Typeを指定してファイルの中身をレスポンスに流し込めばよいのだろうが、さすがにそこまでやるのも嫌だ。
さらにいろいろ探して見つけたページがこちら。
https://stackoverflow.com/questions/13521946/how-to-prevent-static-resources-from-being-handled-by-front-controller-servlet-wstackoverflow.com
そこからリンクされているページ。内容は基本的に上記と同じ。
https://stackoverflow.com/questions/870150/how-to-access-static-resources-when-mapping-a-global-front-controller-servlet-on/3593513#3593513stackoverflow.com
結局、CSSなりを返したいと思ったら、フィルタではdoFilter()を呼べばよくて、逆にサーブレットに渡したい場合は/app/なり.doなりをくっつけてフォワードして、web.xmlのurl-patternにはそれをくっつけたのを書いておく、という方法でどうやらできた。下の例では、サーブレットに.doをくっつけているが、これは利用者には見えない(OreOreFrameworkServletには渡ってしまうが、まあ我慢する)。
web.xml
<filter> <filter-name>staticresourcefilter</filter-name> <filter-class>com.kmaebashi.framework.StaticResourceFilter</filter-class> </filter> <filter-mapping> <filter-name>staticresourcefilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>OreOreFramework</servlet-name> <servlet-class>com.kmaebashi.framework.OreOreFrameworkServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>OreOreFramework</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
StaticResourceFilter.java
import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; public class StaticResourceFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest)request; String path = req.getRequestURI().substring(req.getContextPath().length()); if (path.endsWith(".css") || path.endsWith(".html")) { chain.doFilter(request, response); } else { request.getRequestDispatcher(path + ".do").forward(request, response); } } }
サーブレットなんて20年以上前からあるものだろうに、「url-patternに除外のルールを書く」くらいの機能、標準で付けておいてほしいよなあ。