2015年10月7日水曜日

iOS9のATS絡みの審査とリジェクトについて

Zメンです。iOSの開発に関するあれこれを書いていきます。



■iOS9で出てきたATSってなに?


iOS9が出てから(っていうか出る前から)開発者の間でATS絡みの話題が結構出てますね。
ATS自体はいまさら感がありますが、結局審査の時どうなるの?というところが意外と触れられてないです。
ちなみにATSはAppTransportSecurityの略ですね。


簡単に言うと
「今後アプリでの通信は全てHTTPSが推奨ですから!」
「HTTPで通信しようとしたらHTTPSに強制変換しますから!!」
な話です。


これが結構曲者な感じで、
HTTPSといっても『あれね、SSLね』ではなくて、具体的には
 ○TLS バージョン 1.2 以上
 ○接続時に使用できる暗号スイートに制限がある
 ○サーバ証明書に制限がある
   ・SHA256 以上のフィンガープリント
   ・2048 ビット以上の RSA キー、もしくは 256 ビット以上のECCキー
   ・無効な証明書を使用した場合は強制的に失敗になり、接続できない
な条件があります。


なので使用しているサーバーの環境によってそもそも対応できないよ!てことが普通にあるかと。
運営しているサイトがでかければでかいほどこのへんの変更は時間かかりそうですね。



■ATSの暫定対応(非推奨)


先に公開されてる対応方法だけ書いておきます。
といってもいろんなところで既に書かれまくっているので
こことかここ
とか参考にしてもらったほうが早いです。
『iOS ATS』でわんさか出てきます。



■懸念点と審査・リジェクト


今回の仕様変更の影響範囲は通信全てなので、
NSURLConnectionAFNetworkingみたいなAPI通信以外にも
UIWebViewだとかWKWebViewのようなWEBページ表示もしっかり影響範囲です。



大手でもまだまだWebViewベースでアプリを作ってるところもあるなかで、
この仕様変更は結構きついなーと。

一応HTTPSで動作確認はしましたが、
 ・CDN使ってるとこ
 ・広告表示してるとこ・
 ・オーディエンスデータとってるとこ
などなどがほんとに動作保障しきれるのか?とかなり不安です。
一部通信エラーっぽいログとか出てたし。(ログはかなり不親切)


なので jQueryみたいなCDNのはまだいいとして、個々の広告業者なんかは
しらみつぶしに検証するとかほんと時間の無駄。


クックパッド開発者ブログ
さすがcookpadさん、この辺も言及されてますね。


で、本題のそもそもATSを無効にした場合、
とくにWebView使ってて NSAllowsArbitraryLoadsで設定を切らざるを得ない場合、
はたして審査は通過するのか、ということです。


このあたりは丁度審査中なので追って追記します。
2015.10.08 追記)
審査無事通過しました。
ドメインを個別に指定する方法でも、BOOL値切替でもどちらでも
今のところ審査には支障はないみたいです。



ちなみに、審査通ったからと言ってHTTPSとか考慮しないし、
という訳ではなく、iOSX?以降で必須にされる場合ももちろんあるだろうし、
Appleに対してすべての通信がSecureであるよ、とちゃんと伝える意味でも
早めにATSに準拠したかたちに修正するのが望ましいでしょうね。



参考)Apple Developer 公式ドキュメント


wgetでサイト内のリンク切れチェック(いろいろ実験)

以前、wgetでサイト内のリンク切れチェックの記事でwgetのspiderオプションを紹介しましたが、実施するにあたって
  • 「-recursive -level 1」とすると指定ページだけチェックして終わり?もしくは指定したページからリンクされたページまで辿る?
  • 同一URLのリンクが複数貼られていた場合、毎回チェックしにいくのか
  • リダイレクトされるURLの場合、リダイレクト先まで追ってくれるのか
  • 一斉に大量のリクエストを送ってサーバに負荷をかけ過ぎないか
のような疑問が浮かんだので、実際に実験してみました。

準備

以下の様なリンク構造を持ったサイトを用意しました。



やかましい矢印は他ページへのリンクを表しています。/detail/4.htmlからはトップページ(index.html)へ302リダイレクトするようにしています。

結果


-recursive -level1でどこまでリンクを辿るのか

wget --spider --no-directories --background -o test.log --recursive --level 1 --no-verbose --execute robots=off http://localhost/index.html
上記コマンドを実行したところ、以下の様なアクセスログになりました。
127.0.0.1 - - [17/Sep/2015:19:15:56 +0900] "HEAD /index.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:15:56 +0900] "GET /index.html HTTP/1.1" 200 66
127.0.0.1 - - [17/Sep/2015:19:15:56 +0900] "HEAD /list/1.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:15:56 +0900] "GET /list/1.html HTTP/1.1" 200 111
127.0.0.1 - - [17/Sep/2015:19:15:56 +0900] "HEAD /list/2.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:15:56 +0900] "GET /list/2.html HTTP/1.1" 200 4340
-level 1とすると、指定ページから貼られているリンクだけチェックするようです。

