SpringBootのInterceptorでテンプレートをモバイルとデスクトップでわける

エキサイト株式会社のエンジニアの佐々木です。SpringBootとThymeleafを使ってリビルドしていますが、既存テンプレートがモバイル用とデスクトップ用でわかれているので、Interceptorを使って対応します。

前提

SpringBoot x Gradleを使います。

build.gradleの依存関係は下記のようになっています。

dependencies {

        implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
        implementation 'org.springframework.boot:spring-boot-starter-web'

        implementation 'org.springframework.boot:spring-boot-starter-validation'
        implementation 'org.springframework.session:spring-session-core'
        compileOnly 'org.projectlombok:lombok'
        developmentOnly 'org.springframework.boot:spring-boot-devtools'
        annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
        annotationProcessor 'org.projectlombok:lombok'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
        testImplementation 'io.projectreactor:reactor-test'

}

インターセプターの実装

今回はUser-Agentを使用して、モバイルかデスクトップかをわけていこうと思います。User-Agent内にモバイルと識別できる文字列があったらモバイルのディレクトリにします。

@Slf4j
@Configuration
public class WebTemplateInterceptor implements HandlerInterceptor {

    private static final Pattern MOBILE_USER_AGENT = Pattern.compile("(iphone|android)");
    private static final String MOBILE_TEMPLATE_DIRECTORY = "mobile";
    private static final String DESKTOP_TEMPLATE_DIRECTORY = "desktop";

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        if (modelAndView == null || response.getStatus() != 200) {
            HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
            return;
        }

        String header = request.getHeader("User-Agent").toLowerCase();   // ヘッダを取得
        Matcher matcher = MOBILE_USER_AGENT.matcher(header); // User-Agentで判定

        String templateFile = String.join("/"
                , matcher.find() ? MOBILE_TEMPLATE_DIRECTORY : DESKTOP_TEMPLATE_DIRECTORY
                , modelAndView.getViewName()); // mobile と desktop をprefixとして設定する

        modelAndView.setViewName(templateFile);
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

HandlerInterceptorインタフェースimplementsし、 必要なメソッドをoverrideして処理を追加します。インターセプターは3種類、preHandle(コントローラ処理開始前)postHandle(コントローラ処理終了後)afterCompletion(レスポンス返却後)になります。 今回は、templateのパスを変更するので、コントローラの処理が終わったあとのpostHandlerメソッドをoverrideしました。また、正常なHTTPステータスの時のみパスを変更するようにしています。エラーページについては、モバイルもデスクトップも同じテンプレートになっているのでこのように対応しています。

インターセプターを登録する

インターセプターを実装したら、DIコンテナに登録が必要になります。

@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

    private final WebTemplateInterceptor webInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(webInterceptor);
    }
}

WebMvcConfigurerインターフェースimplementsして、addInterceptorsをoverrideして、先程作成したInterceptorをDIして、登録します。これで設定完了になります。

テスト

テストしてみます。User-Agentに指定した文字列を投入すると下記のようにモバイルとデスクトップが振り分けられます。

SPの場合
$ curl http://localhost:8080/ -H 'User-Agent: iphone'
----
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>Mobile</h1>
<div><h2>common</h2></div>
</body>
</html>
----

PCの場合
$ curl http://localhost:8080/ -H 'User-Agent: sample'
----
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Sample</title>
</head>
<body>
  <h1>Desktop</h1>
  <div><h2>common</h2></div>
</body>
</html>
----

まとめ

レスポンシブが実装されていれば必要ないのですが、今回はこのような形で回避することにしています。この処理をすると、IntelliJでのThymeleafのコードジャンプが使えなくなるので、不便ですが仕方ありません。次のときはレスポンシブで作りたいところです。

最後に

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。

カジュアル面談はこちらになります! meety.net

募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com