Spring MVCでは、コントローラに@RequestMappingというアノテーションをつけるだけで、HTTPのリクエストを良きメソッドに振り分けてくれてとても便利です。
サブドメインを振り分けられない
WEB APIのURL設計のトレンドはこれだ!WEB APIのURL設計まとめに書いたように、WEB APIはサブドメインを分けて提供する場合が多いです。
しかしSpring MVCの@RequestMappingはサブドメインがwwwかapiかなどでマッピングする機構は持っていません。
これには、RequestMappingHandlerMappingクラスを拡張して独自にマッピング処理を実装するという手があるのですが、もうひとつマッピングの前段階としてFilterを用いるという方法が簡単です。
ServletのFilterを用いて、HTTPヘッダを追加する。
@RequestMappingは、HTTPのヘッダパラメータで振り分けをすることができます。
@RequestMapping(value = "/", method = RequestMethod.GET, headers = "X-SUBDOMAIN=api") public String index() { return "index"; } |
このように、独自のヘッダパラメータを創作してしまいます。
サブドメインがapiだった場合に、リクエストのマッピング処理の直前でヘッダパラメータを追加すれば、このメソッドにマッピングされるので、サブドメインによるリクエスト振り分けが実現できます。
実際にやってみる。
web.xmlにFilterの設定を追加します。SubdomainFilterという名前で、「特定のサブドメインを見つけたらそれをヘッダパラメータに追加する」ということをさせます。
<filter> <filter-name>subdomainFilter</filter-name> <filter-class>in.katty.servlet.filter.SubdomainFilter</filter-class> <init-param> <param-name>subdomains</param-name> <param-value>api,www</param-value> </init-param> </filter> <filter-mapping> <filter-name>subdomainFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping> |
SubdomainFilterクラスの中身を丸ごと貼ります。基本は、Filterのインタフェースにのっとって、処理を実装するだけです。
public class SubdomainFilter extends GenericFilterBean { private String subdomains; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { SubdomainHttpServletRequestWrapper httpRequest = new SubdomainHttpServletRequestWrapper((HttpServletRequest) request); String subdomain = getSubdomain(httpRequest); if (subdomain != null) httpRequest.addHeader("X-SUBDOMAIN", subdomain); filterChain.doFilter(httpRequest, response); } private String getSubdomain(HttpServletRequest httpRequest) { List<String> subdomains = new ArrayList<String>(); if (this.subdomains != null) { subdomains = Arrays.asList(this.subdomains.split("\\s*,\\s*")); } for (String subdomain : subdomains) { if (httpRequest.getServerName().startsWith(subdomain + ".")) return subdomain; } return null; } public String getSubdomains() { return subdomains; } public void setSubdomains(String subdomains) { this.subdomains = subdomains; } } |
これで、Spring MVCのリクエストマッピングの処理の前に、サブドメインがヘッダパラメータに変換されるFilter処理が実行でき、サブドメインによるメソッドの振り分けが可能になります。
HTTPリクエストされる際に意図的に、このX-SUBDOMAINのようなパラメータを混ぜ込むことで、Host名と一致しない処理になる危険性はありますが、気になるようであればFilter以外で挿入されたX-SUBDOMAINヘッダを除外すれば良いです。
おまけ: 依存クラス
HttpServletRequestはヘッダパラメータを追加することができないので、ラッパークラスを作っています。
public class SubdomainHttpServletRequestWrapper extends HttpServletRequestWrapper { private Map<String, String> headers = new HashMap<String, String>(); public SubdomainHttpServletRequestWrapper(HttpServletRequest request) { super(request); } public Enumeration<String> getHeaderNames() { HttpServletRequest request = (HttpServletRequest) getRequest(); List<String> headerNames = new ArrayList<String>(); Enumeration<String> enumeration = request.getHeaderNames(); while (enumeration.hasMoreElements()) headerNames.add(enumeration.nextElement().toString()); Iterator<String> iterator = headers.keySet().iterator(); while (iterator.hasNext()) headerNames.add(iterator.next()); return Collections.enumeration(headerNames); } public void addHeader(String name, String value) { headers.put(name, new String(value)); } public String getHeader(String name) { if (headers.containsKey(name)) return headers.get(name); return ((HttpServletRequest) getRequest()).getHeader(name); } } |
このブログは、「Technology: Managing multiple Domain and Sub Domain on Google App Engine for Same Application」を参考にして書かれました。助かりました。
One comment
Pingback: Spring MVCの@RequestMappingでパスにピリオドを含めると拡張子とみなされる問題。 | 三度の飯とエレクトロン