ATS対応が捗るスクリプトを書いてみた


ある程度規模が大きいアプリのATS対応で、面倒なところといえば以下の点です。

- アクセスするドメインが多すぎて、例外設定を一つずつinfo.plistに列挙するのが大変
- アプリからアクセスするドメインがどれだけあるか把握するのが大変
- ATS制限に掛かってしまう際にアプリがどんな挙動になるのか調査するのが大変

アプリからのHTTP通信先が明確で、ドメインの数が少なければ困る事は無いですが、複数のWebサービスやAPIにアクセスするようなアプリを開発している人にとっては、ATSの例外設定を行うだけでも一苦労です。

そして、一つでも漏れてしまえば障害につながってしまう… そんな状況では、落ち着いて夜も眠れません:disappointed_relieved: 

そんな開発者のために、ちょっとでも役に立てばと思い、簡単なスクリプトを書いてみたので、ATS対応手順も交えて紹介します。
なので、アプリからアクセスするドメインが明確だったり、少なかったりする場合は、この記事は読む必要ないです…

##どんな事してくれるスクリプト？

スクリプトはこの記事の最後に載せてあります[^1]
NSURLSessionやNSURLConnectionの通信ログから、以下を行ってくれるスクリプトです


- アプリを操作した際に、実際にATS制限に引っかかったドメイン、ひっかからなかったドメインを一覧表示してくれる
- 同じドメインは重複を除去します
- アプリを操作した際に、実際にATS制限に引っかかったドメインから、plist形式のxml文字列を生成してくれる。それをinfo.plistへコピペすれば例外ドメイン設定が完了する

##スクリプトを使う手順

1. 対象アプリの環境変数に、 `CFNETWORK_DIAGNOSTICS=1` を設定する。
	- xcodeでデバッグ実行する場合は、 `Edit Scheme > Run > Arguments > Environment Variables` に設定する
	- 実機でもシミュレータでも可
	- これによって、NSURLSession,NSURLConnection等の通信ログファイルが出力される
2. 対象アプリを起動し操作する
3. ログの出力先を確認する
	- シミュレータの場合、起動直後のxcodeのコンソールにログファイルの出力先が表示される

	```
CFNetwork diagnostics log file created at: /Users/hoge/Library/Developer/CoreSimulator/Devices/1234/data/Containers/Data/Application/5678/Library/Logs/CrashReporter/CFNetwork_hoge.ATSSampleApp_1234.nwlrb.log
```
	- 実機の場合は、 `xcode の Devices > 該当の実機 > Installed Apps > 該当アプリを選択 > Download Container > Finderでパッケージを開く > /AppData/Library/Logs/CrashReporter` といった流れでログを取得する

4. 以下のスクリプトを実行するための準備
	- 適当にxcode8以降、swiftで、Commandline Toolプロジェクトを作って、main.swiftを置き換えてください。これだけ。
	- スクリプトはこの記事の最後に載せてあります[^1]


5. スクリプトを実行する
	- 実行方法は3つあります。第一引数の値で変わります。

	```
    - xxxscript normal /hoge/fuga/logfile.log
    - xxxscript domainonly /hoge/fuga/logfile.log
    - xxxscript domainonlyformat /hoge/fuga/logfile.log
```
	- `normal` は、アクセスしたURLそのまま表示します。どんなURLでアクセスしているか把握する時はまずこれで出力してみると良いです
	- `domainonly` は、ドメインだけを表示します。同じドメインに複数回アクセスしている場合は、重複を取り除いて表示します。ドメインだけ一覧化したい場合は、これで出力してください
	-  `domainonlyformat` は、重複を取り除いたドメインから、plist形式のxml文字列としてフォーマットしたものを出力します。info.plistにコピペすれば、ATSの例外設定(NSExceptionDomainsの箇所にコピペ)ができます
	- 出力は、以下のようになります( `domainonlyformat` で出力した例)

	```
target file path: /hoge/fuga/ATSSampleApp_1234.nwlrb.log
-----------------------------------------------------
[ kCFErrorDomainCFNetwork Code=-1022 ] -----------------
<key>www.yahoo.co.jp</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
[ Success ] -----------------
```
	- `[ kCFErrorDomainCFNetwork Code=-1022 ] ` の部分は、ATS制限にひっかかったドメインやURLを出力します。つまり、ここに表示されたものは、ATSの例外ドメイン設定を行うか、httpsでアクセスするように変更する必要があるという事がわかります
	- `[ Success ]` の部分は、ATS制限にひっかからなかったドメインやURLを出力します。

