ActionCableをAndroidからOkHttpで使ってみる

  • 1
    いいね
  • 0
    コメント

動機

RailsのWebアプリケーションを作る際に、ActionCableでお手軽にWebSocketを行いたい。
けど、今後の拡張性を考えると、Android/iOSともWebSocketでつなげる必要がある。
とりあえず、Androidで出来ればiOSでも出来るだろう。

先に結論

とりあえず、JSONのフォーマットさえちゃんとしたら、Androidでも動くっぽい。
ただ、 disable_request_forgery_protectionしてるので、本番では問題あるかも?

クライアント側でもある程度実装が必要っぽいので、本番では使わないかなぁ。
(素のWebSocketでの実装・その他の実装も比較する必要はありますが。)

前提

サーバサイド

Ruby: 2.3.1
Rails: 5.0.0.beta1 (バージョン上げようとしたらいくつかエラーが出たので断念。)

https://github.com/JunichiIto/campfire を一部改変して利用しました。(heroku readyなのがありがたい)

クライアント

Android: 4.2.2
okhttp: 3.4.2

サーバサイドの準備

サンプルから改変

https://github.com/JunichiIto/campfire をcloneしてくる。

.ruby-versionGemfileに記載されているRubyのバージョンを、2.3.1に変更。

config/environments/production.rb でrequest originが https?で始まるものに限定していたので、その指定すら無くすようにしました。

-  config.action_cable.allowed_request_origins = [ /https?:\/\/.*/ ]
+  # config.action_cable.allowed_request_origins = [ /https?:\/\/.*/ ]
+  config.action_cable.disable_request_forgery_protection = true

(これって、http headerとかで見ているのでしょうか?本来であれば、何かしら制限しておく必要がある?)

herokuにデプロイ

READMEに書いてある通り、herokuにアップロードする。
heroku open するとURLが分かるので、後でAndroidの実装に組み込む。

クライアントの実装

ライブラリのインポート

compile 'com.squareup.okhttp3:okhttp:3.4.2'
compile 'com.squareup.okhttp3:okhttp-ws:3.4.2'

(ただ、 https://github.com/square/okhttp/pull/2852 を見ると、次のバージョンとかでいろいろ変わるかも?)

送受信の実装

今回は、ActivityのonCreateで接続するようにしました。
結果はログに出るだけです。

MainActivity.java
package hm.orz.chaos114.websocketsample;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.ws.WebSocket;
import okhttp3.ws.WebSocketCall;
import okhttp3.ws.WebSocketListener;
import okio.Buffer;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Request request = new Request.Builder()
                .url("wss://INPUT_YOUR_DOMEIN.herokuapp.com/cable") // INPUT_YOUR_DOMEINは、heroku openしたときのドメインを指定してください。
                .build();

        OkHttpClient client = new OkHttpClient.Builder()
                .build();
        WebSocketCall call = WebSocketCall.create(client, request);
        call.enqueue(new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                Log.d(TAG, "onOpen");

                try {
                    webSocket.sendMessage(RequestBody.create(WebSocket.TEXT, "{\"command\":\"subscribe\",\"identifier\":\"{\\\"channel\\\":\\\"RoomChannel\\\"}\"}"));
                    webSocket.sendMessage(RequestBody.create(WebSocket.TEXT, "{\"command\":\"message\",\"identifier\": \"{\\\"channel\\\":\\\"RoomChannel\\\"}\",\"data\":\"{\\\"message\\\":\\\"sample message!!!\\\",\\\"action\\\":\\\"speak\\\"}\"}"));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public void onFailure(IOException e, Response response) {
                Log.d(TAG, "onFailure " + response, e);
            }

            @Override
            public void onMessage(ResponseBody message) throws IOException {
                Log.d(TAG, "onMessage " + message.string());
            }

            @Override
            public void onPong(Buffer payload) {
                Log.d(TAG, "onPong");
            }

            @Override
            public void onClose(int code, String reason) {
                Log.d(TAG, "onClose");
            }
        });
    }
}

起動すると、RoomChannelに接続し、sample message!!!というテキストを送信します。
heroku openしたブラウザを確認すると、メッセージが追加されていくはずです。

仕組みの考察メモ

  • ApplicationCable::Channel を継承したクラス名が、Channel名となる。
  • Channel名を指定して、subscribeコマンドを発行すると、受信可能状態になる。
  • ApplicationCable::Channel を継承したクラスのメソッドが、Action名になる。
  • Action名を指定して、messageコマンドを発行すると、メッセージを送信できる。
  • 部屋を分ける場合は、 https://github.com/rails/rails/tree/master/actioncable#passing-parameters-to-channel このへんを参考にしたら出来るかも?

参考

いろいろ実装したあとで、 https://github.com/hosopy/actioncable-client-java を見つけました。
コレ使えばよかったのかも。。