PhoenixのChannelを使う
Phoenixのガイドを眺めていて一番気になったのがChannelだったので、上記の公式ガイドに沿ってChannelを使ってみました。結構端折っています。
- 作者: Dave Thomas,笹田耕一,鳥井雪
- 出版社/メーカー: オーム社
- 発売日: 2016/08/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
概要をつかむために公式ガイドの一番上のところだけ訳してみます。
Channels are a really exciting and powerful part of Phoenix that allow us to easily add soft-realtime features to our applications. Channels are based on a simple idea - sending and receiving messages. Senders broadcast messages about topics. Receivers subscribe to topics so that they can get those messages. Senders and receivers can switch roles on the same topic at any time.
Since Elixir is based on message passing, you may wonder why we need this extra mechanism to send and receive messages. With Channels, neither senders nor receivers have to be Elixir processes. They can be anything that we can teach to communicate over a Channel - a JavaScript client, an iOS app, another Phoenix application, our watch. Also, messages broadcast over a Channel may have many receivers. Elixir processes communicate one to one.
The word "Channel" is really shorthand for a layered system with a number of components. Let's take a quick look at them now so we can see the big picture a little better.
適当訳:
チャネルはPhoenixの中でも本当に面白くて強力なところであり、簡単にソフトリアルタイム性をアプリケーションに持たせることができます。チャネルはメッセージの送受信という、単純なアイデアに基いています。senderはtopicについてブロードキャストし、receiverはtopicを購読することによってメッセージを受け取ることができます。sender, receiverはいつでも役割を交代することができます
ElixirはMessage Passingに基いているため、どうしてメッセージの送受信に他の仕組みを利用するのか疑問に思うことでしょう。チャネルでは、senderもreceiverもElixirのプロセスである必要はありません。チャネルと通信するものはJavaScriptやiOSでも何だってよいのです。また、Elixirのプロセスが1対1で通信するのに対し、チャネルにおけるメッセージブロードキャスティングはreceiverが複数になり得ます。
"Channel"という言葉は多くのコンポーネットをもった多層システムを簡略に表したにすぎません。 全体像をもっとよくつかめるようになるために、もう少し覗いてみましょう。
以下、各partsの説明(省略)
- Socket Handlers
- Channel Routes
- Channels
- PubSub
- Messages
- Topics
- Transports
- Transport Adapters
- Client Libraries
手を動かすのが一番早い理解につながるので、 例の如く適当にプロジェクトとDBを作成します
プロジェクト作成
$ mix phoenix.new channel_sample
$ cd channel_sample
$ mix ecto.create
SocketとChannelの設定
lib/hello_phoenix/endpoint.ex
の4行目付近ですでにsocketが定義されています。
defmodule ChannelSample.Endpoint do use Phoenix.Endpoint, otp_app: :channel_sample socket "/socket", ChannelSample.UserSocket ...
ChannelSample.UserSocket
自体は web/channels/user_socket.ex
で定義されています。5行目のコメントアウトを外して、channelの設定をします。
defmodule ChannelSample.UserSocket do use Phoenix.Socket ## Channels channel "room:*", ChannelSample.RoomChannel
Channelモジュールの実装
HelloPhoenix.RoomChannel
モジュールはまだ存在しないので、web/channels/room_channel.ex
ファイルを作成し、以下の内容でモジュールを定義します。
defmodule ChannelSample.RoomChannel do use Phoenix.Channel def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end end
( <>
は文字列結合をしています)
認可のために、join/3
関数を定義する必要があります。
今回は"room:lobby" topicだけは誰でも入れるようにし、private_roomのことは考えないこととします。
クライアント側の設定
web/static/js/socket.js
に最低限の実装が最初からあるので、中身を確認します。
// web/static/js/socket.js ... socket.connect() // Now that you are connected, you can join channels with a topic: let channel = socket.channel("room:lobby", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket
上記のように57行目付近を
let channel = socket.channel("room:lobby", {})
に変更します。
また、web/static/js/app.js
末尾行のコメントアウトを外してsocket.jsを有効にします。
import socket from "./socket"
Phoenixがライブリロードされ、ブラウザコンソールに以下のように出力され、
socket通信が確立されていることがわかります。
(Phoenixを起動していない場合は $ mix phoenix.server
します)
web/templates/page/index.html.eex
を修正して、入力フォームとメッセージ表示用のコンテナを作成します。
<div id="messages"></div> <input id="chat-input" type="text"></input>
socket.js
ファイルを修正して、socket通信でメッセージの受送信ができるようにします。
... let channel = socket.channel("room:lobby", {}) let chatInput = document.querySelector("#chat-input") let messageContainer = document.querySelector("messages") chatInput.addEventListener("keypress", event => { if(event.keyCode === 13){ channel.push("new_msg", {body: chatInput.value}) chatInput.value = "" } }) channel.on("new_msg", payload => { let messageItem = document.createElement("li"); messageItem.innerText = `[${Date()}] ${payload.body}` messagesContainer.appendChild(messageItem) }) // let channel = socket.channel("topic:subtopic", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket
RoomChannelモジュールを修正します。 socketでメッセージが飛んできた際のフック関数として handle_in
を実装すれば大丈夫です。
broadcast!
で接続中のクライアント全員にメッセージを送ります。
handle_out
ではクライアントごとにフィルタリング処理を行ったり、Interceptor的な役割をもっています。今回は何もしていません。
defmodule ChannelSample.RoomChannel do use Phoenix.Channel def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end def handle_in("new_msg", %{"body" => body}, socket) do broadcast! socket, "new_msg", %{body: body} {:noreply, socket} end def handle_out("new_msg", payload, socket) do push socket, "new_msg", payload {:noreply, socket} end end
これでChatができるようになりました。
Sample
こちらにChat Appのサンプルがあるようです。
http://phoenixchat.herokuapp.com/
Heroku上で動いています。逆に対応してるPaaSはHerokuだけかもしれないです。
Programming Phoenix: Productive, Reliable, Fast
- 作者: Chris Mccord,Bruce Tate,Jose Valim
- 出版社/メーカー: Pragmatic Bookshelf
- 発売日: 2016/04/30
- メディア: ペーパーバック
- この商品を含むブログを見る
まとめ
Channelが手軽につかえていい感じです。 当然普通にRails的な使い方もできるのでなかなか汎用性高いのでは?と思いました(小並感)