以前の記事「Phoenix Channelとelm-phoenixについて - Qiita」でElmクライアントからPhoenix channelを利用する方法を示しました。この時のexampleはチャットシステムでした。以下のような会話の流れになります。
Elmクライアント <---> Phoenix Channel <---> Elmクライアント
今回はこのチャットシステムをそのまま変更せずに利用して、これにElixirクライアントを加えてみたいと思います。Elixirクライアントは、ブラウザアプリではないので、TwitterからTweetを拾って、そのままchannelに流したいと思います。
Elmクライアント <--- Phoenix Channel <--- Elixirクライアント <---Twitter Stream
ElixirとTwitterについても過去の記事「ElixirでTwitterを検索する - Qiita」を参考にします。
さて、Elixir を Phoenix channelのクライアントとして使うために、PhoenixChannelClientというライブラリをを選択しました。同じようなライブラリがいくつかありましたが、ヤマカンで使いやすそうなものを選びました。いい加減です。
プロジェクトを開始します。
mix new channel_client
cd channel_client
次にapplication environmentを利用して初期設定を行います。すなわちmix newで作成されたconfig/config.exsファイルにTwitter APIのキーを設定します。以下の項目をファイルの末尾に追加します。
#
config :extwitter, :oauth, [
consumer_key: "xxxxx",
consumer_secret: "xxxxx",
access_token: "xxxxx",
access_token_secret: "xxxxx"
]
#
さて、mix.exsにphoenixchannelclientと、twitter関連のライブラリを追加します。
#
defp deps do
[
{:phoenixchannelclient, "~> 0.1.0"},
{:oauther, "~> 1.1"},
{:extwitter, "~> 0.8"}
]
end
#
次のコマンドでライブラリをインストールします。
mix deps.get
これで準備が整いました。
別端末を開いて「Phoenix Channelとelm-phoenixについて - Qiita」のチャットサーバを立ち上げます。
iex -S mix phx.server
ブラウザからこのサーバにアクセスして、チャットウィンドウを開きます。User1でログインします。
さて元のElixirの端末に戻り、シェルを立ち上げます。
iex -S mix
シェルが立ち上がったら、以下のコマンドを順番に入力します。特にプログラムファイルは書きません。
{:ok, pid} = PhoenixChannelClient.start_link()
{:ok, socket} = PhoenixChannelClient.connect(pid,
host: "localhost",
port: 4000,
path: "/socket/websocket",
secure: false,
heartbeat_interval: 30_000)
channel = PhoenixChannelClient.channel(socket, "room:lobby", %{user_name: "User9"})
PhoenixChannelClient.join(channel)
pid = spawn(fn ->
stream = ExTwitter.stream_filter(track: "ラーメン")
for tweet <- stream do
IO.puts tweet.text
PhoenixChannelClient.push(channel, "new_msg", %{msg: tweet.text})
end
end)
socketを作り、channelを作り、joinし、twitterのstreamをリッスンし、「ラーメン」というキーワードを含んでいたら、channelにチャットを書き込みます。ポイントは「"room:lobby"」というtopicに「 %{user_name: "User9"}」でjoinしていることです。サーバ側のchannelにインターフェースを合わせています。これだけで既存のチャットシステムに参加できてしまいます。
以下が、Elixirクライアントの端末のキャプチャーです。IO.puts tweet.text の出力が流れています。
[root@www13134uf channel_client]# iex -S mix
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:3:3] [ds:3:3:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.7.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> {:ok, pid} = PhoenixChannelClient.start_link()
{:ok, #PID<0.164.0>}
iex(2)> {:ok, socket} = PhoenixChannelClient.connect(pid,
...(2)> host: "localhost",
...(2)> port: 4000,
...(2)> path: "/socket/websocket",
...(2)> secure: false,
...(2)> heartbeat_interval: 30_000)
{:ok, %PhoenixChannelClient.Socket{server_name: #PID<0.164.0>}}
iex(3)> channel = PhoenixChannelClient.channel(socket, "room:lobby", %{user_name
: "User9"})
%PhoenixChannelClient.Channel{
params: %{user_name: "User9"},
socket: %PhoenixChannelClient.Socket{server_name: #PID<0.164.0>},
topic: "room:lobby"
}
iex(4)> PhoenixChannelClient.join(channel)
{:ok, %{}}
iex(5)> pid = spawn(fn ->
...(5)> stream = ExTwitter.stream_filter(track: "ラーメン")
...(5)> for tweet <- stream do
...(5)> IO.puts tweet.text
...(5)> PhoenixChannelClient.push(channel, "new_msg", %{msg: tweet.text})
...(5)> end
...(5)> end)
#PID<0.206.0>
すき焼き
焼肉
ラーメン
寿司
てりたま
フライドポテト
卵かけご飯
焼き鳥丼
唐揚げ
カツ丼
ピザ
食べるのを控えてる食べ物たちであり今食べたいもの?ゐい陲
カラオ皺ヒカオ・邁ハーーォー蟆トーメッルアア族銧サーォーアー・ニーユーサーッ・
サ床ァーューーケマー゙キニーユーサーー・エー゚ー擴擴擴・
きちゃった… (@ ラーメン きずな - @ra_men_kizuna_ in 岡山市, 岡山県) https://t.co/9aPvqSFKPr
RT @purichannel: 初めて煮干しのまぜそば食べてみた!旨すぎ!
https://t.co/NDnFmWldSh
柏のラーメン屋!!
#まぜそば
#ラーメン
#煮干し https://t.co/XOEWRPxUYk
らーめん穀雨@渋谷・ワンタンめん (3個入り) 。
喧騒から離れた、路地裏にひっそり佇むお店。醤油系の清湯スープに、中細ストレート麺。ワンタンは一口サイズで、味は勿論、見た目も品を感じる一杯。\830。
#ラーメン #ramen https://t.co/x2EgAYaK1Z
iex(6)>
以下が、Elixirクライアントの端末のキャプチャーです。 PhoenixChannelClient.push(channel, "new_msg", %{msg: tweet.text}) の書き込みが自動的に流れています。
これはブラウザクライアントが増えても、すべてにTweetsが配信されます。ブラウザ同士でチャットもできます。チャットシステムにElixirクライアントを加えて、Twetterの書き込みを流しているだけですから。これで完成です。
最後に、本プログラムには必要ないものですが、channerの双方向性も押さえておきたいと思います。Elixirクライアントでもチャットメッセージを受け取るためには、iexシェル上で以下のコマンドを打つ必要があります。これを設定すれば、ブラウザクライアントから送られたチャットがiex上に流れるようになります。
receive do
{"new_msg", message } -> IO.puts (message["user_name"] <>": "<> message["msg"])
:close -> IO.puts("closed")
{:error, error} -> IO.inspect error
end
今回の話はこれで終わりです。今回の記事は、半年以上前に書いた記事「Meteor and Reactによるリアクティブシステム「ラーメン野郎を追いかけろ! @ Twitter」を作ってみた - Qiita」がモティーフになっています。このようなリアルタイムシステムを作るのに、Meteorは素晴らしいプラットフォームを提供してくれました。サーバ側にMongoDB、クライアント側にmini-MongoDBを配して、透過的に同期を取るシステムはシンプルでとても魅力的でした。GoogleのFirebaseを除いては、これに代わるものは知りません。今回は、Phoenix channelを使えば同じようなシステムが構築できるのではないかと思い、試してみました。しかもPhoenix channelはMeteorにない魅力もあります。次回はクライアントをReactに変えて、もっと「ラーメン野郎を追いかけろ」と同じようなものを作りたいと思います。地図とTweetを表示したい。