mike-neckのブログ

Java or Groovy or Swift or Golang

Spring MVC で日本語URLにリダイレクトするとURLが文字化けしてしまう場合の対処

Spring MVC で日本語を含むURLにリダイレクトすると…

@GetMapping(path = "bar")
String somePage() {
  return "redirect:/to/日本語url";
}
$ curl -v http://localhost:8080/foo/bar
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /it/japanese HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 302 
< Location: http://localhost:8080/to/???url
< Content-Language: ja-JP
< Content-Length: 0
< Date: Wed, 03 Jan 2018 17:03:47 GMT
< 
* Connection #0 to host localhost left intact

日本語urlにリダイレクトされてほしいけど、日本語がURLエンコーディングされてなくて残念なやつ。

ちょっと調べたのでメモ。

仕組み

  1. ViewResolver (実装は UrlBasedViewResolver/AbstractCachingViewResolver) の resolveViewName から createView を経て RedirectView に解決される。
  2. 解決された RedirectViewDispatcherServlet から View#render(Map, HttpServletRequest, HttpServletResponse) が呼び出される。
  3. RedirectView の継承元の AbstractViewrender メソッドの実装があり、そこで必要なモデルを抽出、ヘッダーに値を設定した後に renderMergedOutputModel を呼び出す。
  4. RedirectView#renderMergedOutputModel(Map, HttpServletRequest, HttpServletResponse) にて引数として返したurlにクエリーなどを付加したurlを作る。
  5. RedirectView で先ほどのurlを HttpServletResponse.sendRedirect(String) にそのまま流す。

単純には次のような形で返すURLをエンコーディングすればよいだけ。

@GetMapping(path = "bar")
String somePage() throws Exception {
  return "redirect:/to/" + URLEncoder.encode("日本語", "UTF-8") + "url" ;
}

とはいえ、日本語URLが大量にあるアプリケーションだと、それもつらいので、次のようなクラスを作ってリダイレクトしてみるとよいかもしれない。

public class EncodingRedirectView extends RedirectView {

  public EncodingRedirectView(final String url, final boolean contextRelative) {
    super(encodeUrl(url), contextRelative);
  }

  private static String encodeUrl(final String url) {
    return Arrays.stream(url.split("/")).map(unhandle(p -> URLEncoder.encode(p, "UTF-8"))).collect(joining("/"));
  }

  private interface UnHandleFunction<A, B> {
    B apply(final A a) throws Exception;
  }

  private static <A, B> Function<A, B> unhandle(final UnHandleFunction<? super A, ? extends B> function) {
    Objects.requireNonNull(function);
    return a -> {
      try {
        return function.apply(a);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    };
  }
}

むしろよい方法あったら、誰か教えて…


2018/01/04 9:42 追記

次のように RedirectAttributes を使う方法があるらしい

@GetMapping
String somePage(final RedirectAttributes attributes) {
  attributes.addAttribute("path", "日本語url");
  return "redirect:/to/{path}";
}