無印吉澤

Site Reliability Engineering(SRE)、ソフトウェア開発、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

艦これアーケード第2回イベント「南方海域強襲偵察!」 プレイデータ解析 〜甲種勲章の十分条件は何だったのか?

f:id:muziyoshiz:20170128164643p:plain

はじめに

2017年4月26日から5月31日まで、艦これアーケードの第2回イベント「南方海域強襲偵察!」が開催されていました。普通の人にこういう話をすると「アーケードゲームにイベントがあるの?」と言われるんですが、最近のアーケードゲームはネット接続してるので、期間限定イベントとか普通らしいです。

艦これアーケードの場合は、イベント期間だけ新キャラを入手できたり、凶悪なボスキャラと戦えたりします。一番難しい難易度は甲乙丙の「甲」で、これをクリアすると「甲種勲章」というアイテムを入手できました。

僕は艦これアーケードのプレイヤー(提督)向けに、プレイデータ管理ツール Admiral Stats というサービスを提供していて、ここに各プレイヤーのイベント攻略状況も集まっています。今回は、このデータをもとに、「甲種勲章」を入手できた提督に共通する特徴を探ってみました。

艦これアーケードと Admiral Stats については、以前スライドにまとめたので、興味のある方はこちらをどうぞ。

第2回イベント「南方海域強襲偵察!」について

今回のイベントは、前段作戦と後段作戦に分かれていて、それぞれの作戦に甲乙丙の難易度がありました。簡単な難易度をクリアすると、より難しい難易度に挑戦できるようになっていて、具体的には下図のような制限がありました。

f:id:muziyoshiz:20170629225150p:plain

(※1周クリア=1周目の掃討戦に出撃していること)

一番の目玉だった「大和」は後段作戦でしか入手できず、そのためには前段作戦に挑戦する必要がある……ということで、約1ヶ月間かなりの提督がゲーセンに詰めかけてました。

ちなみに、後段作戦の甲は出現までが面倒なうえに、とてもクリアさせる気があるとは思えない極悪難易度でした(※個人の感想です)(※褒め言葉です)。

集計対象のプレイデータ

今回は、前段作戦95名、後段作戦80名分のプレイデータが集まりました。

集計対象の詳しい情報は、以下の通りです。

  • 集計対象とした提督
    • 第2回イベントに参加して、Admiral Stats に1回以上プレイデータをアップロードした提督
  • 集計対象としたプレイデータ(イベント進捗情報、艦娘一覧など)
    • イベント攻略率と艦娘カード入手率は、2017年6月8日3:00時点のプレイデータを集計
    • レベルや経験値については、イベント終了時刻になるべく近いプレイデータを集計
      • 2017年6月8日3:00までにアップロードされたプレイデータのうち、イベント終了時刻(2017年5月31日23:59)に一番近い時刻にアップロードされたプレイデータ

SEGA の本家サイトから、イベント進捗情報をエクスポートできなくなったのは6月8日でした。Admiral Stats は毎日2時にデータをバックアップしているので、今回は6月8日 3:00のバックアップを使って解析しました。

プレイデータの解析結果

イベント攻略率

前段作戦

前段作戦の甲難易度をクリアしたのは全提督の 57.9 % でした。前段作戦は、まだ救いのある難易度だったので、この高い割合もわからなくはないです。

f:id:muziyoshiz:20170630003300p:plain

また、甲難易度をクリアした提督のうち 56.4 % が、レアカード(甲種勲章がプリントされた艦娘カード)を求めて、2周以上クリアしていました。10周以上クリアした提督は 10.9 % です。

f:id:muziyoshiz:20170630003334p:plain

詳細:イベント攻略率(南方海域強襲偵察! 前段作戦) - Admiral Stats

後段作戦

後段作戦の甲難易度をクリアしたのは全提督の 57.5 % でした。前段作戦よりもかなり難しくなっているのに、割合は全然変わってないのが不思議です。前段作戦しかアップロードしていない提督がいることによる影響を考えても、この割合はかなり高いような……。

f:id:muziyoshiz:20170630003356p:plain

また、甲難易度をクリアした提督のうち 60.9 % が、2周以上クリアしていました。10周以上クリアした提督は 17.4 % です。後段作戦は大和入手のチャンスがあったので、こちらのほうが前段作戦よりも周回されていたようです。

f:id:muziyoshiz:20170630003434p:plain

詳細:イベント攻略率(南方海域強襲偵察! 後段作戦) - Admiral Stats

艦娘カード入手率

以下は、このイベントでの新艦娘を入手できた提督の割合です。ちなみに、自分でドロップしたカードだけでなく、買ったり借りたりして読み込んだカードも「入手」に含みます。これは、公式プレイヤーズサイトの仕様による制限です。

目玉だけあって、大和の入手率が他より 10 % 以上低かったのがわかります。また、前段・後段の両方で入手できた阿賀野と能代は、5 〜 7 % 高くなっています。

図鑑 No. 艦種 艦名 N Nホロ N中破
131 戦艦 大和 43.3 % 6.7 % 2.2 %
137 軽巡洋艦 阿賀野 65.6 % 8.9 % 4.4 %
138 軽巡洋艦 能代 62.2 % 8.9 % 3.3 %
139 軽巡洋艦 矢矧 57.8 % 6.7 % 5.6 %
140 軽巡洋艦 酒匂 58.9 % 4.4 % 2.2 %

また、Admiral Stats の方ではまだ表示できていないのですが、その他の限定カードの入手率は以下のようになりました。実際にプレイした感覚からすると、意外と高いな、という印象です。

図鑑 No. 艦種 艦名 限定カード
102 航空戦艦 伊勢改 32.2 %
103 航空戦艦 日向改 40.0 %

詳細:艦これアーケードの艦娘カード入手率(南方海域強襲偵察!) - Admiral Stats

艦隊司令部レベル、および経験値

