iOS/Androidアプリにおける
APIサーバーのいろは

YAPC::Hokkaido 2016.12.10 Sat @札幌市産業振興センター
Yuji Shimada (@xaicron)

みなさん

APIサーバー書いてますか?

いろいろと API を作って来た自分がここ数年
iOS/Android アプリ向けの API を作るにあたって考えたことを発表します。

今日のアジェンダ

はい

すべてのAPIはSSLにすべし!

すべての API は SSL にすべし!

すべての API は SSL にすべし!

すべての API は SSL にすべし!

すべての API は SSL にすべし!

よくある質問

Q. HTTPS にしたらアクセス遅くないですか?

よくある質問

Q. HTTPS にしたらアクセス遅くないですか?

A. 一概には言えませんが、HTTP に比べたらハンドシェイクに時間がかかるケースが多いです。
しかし先述したように iOS からのリクエストはすべて ATS に準拠することが推奨されています。

よくある質問

Q. HTTPS にしたらフロントサーバーの負荷やばそう

よくある質問

Q. HTTPS にしたらフロントサーバーの負荷やばそう

A. SSL アクセラレータを買いましょう。
またはそれに準ずるなにかを利用するのがベターです。
(nginx + ssl cache on memcached 的なものとか)

よくある質問

Q. HTTPS にしたらフロントサーバーの負荷やばそう

A. SSL アクセラレータを買いましょう。
またはそれに準ずるなにかを利用するのがベターです。
(nginx + ssl cache on memcached 的なものとか)

もし、そういったものが利用できない場合でも、数年前に比べてハードウェアの性能が上がっていますので、SSL の処理が重くて死ぬ!みたいなことが発生するケースは減っているのではないかと思います。(要出典)

よくある質問

Q. え、API のアプリケーションサーバー自体も HTTPS に対応しないといけないの?

よくある質問

Q. え、API のアプリケーションサーバー自体も HTTPS に対応しないといけないの?

A. SSL アクセラレータまたはそれに準ずるなにかが HTTP にしてプロキシーすればいいので、イントラ内では不要と考えています。

よくある質問

Q. 現時点で HTTP/2 じゃないの?
A. もちろん HTTP/2 でもいいです。(将来的にはそうなるでしょう)
iOS9 からは NSURLSession が、Android では OkHttp が対応しています。
でも iOS8 をサポートする場合は別途 HTTP Client を選定する必要があるでしょう。

まとめ

今後 SSL ではないことによるデメリットのほうが多くなっていくと思われますし、数年前に比べてもサーバーの性能は上がっており、ソフトウェアも改善されていっているので、早めに SSL 化をしてドヤりましょう

APIの認証について

APIの認証について

というのが基本的な要件になります

認証にはJWTを利用

JWTとは

eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ6aWdvcm91IiwiZm9vIjoiYmFyIiwiZXhwIjoxNDgxMjExNzk4fQ.NQMMtnMK9L2JX6-7mheTGvUjMt9Vksq9RyX4GrKdbe0

みたいな文字列

JWTとは

JWTとは

JWTとは

# Header
{"alg":"HS256"}
# Claims
{"iss":"nekokak","foo":"bar"}

secret で署名すると

eyJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJuZWtva2FrIn0.e1zHN8T8VkS8GYddVF3CJeJ5QyrXKQgG3x6AuuWOp18

のようになる

JSON::WebToken で書くと以下のような感じ

my $jwt = JSON::WebToken->encode(
  { iss => 'nekokak', foo => 'bar' }, # Claims
  'secret',                           # secret
  'HS256',                            # alg (デフォルトがHS256なので省略可)
);

JWTを認証に利用する

JWT を HTTP Header に入れてアプリから API リクエストを行う。このとき、JWT の Claims に認証情報をいれる。

例えば

などを送ることで、JWT を検証するのみで、ユーザーの認証が可能になります。

JWTを認証に利用する

実際の利用ケースとしては以下のように Authorization ヘッダーにぶっこむなどする
と良いでしょう。

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJuZWtva2FrIn0.e1zHN8T8VkS8GYddVF3CJeJ5QyrXKQgG3x6AuuWOp18

まとめ

APIのデータ形式について

JSON-RPCの利用

JSON-RPCとは

JSON-RPCとは

リクエスト形式は以下のようなもので

{
    "id": "xxxxx",
    "jsonrpc": "2.0",
    "method": "someMethod",
    "params": { ... }
}

レスポンスは以下のような感じ

{
    "id": "xxxxx",
    "jsonrpc": "2.0",
    "result": { ... }
}

JSON-RPCとは

たとえば、getTweet のような API があったとしたら

{
    "id": "xxxxx",
    "jsonrpc": "2.0",
    "method": "getTweet",
    "params": {
       "tweetId": "123456789" # ツイートのユニークな ID をしていする
    }
}
{
    "id": "xxxxx",
    "jsonrpc": "2.0",
    "result": { # ツイートオブジェクトが返ってくる
       "tweetId": "123456789",
       "screenName": "xaicron",
       "tweet": "お前はいままで逃した終電の数を覚えているか?",
       ...
    }
}

のような感じになるでしょう。

とっても簡単ですね😊

エラーケース

リクエスト形式は以下のようなもので

{
    "id": "xxxxx",
    "jsonrpc": "2.0",
    "method": "someMethod",
    "params": { ... }
}

レスポンスは result がなくて error が返ってくる

{
    "id": "xxxxx",
    "jsonrpc": "2.0",
    "error": {
       "code": -32600,
       "message": "Invalid Params"
    }
}

JSON-RPC over HTTP