同一URLのリンクが現れた時、毎回チェックするのか

-level 3と指定して実行したところ、以下の様なアクセスログになりました。
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /index.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "GET /index.html HTTP/1.1" 200 66
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /list/1.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "GET /list/1.html HTTP/1.1" 200 111
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /list/2.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "GET /list/2.html HTTP/1.1" 200 148
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /detail/1.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "GET /detail/1.html HTTP/1.1" 200 180
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /detail/2.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "GET /detail/2.html HTTP/1.1" 200 180
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /detail/3.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "GET /detail/3.html HTTP/1.1" 200 180
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /detail/4.html HTTP/1.1" 302 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /index.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "GET /index.html HTTP/1.1" 200 66
一度チェックしたURLは再度現れても省略されるようです。これは助かります。
ただ、パラメータが付くURLの場合、その順番が若干ずれるだけで別URLとみなされるので、
URLの埋め方によっては効率が悪くなってしまいますね。

リダイレクト先まで追ってくれるのか

先ほどのアクセスログにもありますが、リダイレクト先も追ってくれます。これも助かります。
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /detail/4.html HTTP/1.1" 302 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "HEAD /index.html HTTP/1.1" 200 -
127.0.0.1 - - [17/Sep/2015:19:20:32 +0900] "GET /index.html HTTP/1.1" 200 66
ただし、リダイレクト後のURLはチェック済みであっても再度確認しにいくようですので、注意が必要です。

サーバへの負荷は大丈夫か

パラメータだけ異なるリンクを100個ほど増やして試してみたところ、今回の環境(windows上に立てたapache)では65req/secほどでした。
通常のwebサイトでは問題なさそうですが、ページごとの処理内容やサーバのスペックにもよるかと思いますので、
参考程度として頂けたらと思います。

wgetでサイト内のリンク切れチェック

主にリソースのダウンロードに多用するwgetコマンドですが、オプションの指定の仕方によってリンク切れチェッカーとして使えることがわかりました。
社内サーバやベーシック認証がかかっている環境にも適用可能で、cron等のタスクスケジューラにも登録しやすいので、
開発の最終フェーズから、日々のサイト内リンクの死活監視まで幅広く重宝すると思います。

■コマンド

色々なオプションがありますが、実際に使ったコマンド例をご紹介します。
$ wget --spider --no-directories --background -o {YOUR_LOG_PATH} \
--recursive --level 3 --no-verbose --execute robots=off \
--user={YOUR_ACCOUNT} --password={YOUR_PASSWORD} \
--user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4" \
http://{YOUR_SITE_URL}
また、実行した環境は以下のとおりです。(Windows7 Cygwin)
$ uname -a
CYGWIN_NT-6.1 STCOM1053 2.2.1(0.289/5/3) 2015-08-20 11:42 x86_64 Cygwin
$ wget --version
GNU Wget 1.16.3 built on cygwin.
各オプションについて解説していきたいと思います。

■各オプションについて


–spider

ファイルの存在チェックのみ行い、ダウンロード(保存)を行わないようになります。(実際は保存してから削除している模様)
webサーバのアクセスログを見ると、まずHEADでリクエストし、200が帰ってきたら再度GETでリクエストといった挙動のようです。

–no-directories

–spiderオプションによりファイルの保存はされませんが、このオプションを指定しないと空ディレクトリが掘られてしまいます。
消すのも手間なので、指定したほうが無難かと思います。

–background

バックグランドのタスクとして実行されます。
サイト内を再帰的に巡回するケースだと、サイト規模にもよりますが長時間かかると思われるので、
コンソールが落ちても中断されないようにします。

-o {YOUR_LOG_PATH}

ログを出力するファイルパスを指定します。
コマンドラインに垂れ流しだとすぐに流れてしまいますので、別ファイルに書きだしたほうが良いでしょう。

–recursive –level 3

–recursiveで再帰的にリンクを辿るようになります。
–levelでどの階層まで辿るかを指定します。値が大きくなればなるほど実行時間が飛躍的に長くなりますので、徐々に探っていくといいと思います。
–levelを指定しないと無限にリンクを辿ります。

–no-verbose

ログの詳細情報を省略するようになります。
リンクの死活チェックだけであれば、ログファイルの容量圧縮のためにも省略して問題ないでしょう。

–execute robots=off

robots.txtの制限を無視するようになります。

–user={YOUR_ACCOUNT} –password={YOUR_PASSWORD}

ベーシック認証のユーザ名とパスワードを指定します。
認証をかけていない公開サーバであれば、このオプションは不要です。