ここから先は、Admiral Stats 上で表示していない(自動集計機能がまだない)集計結果の紹介です。

Admiral Stats で上記のイベント攻略率を公開した際に、「こんなツールを欲しがるのはやりこんでるプレイヤーだけなんだから、実際のプレイヤー全体よりも攻略率が大幅に高いのでは?(意訳)」という指摘がありました。

その点を検証するために、まずは、ステージのクリア回数とステージの難易度によって増える「艦隊司令部レベル」の分布を出しました。この結果によると、レベル90以上の提督は 49.47 % です。

艦隊司令部レベル 提督数 割合
0 以上 10 未満 3 3.16 %
10 以上 20 未満 1 1.05 %
20 以上 30 未満 2 2.11 %
30 以上 40 未満 5 5.26 %
40 以上 50 未満 5 5.26 %
50 以上 60 未満 10 10.53 %
60 以上 70 未満 7 7.37 %
70 以上 80 未満 7 7.37 %
80 以上 90 未満 8 8.42 %
90 以上 100 未満 28 29.47 %
100 以上 110 未満 18 18.95 %
110 1 1.05 %
総計 95 100.00 %

また、艦隊司令部レベルを経験値に換算したものが以下の表です。レベル99になるのに経験値が 1,000,000 必要なので、イベント参加提督の 33.69 % がレベル99以上ということになります。

経験値 提督数 割合
0 以上 100000 未満 14 14.74 %
100000 以上 200000 未満 14 14.74 %
200000 以上 300000 未満 8 8.42 %
300000 以上 400000 未満 6 6.32 %
400000 以上 500000 未満 6 6.32 %
500000 以上 600000 未満 3 3.16 %
600000 以上 700000 未満 6 6.32 %
700000 以上 800000 未満 3 3.16 %
800000 以上 900000 未満 3 3.16 %
900000 以上 1000000 未満 0 0.00 %
1000000 以上 2000000 未満 23 24.21 %
2000000 以上 3000000 未満 5 5.26 %
3000000 以上 4000000 未満 2 2.11 %
4000000 以上 5000000 未満 0 0.00 %
5000000 以上 6000000 未満 2 2.11 %
総計 95 100.00 %

Admiral Stats のユーザにはやりこみ勢が多い、という主張は、まあ正しそうですね……。とはいえ、そうでないユーザも居るので、甲難易度をクリアした提督と、そうでない提督の差を調べることはできそうです。

ちなみに、艦これアーケードのレベルアップに必要な経験値は、艦これ本家と同じと言われています。Admiral Stats では 艦これ攻略 Wiki の「経験値」ページ を参考に経験値を計算しています。

攻略度と艦隊司令部レベル/経験値の比較

では、艦隊司令部レベルがどれくらい高ければ後段作戦の甲をクリアできたのか? それを調べるために、攻略度と、艦隊司令部レベル/経験値を比較したのが以下の表です。

この結果によると、後段作戦の甲をクリアできた提督は、最低でもレベル63、平均レベル96.3でした。艦これの経験値テーブルはレベル90前後から急上昇するので、経験値の平均を取ると1121310.2で、これはレベル100相当です。平均が高すぎる……。

甲クリア提督を見てもやりこみ度が高すぎるので、乙クリア提督を見たほうが参考になるかもしれません。後段作戦の乙をクリアできた提督は、最低でもレベル41、平均レベル79.2。経験値の平均は427060.0で、レベル83相当でした。この間のどこかに境界がありそうですね。

前段作戦の攻略度と、艦隊司令部レベル

攻略度 提督数 最大 最小 平均 標準偏差
甲攻略済 55 110 63 95.4 9.9
乙攻略済 15 97 30 66.8 19.4
丙攻略済 15 78 26 49.5 15.0
未攻略 10 65 6 32.5 21.6
総計 95 110 6 77.0 27.2

前段作戦の攻略度と、艦隊司令部経験値

攻略度 提督数 最大 最小 平均 標準偏差 平均(レベル換算)
甲攻略済 55 5900000 203400 1264850.9 1070768.8 99
乙攻略済 15 761500 43500 296233.3 200063.1 73
丙攻略済 15 356200 32500 138513.3 89758.1 53
未攻略 10 219500 1500 75690.0 73349.5 39
総計 95 5900000 1500 808893.7 980487.1 97

後段作戦の攻略度と、艦隊司令部レベル

攻略度 提督数 最大 最小 平均 標準偏差
甲攻略済 46 110 63 96.3 9.9
乙攻略済 15 97 41 79.2 16.0
丙攻略済 14 75 26 49.5 15.5
未攻略 5 52 6 29.4 19.1
総計 80 110 6 80.7 25.4

後段作戦の攻略度と、艦隊司令部経験値

攻略度 提督数 最大 最小 平均 標準偏差 平均(レベル換算)
甲攻略済 46 5900000 203400 1360919.6 1121310.2 100
乙攻略済 15 761500 82000 427060.0 191149.6 83
丙攻略済 14 319000 32500 138964.3 88616.0 53
未攻略 5 132700 1500 60000.0 52672.3 35
総計 80 5900000 1500 890671.3 1020211.4 98

攻略度と艦娘の練度の比較

艦隊司令部レベルが低くても、特定の艦娘だけレベルを上げていればクリアは可能だったはずです。そこで、攻略度と、一定レベル以上の艦娘の数も比較してみました。

艦隊司令部レベルよりも、こちらのほうが、甲提督と乙提督の差が激しいですね。イベント開始時の全艦娘110隻をレベル99にしているようなやりこみ勢(凄すぎる……)が平均を上げています。

レベル70以上の艦娘数0で甲攻略済みの提督もいたので、低レベルでも攻略は可能だったみたいです。ただ、「高レベル艦娘を揃えていて、乙攻略済み以下」という提督はほとんどいないので、「レベルを上げれば物理で殴れた」とも言えそうです。

前段作戦

Lv50以上の艦娘数

