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という名前で、「特定のサブドメインを見つけたらそれをヘッダパラメータに追加する」ということをさせます。
subdomainFilter
in.katty.servlet.filter.SubdomainFilter
subdomains
api,www
subdomainFilter
/*
REQUEST
ERROR
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 subdomains = new ArrayList();
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 headers = new HashMap();
public SubdomainHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
public Enumeration getHeaderNames() {
HttpServletRequest request = (HttpServletRequest) getRequest();
List headerNames = new ArrayList();
Enumeration enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements())
headerNames.add(enumeration.nextElement().toString());
Iterator 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」を参考にして書かれました。助かりました。
コメント
[…] @RequestMappingに関連するブログです。→「Spring MVCの@RequestMappingでサブドメインを振り分ける。」 […]