スマホや携帯の標準ブラウザをnginxで振り分ける

# スマホや携帯の標準ブラウザを振り分ける

## 古いAndroidのブラウザは振り分けないといけない状況になる場合

「HTML4時代に構築された携帯電話向け自社サービスをHTML5ベースのスマホ向けサービスに切り替えよう」という話が社内で出た時の小ネタ。

スマホと一言で言ってもiPhoneなのかAndroidなのかで諸々検討事項はあるものの、iPhoneは多少古めの機種でも概ねバージョンアップされているのであまり問題は起きないと期待される。
ほとんどの場合問題となるのはAndroidではないだろうか。
Androidは端末の画面サイズとブラウザのHTML5対応状況がややこしいというのは割と知られているのではないかと思うが、中でも事態をややこしくしているのは「Androidの4.2以前と4.3以降で標準ブラウザが変わったこと」ではないかと思われる。
つまり、4.2までの標準ブラウザは「AOSPブラウザー」、4.3以降は「Chrome(ベースの)ブラウザー」であるという問題である。
4.3以降であれば実質的にChromeなのでいわゆる普通のHTML5で問題は無いのだが、4.2以前ではAOSPベースでメンテナンスが行き届いていないためHTML5対応が不十分である可能性があり、そのため別サイト(というかHTML4ベースの既存サイト)に誘導する必要があると考えられる。

これらを踏まえて、弊社環境では以下のようになる。

| 機種 | 振り分け先 | 
|:-----------|:------------|
|フィーチャーフォン|HTML4ベースの旧サイト|
|Android 4.2以前|HTML4ベースの旧サイト|
|Android 4.3以降|HTML5ベースの新サイト|
|iPhone|HTML5ベースの新サイト|

## Android 4.4.2の混乱

それでは実際に振り分ける判断基準をどうするかといった場合、一般的にはUser-Agentを見て振り分けることになると思われる。
単純に考えればAndroidのバージョン情報を拾って…となるのだがここにも一つ問題点がある。
一部のスマートフォンではAndroid 4.2から4.4へバージョンアップが行われた際に、「標準ブラウザがAOSPのままになっている」機種があるのである。
そのため、「単純にAndroid 4.3を境目にして振り分ける」という事が困難になっている。

### 例 : HTC J one (HTL22) と isai (LGL22)

HTC J one (HTL22)では標準搭載のブラウザは以下のようになる。

| Androidのversion | 標準ブラウザ | Chrome | 
|:-----------|:------------:|:------------:|
|4.4.2(OSアップデート後)|**AOSP**|Chrome|
|4.2.2(出荷時初期状態)|AOSP|Chrome|

対して、isai (LGL22)では標準搭載のブラウザは以下のようになる。

| Androidのversion | 標準ブラウザ | Chrome | 
|:-----------|:------------:|:------------:|
|4.4.2(OSアップデート後)|**Chrome**|Chrome|
|4.2.2(出荷時初期状態)|AOSP|Chrome|

## Chromeのバージョンで判断する

上記の理由によりAndroidのバージョンを元に判断することが難しいため、Chromeのバージョンを組み合わせて判断することを考える。
Chromeのバージョン閾値をどこに設定するかという問題があるが、判断基準としては以下のいずれかが妥当な線ではないかと考える。

### 最初期のAndroid向けChrome
AndroidにデフォルトでChromeが組み込まれ始めたのは2012年冬モデルのあたりである。
この頃のChromeのバージョンは**Chrome/18**

### Android 4.4の標準ブラウザになった頃のChrome
Androidの標準ブラウザがChromeに切り替わり始めたのは2014年夏モデルのあたりである。
この頃のChromeのバージョンは**Chrome/30**

### Android 4.0でサポートされる最終version
2015年に少し話題に上がった「Android 4.0 以前のバージョンに対してのChromeのサポートの打ち切り」。
対象となるChromeのバージョンは42とされているので**Chrome/42**

## フィーチャーフォンとiPhone
フィーチャーフォンについてはキャリア名(DoCoMo or KDDI or Vodafone or Softbank)が入っている事が知られているため、これで判断できる。
iPhoneについては"iPhone"と入っている事が知られているため、これで判断できる。
iPadなども対象とする場合は、"Mac OS X"を拾った方かもしれない。

## 振り分け条件

| 機種 | 振り分け先 | User-Agentの識別文字列 |
|:-----------|:------------|:------------|
|フィーチャーフォン|HTML4ベースの旧サイト|DoCoMo または KDDI または Vodafone または Softbank|
|Android 4.2以前|HTML4ベースの旧サイト|Android のみ|
|Android 4.3以降|HTML4ベースの新サイト|Android かつ Chrome/30(未満)|
|Android 4.3以降|HTML5ベースの新サイト|Android かつ Chrome/30(以上)|
|iPhone|HTML5ベースの新サイト|iPhone のみ|

### nginxでの振り分け

nginxのリバースプロキシ機能で振り分ける場合は以下のような設定になる

```nginx
# ベースはPC用サイトとする
set $environment PC;
# ガラケー用判定ロジック
if ($http_user_agent ~ (DoCoMo|KDDI|Vodafone|Softbank)) {
  set $environment cellular;
}
# AOSP
if ($http_user_agent ~ ^(?!.*Chrome).*(?=Android).*$) {
  set $environment androidaosp;
}
# Android向けChromeの Chrome/30 未満(29以下)
if ($http_user_agent ~ Android.*Chrome\/[0-2]+[0-9]) {
  set $environment androidoldchrome;
}
# Android向けChromeの Chrome/30 以降
if ($http_user_agent ~ Android.*Chrome\/[3-9]+[0-9]) {
  set $environment androidchrome;
}
# iPhone向け
if ($http_user_agent ~ iPhone) {
  set $environment iphone;
}

location / {
    if ($environment = PC) {
        root /var/www/html/pc;
    }
    if ($environment = cellular) {
        root /var/www/html/cellular;
    }
    if ($environment = androidaosp) {
        root /var/www/html/android/aosp;
    }
    if ($environment = androidoldchrome) {
        root /var/www/html/android/oldchrome;
    }
    if ($environment = androidchrome) {
        root /var/www/html/android/chrome;
    }
    if ($environment = iphone) {
        root /var/www/html/iphone;
    }
}
```

- 上記の例ではif文を使用しているが本来if文は使うべきではないとの事。
- 動作確認は nginx 1.10.1 / CentOS7 のみ
  - クライアントは実機の用意ができなかったのでPC用ChromeのUser-Agent偽装を用いて確認
- 今回はとりあえずChrome/30を区切りとしたけれどプロジェクトでは要調整

## 参考資料
[atmarkIT - Tech TIPS：Android OSと「ブラウザ」、Chrome、アプリ内ブラウザーの関係をまとめる](http://www.atmarkit.co.jp/ait/articles/1507/17/news032.html)
[ITメディアエンタープライズ - Google、Android 4.0向けChromeのサポート打ち切りへ](http://www.itmedia.co.jp/enterprise/articles/1503/04/news052.html)
[nginx - If Is Evil](https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/)