攻略度 最大 最小 平均 標準偏差
甲攻略済 114 6 32.4 24.8
乙攻略済 25 0 9.0 6.9
丙攻略済 8 0 2.9 2.9
未攻略 2 0 0.6 0.8
総計 114 0 20.7 23.6

Lv70以上の艦娘数

攻略度 最大 最小 平均 標準偏差
甲攻略済 114 0 19.1 20.8
乙攻略済 8 0 2.3 2.5
丙攻略済 3 0 0.3 0.8
未攻略 0 0 0.0 0.0
総計 114 0 11.4 18.2

Lv90以上の艦娘数

攻略度 最大 最小 平均 標準偏差
甲攻略済 110 0 10.6 18.7
乙攻略済 1 0 0.2 0.4
丙攻略済 0 0 0.0 0.0
未攻略 0 0 0.0 0.0
総計 110 0 6.1 15.2

Lv99の艦娘数

攻略度 最大 最小 平均 標準偏差
甲攻略済 110 0 5.6 16.0
乙攻略済 0 0 0.0 0.0
丙攻略済 0 0 0.0 0.0
未攻略 0 0 0.0 0.0
総計 110 0 3.2 12.5

後段作戦

Lv50以上の艦娘数

攻略度 最大 最小 平均 標準偏差
甲攻略済 114 6 34.8 25.6
乙攻略済 25 0 11.8 5.9
丙攻略済 8 0 3.4 2.7
未攻略 2 0 0.6 0.8
総計 114 0 22.9 24.2

Lv70以上の艦娘数

攻略度 最大 最小 平均 標準偏差
甲攻略済 114 1 21.0 21.8
乙攻略済 8 0 3.7 2.3
丙攻略済 1 0 0.1 0.3
未攻略 0 0 0.0 0.0
総計 114 0 12.8 19.1

Lv90以上の艦娘数

攻略度 最大 最小 平均 標準偏差
甲攻略済 110 0 12.0 20.1
乙攻略済 2 0 0.5 0.6
丙攻略済 0 0 0.0 0.0
未攻略 0 0 0.0 0.0
総計 110 0 7.0 16.3

Lv99の艦娘数

攻略度 最大 最小 平均 標準偏差
甲攻略済 110 0 6.5 17.4
乙攻略済 1 0 0.1 0.2
丙攻略済 0 0 0.0 0.0
未攻略 0 0 0.0 0.0
総計 110 0 3.8 13.6

攻略度と雷巡改二(大井改二・北上改二)の所有数の比較

今回、特に後段作戦E-6は、雷巡の改二を持っているかいないかで難易度が大きく変わる、と言われていました(参考:南方海域強襲偵察! E-6 - 艦らぼ)。

そこで攻略度と、雷巡改二の所有数も比較してみました。以下の表の読み方ですが、2は大井改二と北上改二を両方持っている、1は片方のみ持っている、0はどちらも持っていない、という意味です。

こうしてみると、雷巡改二が1隻の場合と2隻の場合では、甲難易度の攻略率が大きく違うのがわかります。

前段作戦

攻略度 0 1 2 総計
甲攻略済 11.58% 8.42% 37.89% 57.89%
乙攻略済 6.32% 4.21% 5.26% 15.79%
丙攻略済 12.63% 2.11% 1.05% 15.79%
未攻略 10.53% 0.00% 0.00% 10.53%
総計 41.05% 14.74% 44.21% 100.00%

後段作戦

攻略度 0 1 2 総計
甲攻略済 10.00% 7.50% 40.00% 57.50%
乙攻略済 5.00% 5.00% 8.75% 18.75%
丙攻略済 13.75% 2.50% 1.25% 17.50%
未攻略 6.25% 0.00% 0.00% 6.25%
総計 35.00% 15.00% 50.00% 100.00%

雷巡改二(大井改二・北上改二)の所有数と、艦隊司令部経験値の比較

しかし、レアカードを持っているのはやりこんでいるからで、攻略度に直接影響しているのはやりこみ度の方なのでは?とも考えられます。

そこで、雷巡改二の所有数と艦隊司令部経験値と比較した結果は以下の通りです。やりこみ勢は雷巡改二を両方とも持っていることが多いが、片方だけ持っている提督とどちらも持っていない提督の差はそれほど無いことがわかりました。

大北改二の所有数 提督数 最大 最小 平均 標準偏差
2 42 5900000 174700 1378459.5 1194011.3
1 14 1300000 43500 481650.0 362335.6
0 39 1600000 1500 312987.2 354371.6
総計 95 5900000 1500 808893.7 980487.1

うーん、このデータからは甲難易度クリアの十分条件はよくわからないですね。

雷巡改二の所有数、艦隊司令部レベル、攻略度の比較

これだと消化不良なので、最後に雷巡改二の所有数、艦隊司令部レベル、攻略度をすべて対比してみました。

表が大きくなったので、画像で貼り付けます。表中に「0+」とあるのは、0以上10未満と読んでください。レベル90台だけは詳細に表示しています。

こうやって眺めてみると、「艦隊司令部レベルが高ければ、雷巡改二がなくてもクリアできている」、従って「甲種勲章が欲しければとにかくやりこんでレベルを上げろ」というのが結論っぽいですね。

前段作戦

f:id:muziyoshiz:20170630004411p:plain

後段作戦

f:id:muziyoshiz:20170630004427p:plain

まとめ

今回は、Admiral Stats にアップロードされたプレイデータをもとに、後段作戦の甲難易度をクリアして甲種勲章をゲットするための十分条件を探ってみました。その結果、

  • 艦隊司令部レベルが一定以上になるくらい、やりこんでいれば甲クリアできた
  • 雷巡改二を2隻とも持っていると甲クリア率が上がるが、艦隊司令部レベルの方が寄与が大きかった
  • 逆に、雷巡改二を2隻持っているだけでは、甲クリアに十分ではなかった
  • 艦娘のレベルが低くても甲クリア可能だったが、艦娘のレベルが一定以上なら確実に甲クリアできた
  • Admiral Stats のユーザはやっぱりやりこみ勢が多かった

