스프링 모바일

4 minute read

개요

HTTP 요청을 하는 사용자의 디바이스는 데스크탑, 모바일, 테블릿으로 나눌 수 있는데 HTTP 요청 헤더를 보고 사용자의 디바이스 정보를 알 수 있다.

스프링에서는 HTTP 요청을 보고 디바이스를 판별하는 작업을 간편하게 만들어주고, 더 나아가서 디바이스 기기 마다 다른 URL로 리다이렉트 할 수 있는 기능도 제공한다.

HTTP 헤더 값을 확인하여 모바일 기기 감지하기

스프링 모바일을 사용하지 않고 HTTP 헤더의 User-Agent 값을 분석하여 모바일 기기를 감지할 수도 있지만, 모든 기기에서 전송하는 User-Agent 값을 분석하는 일은 불완전하기 때문에 스프링 모바일을 사용하여 모바일 기기를 감지하는 것이 바람직하다.

User-Agent 헤더값을 필터링 하여 모바일 기기를 감지하는 코드

public class DeviceResolverRequestFilter extends OncePerRequestFilter {

    private static final String CURRENT_DEVICE_ATTRIBUTE = "currentDevice";

    private static final String DEVICE_MOBILE = "MOBILE";
    private static final String DEVICE_TABLET = "TABLET";
    private static final String DEVICE_NORMAL = "NORMAL";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String userAgent = request.getHeader("User-Agent");
        String device = DEVICE_NORMAL;

        if (StringUtils.hasText(userAgent)) {
            userAgent = userAgent.toLowerCase();
            if (userAgent.contains("android")) {
                device = userAgent.contains("mobile") ? DEVICE_NORMAL : DEVICE_TABLET;
            } else if (userAgent.contains("ipad") || userAgent.contains("playbook") || userAgent.contains("kindle")) {
                device = DEVICE_TABLET;
            } else if (userAgent.contains("mobil") || userAgent.contains("ipod") || userAgent.contains("nintendo DS")) {
                device = DEVICE_MOBILE;
            }
        }
        
        request.setAttribute(CURRENT_DEVICE_ATTRIBUTE, device);
        filterChain.doFilter(request, response);
    }
}

스프링 모바일을 이용해 기기 감지하기

스프링 모바일의 DeviceResolverHandlerInterceptor 클래스는 사용하면 모바일 기기의 감지를 쉽게 구현할 수 있다. 다음 코드는 DeviceResolverHandlerInterceptor 클래스인데 HTTP 요청을 해석하여 Device 객체로 반환하고 있다.

public class DeviceResolverHandlerInterceptor extends HandlerInterceptorAdapter {
    private final DeviceResolver deviceResolver;

    public DeviceResolverHandlerInterceptor() {
        this(new LiteDeviceResolver());
    }

    public DeviceResolverHandlerInterceptor(DeviceResolver deviceResolver) {
        this.deviceResolver = deviceResolver;
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Device device = this.deviceResolver.resolveDevice(request);
        request.setAttribute("currentDevice", device);
        return true;
    }
}

DeviceResolverHandlerInterceptor 사용하기 위해서는 WebMvc 인터셉터에 등록한다.

DeviceResolverHandlerInterceptor를 WebMvc인터셉터에 추가하기

@Configuration
public class MobileConfiguration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DeviceResolverHandlerInterceptor());
    }
}

Device 객체를 사용하여 디바이스 감지하기

@GetMapping("/home")
public String home(HttpServletRequest request) {
    
    Device device = (Device) request.getAttribute("currentDevice");
    device.getDevicePlatform(); // eunm DevicePlatForm : IOS, ANDROID, UNKOWN
    device.isMobile(); // boolean
    device.isNormal(); // boolean
    device.isTablet(); // boolean
    return "home";
}

디바이스 기기 정보를 선택하고 저장하기

스프링 모바일에서는 사용자로부터 디바이스 기기 정보를 입력받고, 이를 저장해 두는 특별한 인터셉터를 제공한다. 이 인터셉터의 구현체가 SitePreferenceHandlerInterceptor 클래스인데 이를 사용하면 요청 파라미터(site_preference)를 통해 디바이스 정보를 선택받고, 선택받은 값을 SitePreference 열거형 값으로 저장 할 수 있다.(기본 저장소는 쿠키)

유저의 로케일을 선택받고 쿠키에 저장하는 LocaleChangeResolver 인터페이스와 닮아 있다!

SitePreference 열거형

public enum SitePreference {
    ...
    NORMAL
    ...
    MOBILE
    ...
    TABLET
    ...
}

SitePreferenceHandlerInterceptor는 WebMvc에 추가해주면 바로 사용할 수 있다.

SitePreferenceHandlerInterceptor 추가하기

@Configuration
public class MobileConfiguration extends WebMvcConfigurationSupport {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        ...
        registry.addInterceptor(new SitePreferenceHandlerInterceptor());
    }
}

샘플코드 : 디바이스 감지하기와 입력받은 디바이스 값 쿠키에 저장하기

기기 정보에 따라 뷰 렌더링 하기

방법1 수동으로 뷰 렌더링 선택하기

다음과 같이 HTTP 요청에서 Device 정보를 읽고 읽어들인 디바이스 정보를 분석하여 각각 다른 뷰를 내려주도록 코딩할 수 있다.

