【Spring Boot】マッピング時に末尾のスラッシュに反応しなくなってた話
作成日時: 2023/06/24
更新日時: 2023/06/24
# 背景
ある時ふと、このサイトのURL末尾にスラッシュがある(ex. haroot.net/articles/)と反応しなくなっていたことに気づきました。。

# 調査
調べてみたらそれっぽいものが出てきました。
[setMatchOptionalTrailingSeparator](https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/pattern/PathPatternParser.html#:~:text=true%C2%A0%E3%81%8B%E3%82%89-,false%C2%A0%E3%81%AB%E5%A4%89%E6%9B%B4,-%E3%81%95%E3%82%8C%E3%81%BE)
どうやら`org.springframework.web`が`6.0`になってから、 **デフォルトで末尾のスラッシュは有り無しが区別されるようになったらしい。**
(余談)
`org.springframework.web`のバージョンなんていつあげたっけ?
と気になってので(自分がソース管理してる)githubで確認したところ、Spring Bootのバージョンを`2.5.6`→`3.0.1`にあげた際のプルリクにそれらしきものがいました。
```diff
-
+
```
# 対応
上記のドキュメントのリンクでも説明されているように、 **`/`の有無をこっそり許容すべきではないです(リダイレクト等を使ってスラッシュ有りと無しを明示的に分けるべき)。**
> 末尾のスラッシュの透過的なサポートは、プロキシ、サーブレット /Web フィルター、またはコントローラーを介して明示的なリダイレクトを構成することを推奨して、6.0 の時点で非推奨です。
今回はSpring Boot の`Filter`を使って、全リクエストの末尾のスラッシュがあれば外してリダイレクトするように修正していきます(普通逆な気もするけど)。
filterパッケージを作成して下記のソースを書いてみました。
```java
package com.haroot.home_page.filter;
import java.io.IOException;
import org.springframework.stereotype.Component;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class HomePageFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestUri = httpRequest.getRequestURI();
// ルート("/")以外で末尾にスラッシュがある場合
if (requestUri.matches(".+\\/$")) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
String redirectUri = requestUri.replaceAll("(.+)\\/$", "$1");
String queryStr = httpRequest.getQueryString();
if (queryStr != null) {
// クエリパラメータを再度付与
redirectUri += "?" + queryStr;
}
// 末尾のスラッシュを取り除いてリダイレクト
httpResponse.sendRedirect(redirectUri);
return;
}
// 後続処理へ
chain.doFilter(request, response);
}
}
```
## コード解説
### Filter
今回のメイントピックではないので軽く流しますが、`jakarta.servlet.Filter` の実装クラスを作成し、用意されている`doFilter`をオーバーライドして、後続処理(`chain.doFilter`)の前にパスを書き換える処理を追加しています。
### 正規表現
少し分かりづらい`if (requestUri.matches(".+\\/$"))`ですが、
正規表現を用いて(任意の1文字以上)(スラッシュ)が **末尾にある** パターンのみに絞っています。
"任意の1文字"があるのは、これがないとルート直下(haroot.net/) も対象になってしまうためです。
### クエリパラメータ
クエリパラメータ(`?text=123&abc=def`)の部分はuriには含まれないので、リダイレクト時に付与されていた場合に消されないよう、別途取得して付け直しています。
### 注意点
今回作成したフィルターは、フィルターがかかる範囲を指定してないので、全てのリクエスト( **ページ内のjsやcss取得時を含む** )に対してこの処理が走ります。
必要であれば`org.springframework.boot.web.servlet.FilterRegistrationBean`などを用いて範囲を絞ってください。
# 結果
haroot.net/aritcles/ にアクセスして...

記事一覧が表示できました!

ちゃんとリダイレクトされてますね。