といったことがわかりました。

もう少し分析方法を工夫したり、プレイデータをアップロードしてくれる提督が多くなれば(やりこみ勢に偏っていなければ)、甲種勲章をゲットできる・できないの差がもっと正確にわかるかもしれません。

第3回イベント以降も同じようにプレイデータ解析してみるつもりなので、こういうデータを面白いと思う艦アケ提督はぜひ Admiral Stats を使ってみてください。

www.admiral-stats.com

職場ブログに書いた記事まとめ

2017年9月追記:
私は2017年7月末をもって GMO インターネットを退職しました。これは2017年5月31日当時の情報です。

2015年1月に現職についてから、職場のブログに色々と記事を書いてきました。四半期に1回のペースで、それなりにまとまった内容を書くことを心がけてます。

グループ会社のブログに寄稿することもあって、自分でもどこに何を書いたか思い出せなくなってきたので、自分のブログにまとめておきます。今後、記事が増えたらここに追記します。

DevOps

DevOps 関係の話題は継続的に追っていて、職場でも隙を窺ってツールを導入したりしています。新しいツールの話題に加えて、職場ブログでは(許可を得た上で)個人ブログではなかなか書けない泥臭い話題も書いています。

Embulk

recruit.gmo.jp

recruit.gmo.jp

recruit.gmo.jp

HashiCorp

recruit.gmo.jp

Habitat

recruit.gmo.jp

Ansible

recruit.gmo.jp

recruit.gmo.jp

プログラミング

関数型言語の苦手意識を克服したくて、Elixir を触って記事を書いたりしました。ブログのネタにはしていませんが、最近は Scala も勉強中(何度目かの勉強中)です。

Elixir

recruit.gmo.jp

recruit.gmo.jp

自社サービスの API

recruit.gmo.jp

recruit.gmo.jp

その他(API 設計、Hadoop)

グループ会社の Tech Blog に書いた記事です。Hadoop は業務でもかなり触っているのですが、職場にもっと詳しい Hadoop おじさんがいるので、記事にすることがあんまりなくて……。

techblog.gmo-ap.jp

techblog.gmo-ap.jp

GNU social (OStatus) 自体の仕様に関する情報源まとめ

f:id:muziyoshiz:20170430143245p:plain

(上のロゴは、何故か スペイン語版の Wikipedia にだけあった。本当に公式のロゴ?)

はじめに

Mastodon 大人気ですね。

僕もとりあえず mstdn.jppawoo.net にアカウントを取ってお互いにフォローし、どれくらいの時間差でトゥートが伝達されるのか観察したり、GitHub に公開されているソースコードを少し読んだりしました。

「Mastodon は分散型だ」とか「GNU social と互換性がある」という話を聞いて、一体どんなプロトコルなんだろう……と気になって少し調べたのですが、GNU Social 自体がかなり古いものらしく、ドキュメントを探すのにも苦労しました。

そこで、自分で探した範囲で、原典に近いと思われるドキュメントのリンク集を作っておきます。新しい情報が見つかったら、随時更新します。

プロトコル同士の関係

以下のドキュメントに書かれている、OStatus に関連するプロトコルをまとめると、次のようになります。

  • Atom と RSS フィードを、サーバ間の共通言語として使う
  • Webfinger を使って、サブスクライブしたい相手の Link-based Resource Descriptor Discovery (LRDD) ドキュメントを探す
    • Webfinger を使う前に、meta-data で Webfinger 用の URI を取得する。
  • サーバ間でフィードのアップデートを購読し、プッシュ配信を受けるために PubSubHubbub を使う
  • PubSubHubbub の機能不足を補うために、Atom の拡張(Activity Streams, Portable Contacts, Salmon)を使う
    • フィードが表す social activity を表現するために、Activity Streams を使う(例えば、フォローのときは "follow" verb を使う)
    • プロフィール情報を提供するために Portable Contacts を使う
    • リプライを送るために Salmon を使う

GNU social

OStatus は、GNU social にマージされた StatusNet に端を発するプロトコルです。そこで、まずは GNU social のドキュメントを当たりました。

  • GNU social - Wikipedia

    • 結論から言うと、GNU social そのものについては情報が失われていて、Wikipedia に載っている以上の情報はコードを読むしかないのかもしれない。
  • GNU social の公式サイト

    • 情報量はあまりない。
    • What is GNU social? に、GNU social が2010年に PHP スクリプトの集合として始まったこと、その後 StatusNet とコードベースが共有されたこと、2013年に Free Social project とマージされたことなどが書かれている。
    • FAQ にある "Why are you using PHP? Ruby/Python/Perl/A GUI in Visual Basic would be better!" の答えにちょっと笑った。
  • GNU social の GitLab

    • 最新版は 1.2.x 系。リリースノートもタグもないため、詳細はよくわからず。
    • 開発は継続されているが、Contributors のグラフ を見る限り、当初の開発者はほとんど手を引いている。
    • doc-src ディレクトリ にドキュメントがあるが、ざっと見た限り、クライアント-サーバ間通信の情報しかない。サーバ間通信の情報は見当たらなかった。
  • GNU social の GitHub

    • 上記の GitLab のミラー?
  • The Unofficial GNU Social documentation!

    • 基本的に GNU social のインストール方法についてのマニュアル。
    • Protocol Overview というページがあったので、これは!と思ってクリックしたら "GNU social runs primarily on voodoo magic. If anybody knows better please advise." としか書かれてなかった。
    • ですよね。

