0

koa v2でsocket.ioを使う

koa 2.0.0-alpha.3
socket.io 1.4.5

app.listen(port)せずにapp.callback()すればhttp.Serverに渡せるハンドラが返ってくるとのことだったのでそのようにしたらできた。

import Koa from "koa";
const app = new Koa;

import convert from "koa-convert";
import koaStatic from "koa-static";
app.use(convert(koaStatic("./public")));

import {Server} from "http";
const server = Server(app.callback());

import SocketIO from "socket.io";
const io = SocketIO(server);

import pkg from "../../package.json";
app.name = pkg.name;

import logger from "koa-logger";
app.use(logger());

import router from "./route";
app.use(router.routes());

import Jade from "koa-jade";
new Jade({
app: app,
viewPath: "server/view",
debug: app.env !== "production",
noCache: app.env !== "production",
helperPath: [ ]
});

module.exports = {
app: app,
server: server
};


const server = require("./src/server").server;

const port = process.env.PORT || 3000;
server.listen(port);

あとは io.on(“connection”, (socket) => { }); とかで接続が取れる
クライアント側も普通に /socket.io/socket.io.js ができてる

1

React+Fluxxor+socket.ioでfluxなチャットを作った

fluxxorとsocket.ioを使ってチャットを作った。大変ミニマムな感じの実装を心がけてて、今後何かWebアプリを作る時はこれを少し組み替えて作るぞというのを意識してる。ようするに低機能最低限でしょぼい。サーバー側はexpressとmongodb。

https://github.com/shokai/node-flux-boilerplate

herokuでも動く
https://node-flux-boilerplate.herokuapp.com/

クライアントはこんな実装


Fluxアーキテクチャ

Reactはなんとなくわかってfluxをやるか、と思ったらflux npmの中がdispatcherしかなかった。
flux.DispatcherのwaitForの実装


まずこのへんを読んでフムフムなるほど(わかったようなわからないような)となる。
Flux | Application Architecture for Building User Interfaces


で、このスライド見たらわかりやすすぎて気絶した。とりあえずなんらかのfluxフレームワーク使ってチャットでも作る事にした。


あとこっちも、フレームワークなしで最低限のfluxを自作するとしたらこんな感じになるのね、とわかって気が楽になる。
これ読んでおけば、Fluxxor等のフレームワークがどういう工夫をしているか差分を理解しやすくなりそう。
10分で実装するFlux



Fluxxorというフレームワークを使う



http://fluxxor.com/


FluxxorはFluxなフレームワークで、StoreとActionとDispatcherを担当する。ReactをViewとして使うこと前提に作られている。

この記事がとてもわかりやすい
Fluxなフレームワーク、Fluxxorの紹介 – マルシテイアは月の上


で、Fluxxor+ReactにSocket.ioを追加してチャットをつくってみて、こんな感じの実装になった。


fluxはaction→dispatcher→store→view→actionの4つの部品で構成されるが、fluxxorは三叉のロゴのとおりDispatcherが無くてaction/store/viewだけになる。

Dispatcherはfluxxorを使うことでactionとstoreに内蔵されるようになっている。そしてactionからはdispatch関数のみが、storeからはbindActionでの受信のみがアクセスできる様になっているので、action→storeの1方向にしかメッセージが流れない。試してないけどwaitForで順序制御もできるようだ。

fluxorのstoreにはeventemitterが継承されていて、emit(“change”)するとViewにイベントが飛ぶ。ViewはcreateClassした時のmixinsにFluxxor.storeWatchMixin(Store名)を指定すると、changeイベントを受けたらrenderを呼ぶようになっている。ここでも1方向のメッセージングが強制される。

Viewからactionを呼び出すのはthis.getFlux().actionsの下に関数が生えているので、それを呼び出す。

ふつうにfluxを自分で作ったら、dispatcherがどこからでも見える場所にあったりとかで、1方向にしかメッセージを流さないことを心がける事はできても、メッセージの逆流を防ぐ実装をするのはちょっと面倒臭い。fluxxorに乗ってやると円環の理が強制的に生まれる。


socket.ioどこに置くか問題


socket.ioからの入出力はactionを通してfluxとやりとりする。

