動機
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-version
と Gemfile
に記載されている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で接続するようにしました。
結果はログに出るだけです。
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 を見つけました。
コレ使えばよかったのかも。。