OStatus

  • OStatus - Wikipedia

    • OStatus の歴史と概要、OStatus を採用するソフトウェア一覧あり。
    • このページでは関連するプロトコルとして Atom, Activity Streams, PubSubHubbub, Salmon, Webfinger の名前が挙げられている。
    • "Standards Work" の節に、「pump.io で使われているプロトコルを元にした "ActivityPub" と呼ばれる新しい標準があり、これが OStatus の後継になりうるとされてきた」という記述がある。
  • OStatus Community Group

    • W3C のサイトにある Wiki。情報量は決して多くないが、OStatus に関連するプロトコルについての説明がある。
    • (see spec) と書かれた部分がリンクになっており、そこに OStatus 1.0 Draft 2 の仕様がある。
  • OStatus Community Group - Workflow

    • HTTPリクエスト/レスポンス例を伴った具体例。もしかしたら、これが OStatus のシーケンスに関する、最も詳しいドキュメントかもしれない。
    • ただし、これは OStatus 1.0 Draft 1(リンク切れ)ベースらしいので、Draft 2 とは多少違う可能性がある。
  • OStatus 1.0 Draft 2 の仕様

    • 2010年8月公開。
    • 全体で7ページと、仕様書としては短い。詳細を他の仕様書に譲っているからだが、参照先のドキュメントが古いため、一部はすでに見つからなくなっている。
    • 用語の定義があり、OStatus を調べる人には、それだけでも有用かもしれない。
    • OStatus では、プライベートメッセージングやソーシャルグラフは対象外、と書かれている。
    • "12. Usage scenario" に利用シナリオが書かれている。

meta-data (Web Host Metadata)

これは mstdn.jp で簡単に試すことができます。例えば、https://mstdn.jp/.well-known/host-meta にアクセスすると、以下が返されます。

> curl "https://mstdn.jp/.well-known/host-meta"
<?xml version="1.0"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
  <Link rel="lrdd" type="application/xrd+xml" template="https://mstdn.jp/.well-known/webfinger?resource={uri}"/>
</XRD>

Link-based Resource Descriptor Discovery (LRDD)

  • draft-hammer-discovery-06 - LRDD: Link-based Resource Descriptor Discovery
    • LRDD に関する最後の Internet Draft。
    • このプロトコルは、URI が示すリソースにアクセスするための「プロセス」を定義している。host-meta を使う方法は、そのプロセスの一つである。
    • host-meta を使う方法は "5.1. host-meta Document" に記載されている。

Webfinger

これも mstdn.jp で簡単に試すことができます。

LRDD が示すように "application/xrd+xml" を指定して https://mstdn.jp/.well-known/webfinger?resource=acct%3Amuziyoshiz%40mstdn.jp にアクセスすると、XML 形式で返されました。ちなみに、Web ブラウザでアクセスすると、JSON(application/jrd+json)が返されます。

> curl -H "Accept: application/xrd+xml" "https://mstdn.jp/.well-known/webfinger?resource=acct%3Amuziyoshiz%40mstdn.jp"
<?xml version="1.0"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
  <Subject>acct:muziyoshiz@mstdn.jp</Subject>
  <Alias>https://mstdn.jp/@muziyoshiz</Alias>
  <Alias>https://mstdn.jp/users/muziyoshiz</Alias>
  <Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="https://mstdn.jp/@muziyoshiz"/>
  <Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="https://mstdn.jp/users/muziyoshiz.atom"/>
  <Link rel="salmon" href="https://mstdn.jp/api/salmon/14903"/>
  <Link rel="magic-public-key" href="data:application/magic-public-key,RSA.wOHgmclLfwfGDWfxN1pWfGIwr5GTXbFhJG49yuqrdI6T2WULDvlXUJx3vSIMCiwtkZn-DE9Rhpyse9_69xshlYerke0RvI6OfnvTv20RqFEz0Z65k9W4GTcYKAKu441OzMnY9C3144SiecDpW2noULukzFOMOEY22ON21yQk94QAzJXFt2Hh35ia31uK_JI5NDWGrcl-Rdl8mTHDjhkA4sZC504IInxEpMSxOMMhs75DS_HYYdYuWX-hkGtGEZy5qEfz7HSrSMU8x6e-hwq_ULZ-a5TmIWslJkqWoX_T94gR0hiLPEjpNQpf7R50jB57dltmeo_wKyeETkjxoWBRDw==.AQAB"/>
  <Link rel="http://ostatus.org/schema/1.0/subscribe" template="https://mstdn.jp/authorize_follow?acct={uri}"/>
</XRD>

PubSubHubbub (PuSH)

twitter.com

Activity Streams

Portable Contacts

Portable Contacts は、Google Contacts などでも使われている仕様らしいが、正式な仕様書らしきものが見つからなかった。

Salmon (Salmon Protocol)

The Good Stuff によると、「鮭が上流に泳いでいくように」元のブログサーバにコメントを送り返すためのプロトコルなので、Salmon という名前にしたそうです。OStatus では、これをリプライを返すための仕組みとして使っています。

OStatus が使っている、その他の Atom 拡張

番外:ActivityPub

OStatus の範囲外ですが、ActivityPub についても取り上げておきます。

まとめ

実際にドキュメントを追ってみて、噂に聞く通り、OStatus は非常に雑多な(一部はすでに廃れた)プロトコルの組み合わせで作られていることがわかりました。それぞれのプロトコルが、Mastodon 上でどう実装されているかは、他の人の調査に任せたいと思います。

実際、OStatus について先行して調べていた岡本さんによると、GNU social などの既存実装は、これらのドキュメント通りに実装しても動かないそうです。

twitter.com

OStatus について、5月12日に出る(早い!)マストドン本で岡本さんが解説してくれるらしいので、とりあえず僕はこの本を待とうと思います。

okapies.hateblo.jp

これがマストドンだ!  使い方からインスタンスの作り方まで (NextPublishing)

これがマストドンだ! 使い方からインスタンスの作り方まで (NextPublishing)

Ruby on Rails 5 アプリにあとから API 機能(JWT, CORS 対応)を追加する

f:id:muziyoshiz:20170320163237p:plain