受信
socket.ioからチャットメッセージ受信→action→storeのlogsに追加→reactがView更新

送信
チャット送信ボタン押す→action→socket.ioでサーバーに送信

socket.io関係のコードをどこに置くかでけっこう迷った。
最初はChatStoreに持たせようとして、でもただの「接続しました!」みたいなStore関係ないメッセージ表示するようなコードはどこに置くのか。

結局fluxの外に独立した部品として設置してactionで通信することにしたけど、送信時のactionからのdispatchを受信させる方法が良くない。dispatchはstoreのbindAction以外にも、flux.on “dispatch”, (type, payload) ->でも受信できるけど、

An action of type send-chat was dispatched, but no store handled it dispatcher.js:105


というwarningがどうしても出される。これを避けるにはどうしてもStoreのフリをするしかないし、そもそもdispatchがstoreじゃない所に飛ぶ可能性を作るのはせっかくfluxとかいう円環の理を作ってバグの場所を特定しやすくした意味がぶち壊しになるのでやめた。
おとなしくactionから関数を呼び出すことにした。socktes/という別ディレクトリを掘ってその中にsocket.io関連のコードはまとめて、fluxとのやりとりは必ずaction経由でやる。

0

socket.io-client-simple gemをsocket.io 1.1.0に対応した

socket.io 1.1.xに対応した、socket.io-client-simple gem 1.1.2を今朝リリースした。
これを使うとrubyでsocket.ioサーバーに接続できる。

https://rubygems.org/gems/socket.io-client-simple
Comparing v1.0.0…v1.1.2 · shokai/ruby-socket.io-client-simple

1.1.2のクライアントでsocket.ioサーバー1.0系に日本語を送ると文字化けするので注意してほしい。「ああ」が「BB」になったり「はい」が「oD」に化ける。


socket.io 1.1.0


1.0→1.1での変更
Socket.IO — Socket.IO 1.1.0


だいたいbug修正じゃね?と思ってたら、マルチバイト文字列のエンコーディング方法が修正された。

これにより、1.0系のsocket.io-clientから1.1系のサーバーに日本語などのマルチバイト文字を送ると文字化けするし、1.1系のクライアントから1.0系のサーバー通信しても文字化けする。

サーバーとクライアントのバージョンを合わせるか、URLエンコードするかすればいいと思う。


あと、websocket proxy(herokuやnginxなど)を前に建てたsocket.ioサーバーと接続している時に、proxyが生きているけどサーバーが再起動した時などに切断を検知できるようになった

0

socket.io-client-simple gemをsocket.io 1.0.x対応した

5月末にでたSocket.IO 1.0に、socket.io-client-simpleを対応させた。

1.0対応は2週間ぐらい前にだいたいできてたんだけど、切断の検知と自動再接続する処理のあたりがうまくいかなくてリリースできてなかった。rubyのthreadの闇は深い。


うまく動くようになったのでどうぞ

% gem install socket.io-client-simple

変なところあったらgithubにissue立てたり、@shokaiにお知らせください


経緯


socket.io meetupに行ったら意識が高まったので、プレゼンとか聞きながらデバッグしてたらできてきて、意識高まりすぎて寿司とビールスルーして帰って電車の中でgem pushした。


socket.io 0.9 → 1.0の変更点メモ

ソースを読んだりして挙動を調べた。
研究室のwikiに書いたのメモのコピペで、たぶんgeta6とかnekobatoが書いてくれた情報も含まれる

クライアント作者としてはping-pongがサーバー→クライアントだったのがクライアント→サーバーになったおかげで切断検知しやすくなってうれしい感じした。


engine.io 1.1 -> 1.2に上がった

むしろこっちの変更の方が多いような気もする、あんまりちゃんと追えてない


初期化プロセスが変わった

最初に接続可能なプロトコル一覧を取るところから違う
xhr(comet?)で最初つないで、websocket使えるならwsに移行する

ということはrubyのsocket.ioクライアントはwebsocketだけのシンプル実装じゃだめ?
と思ったけど、いきなり/socket.io/?transport=websocket にwebsocket接続すればokだった。transportオプションがキモ

node.jsでもクラスターを取るとupgradeリクエストの後で400を返してしまう
クライアントの接続オプションにtransports: [‘websocket’, ‘polling’]を渡して以前と同じ挙動にできる
io.connect window.location.host, {transports: [‘websocket’, ‘polling’]}