@GetMapping("/home")
public String home(HttpServletRequest request) {
    Device device = DeviceUtils.getCurrentDevice(request);
    if (device.isMobile()) {
        return "mobile/home";
    } else if (device.isTablet()) {
        return "tablet/home";
    } else {
        return "home";
    }
}

다음과 같이 WebMvc 구성에 DeviceHandlerMethodArgumentResolver를 빈으로 등록하면 Device 값을 컨트롤러 메서드의 인수로 전달 받을 수 있다.

@Configuration
public class MobileConfiguration extends WebMvcConfigurationSupport {
    ...
    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new DeviceHandlerMethodArgumentResolver());
    }
}
@GetMapping("/home")
public String home(Device device) {
    if (device.isMobile()) {
        return "mobile/home";
    } else if (device.isTablet()) {
        return "tablet/home";
    } else {
        return "home";
    }
}

방법2 뷰 렌더링 선택 자동화 하기

스프링 모바일의 LiteDeviceDelegatingViewResolver를 이용하면 Device 값과 SitePreferences값을 해석하여 뷰 이름을 진짜 뷰 리졸버로 넘겨 주기 전에 추가 접두어/접미어를 덧붙일 수 있다.

LiteDeviceDelegatingViewResolver를 빈으로 등록

@Bean
public ViewResolver mobileViewResolver() {
    LiteDeviceDelegatingViewResolver delegatingViewResolver = new LiteDeviceDelegatingViewResolver(viewResolver());
    delegatingViewResolver.setOrder(1);
    delegatingViewResolver.setMobilePrefix("mobile/");
    delegatingViewResolver.setTabletPrefix("tablet/");
    return delegatingViewResolver;
}

LiteDeviceDelegatingViewReoslver 클래스에서 뷰를 해석하는 코드

public class LiteDeviceDelegatingViewResolver extends AbstractDeviceDelegatingViewResolver {
...
	@Override
	protected String getDeviceViewNameInternal(String viewName) {
    	if (ResolverUtils.isNormal(device, sitePreference)) {
			resolvedViewName = getNormalPrefix() + viewName + getNormalSuffix();
		} else if (ResolverUtils.isMobile(device, sitePreference)) {
			resolvedViewName = getMobilePrefix() + viewName + getMobileSuffix();
		} else if (ResolverUtils.isTablet(device, sitePreference)) {
			resolvedViewName = getTabletPrefix() + viewName + getTabletSuffix();
		}
    }
}

LiteDeviceDelegatingViewResolver로 디바이스 별로 뷰를 선택하는 작업을 위임했으므로, 다음과 같이 핸들러 매핑 메소드는 간단해진다.

@GetMapping("/home")
public String home() {
    return "home";
}

샘플코드 : 스프링 모바일 / 디바이스에 따라 다른 뷰 랜더링 하기

디바이스에 따라 사이트 스위칭 구현하기

스프링 모바일의 SiteSwitcherHandlerInterceptor 클래스는 감지한 Device값에 따라 모바일 전용 웹 사이트로 전환할 수 있다. 다음과 같은 팩토리 메소드를 사용하여 리다이렉트 되는 URL을 설정하고 SiteSwitcherHandlerInterceptor 객체를 생성한다.

SiteSwitcherHandlerInterceptor 팩토리 메소드

팩토리 메서드 설명
mDot() m.으로 시작하는 도메인으로 리다이렉트 합니다.
ex) http://www.yourdomain.com -> http://m.yourdomain.com
dotMobi() .mobi로 끝나는 도메인으로 리다이렉트 합니다.
ex) http://www.yourdomain.com -> http://www.yourdomain.mobi
urlPath() 기기마다 서로다른 컨텍스트 루트를 설정해서 정해진 URL 경로로 리다이렉트 합니다.
ex) http://www.yourdomain.com -> http://www.yourdomain.com/mobile
standard() 웹 사이트의 도메인을 모바일, 태블릿, 그리고 일반 버전별로 지정 가능한 가장 유연한 팩토리 메서드 입니다.

mDot, dotMobi, urlPath는 모두 standard 메소드를 사용한다.

urlPath() 펙토리 메소드를 사용하여 사이트 스위칭 구현하기

SiteSwitcherHandlerInterceptor 클래스를 urlPath() 펙토리 메소드로 생성하고 빈으로 등록하고 WebMvc의 인터셉터로 등록한다.

디바이스별 리다이렉트 주소

  • 모바일: /m
  • 테블릿: /t
  • 노멀: /
@Bean
public SiteSwitcherHandlerInterceptor siteSwitcherHandlerInterceptor() {
    return SiteSwitcherHandlerInterceptor.urlPath("/m", "/t", "/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(siteSwitcherHandlerInterceptor());
}

디바이스별로 리다이렉트되는 요청을 받을 수 있도록 컨트롤러를 각각 만들어 준다.

@Controller
@RequestMapping("/m")
public class MobileController {
    @GetMapping("/home")
    public String home() {
        return "home";
    }
}

@Controller
@RequestMapping("/t")
public class TabletController {
    @GetMapping("/home")
    public String home() {
        return "home";
    }
}

@Controller
public class NormalController {
    @GetMapping("/home")
    public String home() {
        return "home";
    }
}

샘플코드 : 스프링 모바일 / 디바이스에 따라 사이트 스위칭 구현하기


참고도서
제목: 스프링5레시피(4판)
지은이: 마틴데니엄, 다니엘 루비오, 조시 롱
옮긴이: 이일웅
펴낸곳: 한빛미디어