はじめに

https://www.admiral-stats.com/ という URL で、Ruby on Rails 5 で作った Admiral Stats というサービスを動かしています。このサービス自体については、過去の記事 を参照ください。

このサービスに最近 API 機能を追加したので、その方法を紹介します。一通り読んでもらえると、API サーバって割と簡単に作れるんだなーというのがわかると思います。

今回追加した API

API を追加した目的

Admiral Stats は、艦これアーケードというゲームのプレイデータを、ユーザ(提督)から時々アップロードしてもらって、それを時系列に可視化するサービスです。

f:id:muziyoshiz:20160828003101p:plain

プレイデータは、SEGA の公式プレイヤーズサイトから、非公式のエクスポータを使ってエクスポートしてもらいます。そのためのエクスポータも、ブックマークレット版、PowerShell 版、Ruby 版と色々配布しています。

今までは Web ブラウザで Admiral Stats にログインして、「インポート」ページからプレイデータをアップロードしてもらう必要がありました。

f:id:muziyoshiz:20170320163724j:plain:w600

自分で使っていてもこれが面倒だったので、この部分を自動化するために API を作ることにしたわけです。

最終的に作った API の仕様

  • GET /api/v1/import/file_types
    • Admiral Stats がインポート可能なファイル種別のリストを返す。
    • エクスポータによっては、現在の Admiral Stats がまだサポートしていないプレイデータもエクスポートする。この API を使うことで、未サポートのデータをアップロードしなくて済むようにする。
  • POST /api/v1/import/:file_type/:timestamp
    • ボディ部に含まれる JSON(プレイデータ)をパースし、データベースに登録する。
    • :file_type/api/v1/import/file_types が返すファイル種別のいずれか。
    • :timestamp%Y%m%d_%H%M%S 形式。日本にしか無いゲームのためのツールなので、問答無用で JST で解釈する。

この API の呼び出しは、後述するトークンで認証します。例えば、curl で呼び出す場合、Authorization ヘッダにトークンを入れて、以下のように呼び出します(このトークンは架空のものなので、実際に叩くと失敗します)。

happyturn% curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiaWF0IjoxNDg5MDY1Nzg1fQ.q-SlF1tPKyCiNBOzdbQ2JLSqSr7d380WbKxZp818xeo" \
> -i -X GET https://www.admiral-stats.com/api/v1/import/file_types
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
ETag: W/"289acc0adafeb819ebf2fca179abb2a7"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 553b2151-562e-47f7-b46f-c0a41384ed5a
X-Runtime: 0.044076
Vary: Origin
Transfer-Encoding: chunked

["Personal_basicInfo","TcBook_info","CharacterList_info","Event_info"]

使い方

API トークンの発行

ユーザは、まずは API トークンを発行します。Admiral Stats にログインすると、「API トークンの設定」というメニューが表示されるので、ここで新しいトークンを発行できます。

もしトークンが漏れた場合は、「トークンの再発行」ボタンを押すと新しいトークンが発行されて、古いトークンは使えなくなります。このような失効処理は、JWT の機能では実現できないので、後述するテーブルを使って実現しました。

f:id:muziyoshiz:20170320164221p:plain:w600

エクスポータの設定

次に、このトークンをエクスポータに設定します。この設定はブックマークレット版が一番簡単で、Admiral Stats が生成するブックマークレットに、そのユーザのトークンが自動的に埋め込まれるようになります。

f:id:muziyoshiz:20170320164428p:plain:w600

これをブックマークバーにドラッグアンドドロップすると、以下のようなブックマークレットが登録されます。

javascript:(function(u,t,b){var%20s=document.createElement('script');s.charset='UTF-8';s.id='admiral-stats-exporter';s.setAttribute('data-token',t);s.setAttribute('data-skip-backup',b);s.src=u;document.body.appendChild(s)})('https://www.admiral-stats.com/bookmarklets/exporter.js','eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiaWF0IjoxNDg5MDY1Nzg1fQ.q-SlF1tPKyCiNBOzdbQ2JLSqSr7d380WbKxZp818xeo','true');

ちなみに、PowerShell 版の場合は起動時に表示されるプロンプトでトークンを書く、Ruby 版の場合は設定ファイル config.yaml にトークンを書く、という方法で設定できます。

エクスポータの実行

あとは、従来通りに、ブックマークレットを SEGA 公式サイトで実行すると、エクスポートから自動アップロードまで行われます。

ただ、ブックマークレット版は、非同期の JavaScript しか書けない関係上、「エクスポートから自動アップロードのどこかが失敗したらエラーを出す」ということがうまくいきません(無理矢理書くことはできるのかもしれませんが……)。

その対策として、Admiral Stats に「API ログの確認」というメニューを作り、ここを見てもらえれば自動アップロードが成功したかどうかを確認できるようにしました。ちなみに、PowerShell 版と Ruby 版では、実行に失敗するとエラーメッセージが表示されます。

f:id:muziyoshiz:20170320164603p:plain:w600

以上が、API の簡単な使い方の解説でした。詳細は Admiral Stats の使い方 をご参照ください。

JSON Web Token (JWT) への対応

まずは、API のための認証を簡略化するための、トークン発行・検証機能を実装します。このトークンとして、今回は JWT を採用しました。

JWT とは?

JWT とは、認証トークンとして使うために、JSON 形式のデータを Base64 エンコードして署名を付けた文字列のことです。

JWT に関する情報は https://jwt.io/ にまとまっています。また、IETF の RFC 7519 - JSON Web Token (JWT) として仕様策定されています。ちなみに、この RFC によると、JWT の発音は「ジョット」らしいです。

The suggested pronunciation of JWT is the same as the English word “jot”.

JWT は、3個の Base64 文字列を、ドット(.)で繋いだ文字列です。https://jwt.io/ に表示されているサンプルを、試しに irb でデコードしてみます。