–user-agent=”Mozilla/5.0…

各リンクにアクセスする際のユーザエージェントを指定します。
スマートフォン向けサイトを確認する場合などに指定します。
デバイスに関係のないサイトであれば、このオプションは不要です。

http://{YOUR_SITE_URL}

対象サイトの起点となるURLを指定します。通常はトップページになるかと思います。
URLはコマンドの一番最後に指定する必要があります。

■結果の確認方法

リンク切れがなかった場合、以下の様なメッセージがログの末尾に出力されます。(日本語の場合)
壊れたリンクはありませんでした。
終了しました –2015-09-12 18:58:36–
経過時間: 0.02s
ダウンロード完了: 3 ファイル、288 バイトを 0s で取得 (3.11 MB/s)
リンク切れがあった場合は以下の様なメッセージになります。
1 個の壊れたリンクを見つけました。
http://localhost/detail/4.html
終了しました –2015-09-12 19:01:37–
経過時間: 0.04s
ダウンロード完了: 6 ファイル、574 バイトを 0s で取得 (4.06 MB/s)
上記の場合、http://localhost/detail/4.html がリンク切れということになります。
しかし残念なことに、そのリンクがどのページから貼られているのかは、これだけではわかりません。
ひとつの解決策として、対象のwebサーバのアクセスログにリファラを出力するようにすれば、
リンク元特定の手助けになるかもしれません。(wgetはREFERRERヘッダも送信してくれるようです。)
参考サイト
wgetマニュアル

続き:いろいろ実験しました

Java初心者がはまったこと2(ラッパークラス)

こんにちは。Java初心者のWebエンジニアKです。
PHPのノリでJavaを書いていたらはまったこと第二弾として、今回はラッパークラスについて書きたいと思います。

■intとIntegerではまる

Javaのソースコードを見ていると、例えば数値を扱う変数を作るとき、以下の2通りの宣言があることに気付きました。
int num = 100;
Integer num2 = 100;
両者の違いを正しく理解せずに、その日の気分で使い分けていたら見事にはまりました。

■intとIntegerの違い

intはプリミティブ型のひとつで、値(intの場合は特に数値)のみを扱うための型です。 初期値は0です。
一方Integerはラッパークラスと呼ばれる参照型のひとつで、intを値だけでなく「振る舞い」も合わせて扱えるように定義した型となります。
「振る舞い」は平たく言うとクラスメソッド(toStringやvalueOf)のことですね。

また、Integer型は参照型のため値にnullを取ることができ、初期値も0ではなくnullになります。
ここが個人的なはまりポイントでした。
両者の違いをまとめてみると、
  • Integerは値にnullもとれる
  • Integerの初期値はnull、intは0
  • IntegerにはtoStringやvalueOfなどのメソッドが実装されている
javaのラッパークラスはint:Integer以外にもboolean:Booleanなど8種類あります。
両者の違いを理解して、要件にあった使い分けをすることが重要ですね。
参考サイト

Java初心者がはまったこと(文字列比較)

はじめまして。WebエンジニアのKと申します。
これまでサーバサイドはほぼPHPで実装してきましたが、最近ではJavaへの挑戦も始めました。
慣れたPHPの感覚でJavaを書き始めると、思わぬところではまってしまうことがあり、
ここでは自戒の念を込めてはまったポイントを記しておこうと思います。

■文字列比較ではまる

こんなif文を書いて、判定がtrueとならず、頭を悩ませていました。
※str = “hoge”だと、この単純な例の場合たまたま判定がtrueになってしまうので、わかりにくいですが”hog”と”e”にわけて宣言しています。【参考
String str = "hog";
str += "e";
if (str == "hoge") { // false...
    // do something
}
PHPだと文字列はプリミティブ型なので、上記のような文字列が格納された変数(str)と文字列リテラル(“hoge”)の比較でうまくいくのですが、
Javaの場合だと文字列は参照型で、文字列リテラルも一見プリミティブのように見えますが、内部ではString型のオブジェクトとして扱われます。
イメージとしては以下のようにStringクラスをnewしている形ですね。
String str = new String("hoge");
※ただ、これはあくまでイメージで、本当にこの書き方をすると文字列リテラルから変換されたString型オブジェクトを、さらにStringクラスのコンストラクタに渡していることになるので、二度手間となってしまいます。

“hoge”がString型のオブジェクトだとわかれば、比較演算子(==)でtrueとならないことを受け入れることができました。

■結局

で、結局どうすればよかったというと、Stringクラスに実装されている文字列比較用のメソッド”equals”を使いましょうというお話でした。
String str = "hog";
str += "e";
if (str.equals("hoge")) { // true!
    // do something
}
なんとなく見よう見まねで書けても、きちんと入門書などで言語仕様を把握しておくことが重要だと痛感した一日でした。