RubyでFCMのHTTPv1 APIをHTTP/2を使ってやりとりするffccmmxをリリースした

ということでgem作者になりました。どうぞお使いください。

rubygems.org

RubyでFCMのAPIクライアントとしてはfcmpushfcmが有名です。これらのgemは非常に使い勝手がいいのですが、使用するHTTPのプロトコルがライブラリの都合上HTTP/1.1に限定されるという話題があります。

FCMのAPIはHTTP/2に対応しているため、通信を多重化できます。これを使うと、まとめて複数人に通知するユースケースでは大幅な時間短縮が見込まれます。

しかしRubyのFCMクライアントでHTTP/2に対応しているものは見た感じ存在しないという問題がありました。既存のgemにPRを送るとしても内部で利用しているHTTP Clientの選定から変更するという抜本的な対応をせざるをえないため、クライアントの使用感が大幅に異なるという問題も孕んでいました。

ということで既存のgemであるfcmpushを参考にさせていただき、HTTPクライアントごと切り替えたライブラリであるffccmmxを作りました。命名Rubyっぽい名前として色々悩みましたが、内部的にHTTP Clientとしてhttpxを利用していることと、デフォルトでHTTP/2を使うのでfcmを2倍してffccmmxです。

多重送信用機能

concurrent_XXXという命名規則で提供しているメソッドを使うと多重送信でリクエストされます。 機能としてはhttpx側で制御しており、オプションもインスタンス作成時に渡せるようになっているので、httpxが提供している機能がそのまま使えるようになってます。

@client = Ffccmmx.new(project_id)
notification_messages = [ 
  {
    message: {
      token: device_token,
      notification: {
        title: "test title",
        body: "test body"
      }
    }
  },
  {
    message: {
      token: other_device_token,
      notification: {
        title: "test title",
        body: "test body"
      }
    }
  },
]
responses = @client.concurrent_push(notification_messages)
responses.each do |response|
  httpx_response = response.value
end

多重送信の場合はResponse型で返ってくるので、#valueでhttpx responseそのものにアクセスできます。

リトライ系

エラーが返ってきた場合リクエスト可能かどうかもライブラリ側で判定、リクエスト可能であった場合は次にリクエスト出来る時間をヘッダまたは手動で計算します。

begin
  @client.push(notification_message)
rescue Ffccmmx::HTTPXRetryableError => e
    puts "Retry after: #{e.retry_time}"
end

ヘッダがわたってこなかった場合の対策として、countで試行回数を指定するといい感じに指数オーダーで計算してくれます。

e.retry_time(count: 2) #  Time.now + 2**2

#retry_timeはTimeオブジェクトを使うので、ActiveJobを使っている場合はwait_untilとしてそのまま渡せます。

実際使うとどうなるか

4000msくらいかかっていた通知ジョブで実験したところ300msくらいになりました。

感想

このライブラリの作業きっかけではないのですが、ちょっと作成している間に体調崩すみたいなトラブルがあったのですが、無事リリースできて良かったです。(今はある程度回復してきた)

今回はRubyのHTTPクライアント事情やwebmockの雰囲気、gemの命名問題など、色々と学ぶことが多かったです。

HTTPクライアントはいろいろとエントリを見たソースコードを見たりしたのですが、意外とRubyもいろいろあるのね...!という感じでした。

PerlCPANモジュールを作るときといえば現代ではminillaですが、Rubyの場合はbundlerで雛形が作れるのが体験として手軽で良かったです。特にRakefileでアップロード用のtaskが生えてたり、すでにrubocopの設定がされていたりと至れりつくせり....。最近は型の情報も雛形があってなるほど〜〜という感じで書いてました。

webmockとかもわりとRSpecのbefore/afterなどでうまく組み合うように書く必要があるのが、とりあえずスコープで全て管理していたPerlとの違い...というのが結構わかってきたなという印象です。Perlだとインスタンス変数にモックいれて有効な間はドーンみたいな感じなので....。

ちなみに完全にPerlのノリで名前を「Fcmpush::HTTPXとかにしようかな...」とか言ってたら全会一致で「拡張モジュールっぽいからやめとけ」みたいな話題になり、これがRubyの世界...!となっていたりしました。(PerlだとXXX::TinyとかでXXXと同じインターフェイスの別物、がありがち)

あとは時止める系のライブラリがtimecopなのめちゃくちゃいいですね。

また高速化は仕事の一環としてやらせていただいたので、 id:h6n さんやぱるすさんのレビューもいただいてリリースできました。ありがとうございました。