irb(main):001:0> require 'base64'
=> true
irb(main):002:0> Base64.decode64('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9')
=> "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"
irb(main):003:0> Base64.decode64('eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9')
=> "{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"admin\":true}"
irb(main):004:0> Base64.decode64('TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ')
=> "L\x95@\xF7\x93\xAB3\xB16p\x16\x9B\xDFDL\x1E\xB1\xC3pG\xF1\x8E\x86\x19\x81\xE1N4X{\x1E\x04"

JWT では、特に暗号化されているわけではないのがわかると思います。また、3個目の Base64 文字列は署名なので、JSON にはなりません。署名の検証は、後述する gem で行えます。

また、トークンを検証する際には「有効期限が切れていないか調べる」とか「必要な権限を持っているか調べる」とか、よく行う作業があると思います。

JWT では、よく使う情報を格納するためのキー名が予約されていて、それらは Claim Names と呼ばれます。例えば、トークンの期限切れ時刻を表す “exp” (Expiration Time) という claim があります。この名前に従う必要はないんですが、従っておくと、JWT のライブラリにビルトインされている検証処理を使えて便利です。

JWT を操作するための gem

https://jwt.io/ には、以下の3種類の gem が載っています。このサイトの比較表では、機能の違いは特にありません。

このなかで、jwt は最も star が多く、README がこれを読むだけでも十分 JWT を理解できそうなくらい充実していました。そこで今回は、無難に jwt を選びました。

Rails アプリに組み込む - トークンの発行

Admiral Stats が発行する JWT のペイロードは、以下の形式にしました。(設定ファイルへのコピペが面倒にならないように)トークンをなるべく短くしたかったため、"iat" の claim だけ採用しています。

{ "id": ユーザID, "iat": トークン発行日 }

トークンの鍵は、secret_key_base の値をそのまま使うことにしました。この値は Rails.application.secrets.secret_key_base で参照できるようです。

また、一度発行したトークンをいつでも無効にできるように、トークンの内容を admiral_tokens というテーブルに保存するようにしました。JWT の検証に成功しても、このテーブルに同じトークンが登録されていない場合は「有効期限切れ」と扱うことにしました。

以下は、token_controller.rb 内に実装した、トークンの発行処理です。

    begin
      AdmiralToken.transaction do
        AdmiralToken.where('admiral_id = ?', current_admiral.id).delete_all

        issued_at = Time.now
        token = JWT.encode({ id: current_admiral.id, iat: issued_at.to_i }, Rails.application.secrets.secret_key_base, 'HS256')

        AdmiralToken.create!(
            admiral_id: current_admiral.id,
            token: token,
            issued_at: issued_at
        )
      end
    rescue => e
      logger.error(e)
      @error = "トークンの発行に失敗しました。(原因:#{e.message}"
    end

Rails アプリに組み込む - トークンの検証

検証処理は、application_controller.rb 内の jwt_authenticate メソッドとして実装しました。この実装は An Introduction to Using JWT Authentication in Rails を参考にしました。

  # JWT で認証したユーザ(提督)の情報を返すメソッド
  def jwt_current_admiral
    @jwt_current_admiral ||= Admiral.find(@jwt_admiral_id)
  end

  # Authorization ヘッダに含まれる JWT で認証状態をチェックするためのメソッド
  def jwt_authenticate
    unless jwt_bearer_token
      response.header['WWW-Authenticate'] = 'Bearer realm="Admiral Stats"'
      render json: { errors: [ { message: 'Unauthorized' }]}, status: :unauthorized
      return
    end

    unless jwt_decoded_token
      response.header['WWW-Authenticate'] = 'Bearer realm="Admiral Stats", error="invalid_token"'
      render json: { errors: [ { message: 'Invalid token' } ] }, status: :unauthorized
      return
    end

    unless @jwt_admiral_id
      # 有効期限の検査
      jwt_admiral_id = jwt_decoded_token[0]['id']
      if AdmiralToken.where(admiral_id: jwt_admiral_id, token: jwt_bearer_token).exists?
        @jwt_admiral_id = jwt_admiral_id
      else
        response.header['WWW-Authenticate'] = 'Bearer realm="Admiral Stats", error="invalid_token"'
        render json: { errors: [ { message: 'Expired token' } ] }, status: :unauthorized
      end
    end
  end

  # Authorization ヘッダの Bearer スキームのトークンを返します。
  def jwt_bearer_token
    @jwt_bearer_token ||= if request.headers['Authorization'].present?
                            scheme, token = request.headers['Authorization'].split(' ')
                            (scheme == 'Bearer' ? token : nil)
                          end
  end

  # JWT をデコードした結果を返します。
  def jwt_decoded_token
    begin
      # verify_iat を指定しても、実際には何も起こらない
      # iat を含めない場合も、iat が未来の日付の場合も、エラーは発生しなかった
      @jwt_decoded_token ||= JWT.decode(
          jwt_bearer_token,
          Rails.application.secrets.secret_key_base,
          { :verify_iat => true, :algorithm => 'HS256' }
      )
    rescue JWT::DecodeError, JWT::VerificationError, JWT::InvalidIatError
      # エラーの詳細をクライアントには伝えないため、常に nil を返す
      nil
    end
  end

ちなみに、エラーメッセージは、以下のように出し分けています。

  • Unauthorized: Authorization ヘッダに Bearer トークンが含まれていない
  • Invalid token: JWT の署名検証に失敗した
  • Expired token: JWT の署名検証には成功したが、テーブル上に無かった

このメソッドをAPI のコントローラ(api_import_controller.rb)の before_action に追加すると、トークンでの認証が可能になります。

また、今回は、普通の Rails アプリに、あとから API サーバの機能を追加しているので、API 用のコントローラのみ CSRF 対策を無効化する必要があります。以下は、コントローラのコードの冒頭です。

class ApiImportController < ApplicationController
  include Import

  # API 用のコントローラでは CSRF 対策を無効化する
  skip_before_action :verify_authenticity_token

  before_action :jwt_authenticate