failするユーザが高速につなげるようにするためにxhr-pollingを先行させている
開発するもののターゲットにレガシーブラウザが入っていなければwsを先行していいと思う

バージョンが混ざったらやばい

1.0のクライアントを0.9のサーバーに接続させようとするとサーバーで大量の”unhandled socket.io url”エラーが出る


通信データの格納方法が違う

server/client両方で環境変数 DEBUG=* を付けて通信させてみるとわかりやすい
頭に数字(制御コード)が付いたjsonをやりとりする。v0.9までは、”数字:::JSON” だった

“0” – 接続時にsession idなどを通知
0{"sid":"tOqSjGeyyhBo5x3vAAAA","upgrades":[],"pingInterval":25000,"pingTimeout":60000}

“41” – 切断
サーバーで明示的にsocket.disconnect()した時にクライアントに送られてくる
ただ “41” だけが送られてくる。


“42” – data
サーバーでemit(‘chat’, {msg: “hello”}, {foo: “bar”})した場合
42["chat",{"msg":"hello"},{"foo":"bar"}]
が送られてくる
送信時も42を付けて配列のJSONで送る


“1:6” – fast upgrade
はやくwebsocketにupgradeしろって指示をサーバーからクライアントに送る


“2” – ping
clientからserverへのping


“3” – pong
pingを受けての、serverからclientへのpong。これがすぐ返ってこなかったら、前段のnginxか何かにsocketがつながっているだけで、バックエンドのsocket.ioサーバーが息してないと判断できる


“2probe” – ajax ping
“3probe” – ajax pong

“5” – upgrade
ajax-pollingからwebsocketにupgradeする時にクライアントからサーバーに送る


heartbeatの手順が変わった

v0.9まで
サーバーが “2::” を送ってくるので、clientはすぐに”2::”を返す

v1.0以降
clientが”2″を送るとサーバーがすぐに”3″を返す
clientは接続してからすぐにサーバーに2を送らないと数秒で切られる

接続時の”0″で指定されたpingIntetrval(ミリ秒)毎に”2″を送ればok

この変更はすごく良い。普通TCP socketだとクライアントはなにかパケットを送ってみないと切断を検知できないので、今まで切断検知の為だけにnull送ってて無駄だった。

サーバーは何もしなくてももともと切断検知できるできる


io.configureが無くなった

log levelの設定はdebug npmを使う


io.connected

クライアントライブラリ側で接続状態を確認するio.socket.connectedがio.connectedに移動してた


シングルトン

client libは複数インスタンスを作っても、同じサーバーへの接続ならコネクションが共有される(単にキャッシュされた同じインスタンスが返ってくる)


client npmのIPアドレスが取得できない

socket.io-client npmからの接続のみ、サーバー側でクライアントのIPアドレスが取得できない
v0.9ではsocket.handshake.address.addressに入っていた
ブラウザjsからの場合、socket.handshake.address.address に入っている
client npmからだとaddressがnull


encoding

全体がUTF-8でエンコーディングされるようになった

0

express+socket.io+mongooseのテンプレ作った

作った
https://github.com/shokai/express-template

簡単なチャット。チャットログはMongoDBに保存する。
https://express-chat-template.herokuapp.com/


ガチwebサービスではなくゆるふわプロトタイピング用のテンプレになっている。expressあんまりよくわかってないから変な所あったらissueとか建てて盛大にdisって欲しい。
基本的にgeta6/coahを参考にしてるんだけど、ブラウザの為にcoffeeをコンパイルしてjsにするとか諸々の強烈なasset pipelineを取り外した。

構成はこんな感じ

  • express 4.4
  • socket.io 1.0
  • mongoose 3.8
  • coffee-script
  • ブラウザもcoffee-script.js
  • herokuにデプロイ
  • mocha + supertest + Travic CI
  • gruntでwatchしてcoffeelintとmocha回す


ブラウザでcoffeeコンパイルして実行してるけど、文法エラーはlintかけてるし、実行時エラーはコンソールにも下の様にちゃんと出るからsourcemapとかいらないしでだいぶすっきり構成になったと思う。