##ATSの挙動に関する前提知識
オススメのATS対応手順を紹介する前に、ATSの前提知識を少し書いておきます。

###そもそもATS制限にひっかかると何が起きるか？
まず、その挙動を知っておく必要があります。
**ATS制限にひっかかると、通信は行われません。**
なので、プロキシを挟むやり方では、実行時にATS制限にひっかかったURLを知る事は出来ません。
そこで、前述のスクリプトを使って、CFNetwork Diagnostic ログから調査する事になるわけです。

###CFNetwork Diagnostic ログで出力されるもの

- CFNetworkのリクエストのログが出る。失敗も成功も
- NSURLSessionやNSURLConnectionなど、内部的にCFNetworkを使っているアクセスのものは出る
- `CFNetworkErrors.h` に、CFNetworkのエラーコードと定数名が一覧になっている
    - ATS制限に掛かって失敗したエラーは、 `-1022`
- `SecureTransport.h`
    - NSURLSessionなど、SSL通信は、内部的にiOSのSecurityFrameworkを利用している模様
    - そのため、それ用のエラーコードは、ここに一覧化されている


参考:
https://developer.apple.com/library/content/qa/qa1887/_index.html

###サーバがSSL対応していてもTLSバージョン等でATS制限にひっかかる事がある
これもちゃんと理解しておく必要があります。
TLS1.2以上じゃなきゃいけないなど… 詳しくは公式ドキュメントを翻訳していますので参照してください。http://qiita.com/caffezom/items/e2397de6ae969773e41f

ちなみに、対象ドメインが明確になっていて、ATSの条件を満たしているサーバかどうか調査するだけなら、 `nscurl` コマンドが使えます。

```
/usr/bin/nscurl --ats-diagnostics [--verbose] URL
```


###ATS制限にかかる通信を調査する際に難しい事
当たり前ですが、一般的な複数の通信を行うアプリは、順番にURLへアクセスしていきます。途中で通信に失敗すると、当然その先のURLへアクセスする事なくエラー処理に流れます。

従って、ATS制限にひっかかるかどうかアプリを実際に動かしながら漏れなく調査するには、 `アプリ操作 > ATS制限にひっかかる > そのドメインを例外設定する > アプリ操作 > 次のURLがATS制限にひっかかる > そのドメインを例外設定する > アプリ操作 ...` これを繰り返していくことになります。


##オススメのATS対応の流れ
以下に私が思うATS対応の進め方を紹介しておきます。

1. ソースコードからアクセスするドメインを洗い出す
	- これだけで全て網羅できるなら素敵ですが、漏れる心配がある場合は、以下の手順で実際に動作させて試してみる必要があります
2. `CFNETWORK_DIAGNOSTICS=1` をアプリの環境変数に設定して、 `NSAllowsArbitraryLoads=YES` で、ATSを完全無効にして、アプリを操作する
3. ATS制限にひっかかって次に進めなくなったら、手順2で出力したログに、本記事に掲載したスクリプトを実行して、例外設定を出力する

	```` 
	xxxscript domainonly /hoge/fuga/logfile.log
````

4. 次に、`NSAllowsArbitraryLoads=NO` で、ATSを有効にして、アプリを操作する。その後、スクリプトで例外設定を出力する

	```` 
	xxxscript domainonlyformat /hoge/fuga/logfile.log
````

5. 以降、 `アプリ操作→ログ出力→スクリプト実行→例外設定→...` を繰り返す

##スクリプト
正直、実装はかなり適当でヒドイものですがご容赦ください…:cry:  (動けばいいや、目的が達成できればいいやで書いたもの)
<script src="https://gist.github.com/caffezom/6556d67078d2a65445dc4a58e23b2482.js"></script>

[^1]: https://gist.github.com/caffezom/6556d67078d2a65445dc4a58e23b2482