あとは API の機能そのものを実装すれば、API サーバの実装完了です。

Cross-Origin Resource Sharing (CORS) への対応

ただし、この API をブックマークレットから叩こうとすると、Web ブラウザが以下のようなエラーを出して、クロスドメイン接続をブロックしてしまいます。

クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://www.admiral-stats.com/api/v1/import/Area_captureInfo/20170308_235926 にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダ ‘Access-Control-Allow-Origin’ が足りない)。

この同一生成元ポリシー(Same-Origin Policy)は、セキュリティのための機能ですが、今回のような場合には困ります。この接続を許可するには、API サーバ側が CORS に対応する必要があります。

CORS とは?

CORS とは、別ドメインの Web サーバが明示的に許可した場合に限り、あるドメインから別ドメインへのクロスドメイン通信を許可するための、HTTP の仕様です。

別ドメインの Web サーバが明示的に許可した場合だけ使える、という意味では、できることは JSONP(JavaScript の読み込みとしてリクエストを送り、コールバック関数でラップした JSON を返す)と大差ありません。しかし、CORS のほうが、サーバ・クライアント双方の実装を大幅に簡略化できます。

CORS のプロトコルを大まかに説明すると、次のようになります。

  1. ページ内で読み込んだ JavaScript が、別ドメインへの XMLHttpRequest を send する
  2. Web ブラウザが、1 の URL に対して、OPTIONS リクエストを送信する
  3. Web サーバが、Access-Control-Allow-Origin ヘッダなどを含み、ボディが空のレスポンスを返す
  4. 3 に含まれるヘッダが、1 のリクエストを許可している(例:Access-Control-Allow-Origin ヘッダにそのサイトのドメインが含まれている)かどうかを検査する
  5. 4 の検査をパスしたら、Web ブラウザが、1 の URL に対して、実際のリクエスト(GET や POST)を送信する
  6. Web サーバが、Access-Control-Allow-Origin ヘッダなどを含む、本来のレスポンスを返す

CORS に対応するための rack-cors gem

Ruby on Rails で CORS に対応するためには、rack-cors という gem を使います。

実は、Rails 5 から導入された rails new NAME --api でアプリケーションを作ると、この rack-cors を使うための以下の設定が、コメントアウトされた状態で書き込まれます(参考)。

  • Gemfile に gem 'rack-cors'
  • config/initializers/cors.rb に以下の設定

しかし、Admiral Stats は、すでに普通の Rails アプリとして作ってしまっているので、これと同じことを手作業で行います。

今回は以下の内容で cors.rb を作成しました。設定と、実際に返される HTTP ヘッダの対応関係については、コード中に書いたコメントの通りです。

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  # Insert CORS request headers for API responses
  allow do
    # Access-Control-Allow-Origin: (Origin に書かれたものをそのまま返す)
    origins '*'

    # /api 以下の URL に対してのみ、CORS 対応
    # Access-Control-Allow-Methods: GET, POST, OPTIONS
    # Access-Control-Allow-Headers: (OPTIONS に対してのみ Access-Control-Request-Headers に書かれたものをそのまま返す)
    # Access-Control-Max-Age: 3600
    resource '/api/*',
             :methods => [:get, :post, :options],
             :headers => :any,
             :max_age => 3600
  end
end

rack-cors の注意点

rack-cors を導入すると、OPTIONS メソッドに対してレスポンスを返すようになります。そのために routes.rb を変更する必要はありません

ただ、テストコードのなかで、この OPTIONS へのレスポンスをテストしようとしたところ、以下のように RoutingError が出て失敗しました。IntegrationTest から、rack-cors が生成するルートは見えないようです。

ActionController::RoutingError: No route matches [OPTIONS] "/api/v1/import/Personal_basicInfo/20170309_000000"
    test/controllers/api_import_controller_test.rb:233:in `block (2 levels) in <class:ApiImportControllerTest>'
    test/controllers/api_import_controller_test.rb:232:in `block in <class:ApiImportControllerTest>'

一方、routes.rb に書かれている GET や POST については、以下のように Origin ヘッダをつければ、正しく Access-Control-Allow-Origin ヘッダなどが返されました。

  test 'data_types Origin ヘッダがある場合' do
    get '/api/v1/import/file_types', headers: { 'Authorization' => "Bearer #{TOKEN}", 'Origin' => 'https://kancolle-arcade.net'}

    assert_response 200
    assert_equal JSON.generate(
        [
            'Personal_basicInfo',
            'TcBook_info',
            'CharacterList_info',
            'Event_info'
        ]), @response.body

    assert_equal 'https://kancolle-arcade.net', @response.headers['Access-Control-Allow-Origin']
    assert_equal 'GET, POST, OPTIONS', @response.headers['Access-Control-Allow-Methods']
    assert_nil @response.headers['Access-Control-Allow-Headers']
    assert_equal '3600', @response.headers['Access-Control-Max-Age']
    assert_equal 'true', @response.headers['Access-Control-Allow-Credentials']
  end

そのため「OPTIONS についても同様のヘッダが返されるはずだ」と考えて、テストを書くのは諦めました。まあ、すべての API に OPTIONS のテストを書くのは、現実的ではないですしね……。

以上で、ブックマークレット版のエクスポータからも、Admiral Stats の API を叩いて、JSON ファイルをアップロードできるようになりました。

まとめ

今回は、普通の Ruby on Rails 5 アプリに、あとから API 機能を追加するために必要だったことをまとめました。API 機能のために、jwt gem を使ってトークンの発行・検証を行い、rack-cors gem を使って CORS 対応(ブックマークレット対応)を行いました。

実際にやってみて、やり方さえわかれば、割と短時間で実装できるもんだな……と思いました。Admiral Stats のソースコードは GitHub で公開しているので、興味のある方は読んでみてください。

github.com