さて、JSON-RPC は通信プロトコルまでは定義されていません。
HTTPS で通信することを決めましたので、HTTP プロトコル上で JSON-RPC を使う事になります。

JSON-RPC over HTTP?

JSON-RPC over HTTP?

たとえば、Invalid Params を例に上げると

400 Bad Request

{
    "id": "foo",
    "jsonrpc": "2.0",
    "error": {
       "code": -32600,
       "message": "Invalid Params"
    }
}

みたいな感じでしょうか?

JSON-RPC over HTTP?

たとえば、Invalid Params を例に上げると

400 Bad Request # <- HTTP Status

{
    "id": "foo",
    "jsonrpc": "2.0",
    "error": {
       "code": -32600, # <- JSON-RPC Error Code
       "message": "Invalid Params"
    }
}

なんかエラーコードが2つあるぞ...!!!

複数のエラーコードを処理するのは人類には早すぎる

JSON-RPC over HTTP

アプリ側からは

JSON-RPC over HTTP

アプリ側からは

-> この2つのルールを守るだけでアプリ側のエラー処理がものすごく簡単になる!

JSON-RPC over HTTP

HTTP ステータスを最初に確認するときに簡単に原因が分かるので、大変はかどる

JSON-RPC over HTTP

たとえば

JSON-RPC over HTTP

たとえば

クライアントエンジニア (以下ク): なんか 401 返ってくるんだけど?

JSON-RPC over HTTP

たとえば

クライアントエンジニア (以下ク): なんか 401 返ってくるんだけど?
サーバーエンジニア(以下サ): 社内ネットワークじゃないと Basic 認証かかってるよー

JSON-RPC over HTTP

たとえば

JSON-RPC over HTTP

たとえば

ク: 502 が返ってくるんだけど?

JSON-RPC over HTTP

たとえば

ク: 502 が返ってくるんだけど?
サ: サーバー落ちてた、正直すまんかった

JSON-RPC over HTTP

たとえば

JSON-RPC over HTTP

たとえば

ク: 400 が返ってくるんだけど?

JSON-RPC over HTTP

たとえば

ク: 400 が返ってくるんだけど?
サ: ドメインとかパスとかあってるンゴ?

大変わかりやすいですね😊

まとめ

API種別毎にアクセスを分散したい

すべての API が同じ速度、同じリソース使用率、同じリクエスト数であることはまず無いので、APIごとに負荷を分載したいというニーズが自ずと出てくるかと思います。

JSON-RPC でいうと method 単位で切り分けたいですね。

また、API のバージョンなどで受け付けるサーバーを切り替えたりしたいことも考えられます。

しかし、JSON-RPC のみで API ごとにリクエストを分散するのは若干スマートではありません。

method単位で振り分けたいとき

JSON-RPC over HTTP ではリクエストボディをパースして method を得る必要があります。

{
    "id": "xxxxx",
    "jsonrpc": "2.0",
    "method": "vearyHevyMethod", # この method は専用のサーバーに振り分けたい!
    "params": { ... }
}

method単位で振り分けたいとき

通常、API のアプリケーションサーバーが直接クライアントアプリからリクエストを受
け付けるのではなく、nginx や Apache などのフロントサーバーが存在していると思います。

愚直やるとリクエストボディを、フロントサーバーで JSON-RPC の解析をしなくてはいけなくて若干イケてません。

というわけで、普通に URL で分けることにしましょう。

URLで分けちゃう

URLで分けちゃう

具体的にいうと以下のような形式にします。

https://api.example.com/{version}/{method}

URLで分けちゃう

例えば以下のような形式にします。

https://api.example.com/{version}/{method}

URLで分けちゃう

例えば以下のような形式にします。

https://api.example.com/{version}/{method}

フロントで以下のような URL を受けられるようにすることで、それぞれの API のエンドポイント毎にリクエストを振り分けることが可能になります。

https://api.example.com/v1.0/getBestSpeakerAward

API種別毎にアクセスを分散したい

ただし、愚直にエンドポイントを追加していく方法だと、フロントと API 両方を変更する必要があるので、インフラエンジニアの人とあれこれしないと行けないですね。

API種別毎にアクセスを分散したい

ベースとして以下のようにワイルドカードで指定しておいて、必要なやつから徐々に分散していくのが良いでしょう

例えば nginx だったら

location ~ ^/v\d+.\d+/getBeer$ { proxy_pass http://beer.example.com/; }
location ~ ^/v\d+.\d+/.+$      { proxy_pass http://all.example.com/; }

のような感じにしておけばカジュアルに method が追加できて便利ですね。

クライアント側ではどうするの?

version + method で URL を自動生成するようにしておけば、新しい method を追加したときでも何も気にせずにこの仕組に乗れるのでとっても簡単です!

まとめ

はい

今日は珍しく API の中身の話ではなく、その周辺のアーキテクチャ的な話をしました。

ただ、今後はオンプレでこういうのを考えることは減っていくと思いますし、「それクラウドでw」という「それクラ」も増えていくでしょう。(というかそうなっている。)

だいたい2014年ぐらいに考えて実装した仕組みですが、だいたい現在もクラウドでマイクロサービスあんじゃ〜!って感じでそんなに当時考えていたことを未来がずれてなかったな〜という感じです。

とはいえ今後は API サーバーを自分で作るとかなくなっていくと思いますし、よりサービスを良いサービスを作る方向にフォーカスしやすくなっていって良い世の中だなって思いました!(小並感)

ご清聴ありがとうございました

ご質問タイム

j or →: next

k or ←: prev

h or ↑: list

l or ↓: return

o or ↵: open

? or /: toggle this help