{"version":{"__v":1,"_id":"549a1afd4c523e0b006d7928","forked_from":"5484f8220d7c73140052fdd7","project":"54348ec95b10711400c6c445","createdAt":"2014-12-24T01:46:37.298Z","releaseDate":"2014-12-24T01:46:37.298Z","categories":["549a1afd4c523e0b006d7929","549a1afd4c523e0b006d792a","549a1afd4c523e0b006d792b"],"is_deprecated":false,"is_hidden":false,"is_beta":true,"is_stable":false,"codename":"","version_clean":"0.7.2","version":"0.7.2"},"category":{"__v":1,"_id":"549a1afd4c523e0b006d792a","pages":["549a1afe4c523e0b006d7937","549a1afe4c523e0b006d7938","549a1afe4c523e0b006d7939","549a1afe4c523e0b006d793a","549a1afe4c523e0b006d793b","549a1afe4c523e0b006d793c","549a1afe4c523e0b006d793d","549a1afe4c523e0b006d793e","549a1afe4c523e0b006d793f","549a1afe4c523e0b006d7940"],"project":"54348ec95b10711400c6c445","version":"549a1afd4c523e0b006d7928","sync":{"url":"","isSync":false},"reference":false,"createdAt":"2014-10-09T04:18:04.869Z","from_sync":false,"order":1,"slug":"guides","title":"Guides"},"__v":0,"project":"54348ec95b10711400c6c445","user":"5435e00ad7d8700800bbec51","_id":"549a1afe4c523e0b006d793e","updates":[],"next":{"pages":[],"description":""},"createdAt":"2014-12-03T22:08:06.734Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"auth":"never","params":[],"url":""},"isReference":false,"order":7,"body":"[block:callout]\n{\n  \"type\": \"danger\",\n  \"title\": \"This guide is under construction\",\n  \"body\": \"Here is something copied from the Phoenix README\"\n}\n[/block]\n### PubSub\n\nThe PubSub module provides a simple publish/subscribe mechanism that can be used to facilitate messaging between components in an application. To subscribe a process to a given topic, call `subscribe/2` passing in the PID and a string to identify the topic:\n\n```elixir\nPhoenix.PubSub.subscribe self, \"foo\"\n```\n\nThen, to broadcast messages to all subscribers to that topic:\n\n```elixir\nPhoenix.PubSub.broadcast \"foo\", { :message_type, some: 1, data: 2 }\n```\n\nFor example, let's look at a rudimentary logger that prints messages when a controller action is invoked:\n\n```elixir\ndefmodule Logger do\n  def start_link do\n    sub = spawn_link &(log/0)\n    Phoenix.PubSub.subscribe(sub, \"logging\")\n    {:ok, sub}\n  end\n\n  def log do\n    receive do\n      { :action, params } ->\n        IO.puts \"Called action #{params[:action]} in controller #{params[:controller]}\"\n      _ ->\n    end\n    log\n  end\nend\n```\n\nWith this module added as a worker to the app's supervision tree, we can broadcast messages to the `\"logging\"` topic, and they will be handled by the logger:\n\n```elixir\ndef index(conn, _params) do\n  Phoenix.PubSub.broadcast \"logging\", { :action, controller: \"pages\", action: \"index\" }\n  render conn, \"index\"\nend\n```\n\n### Channels\n\nChannels broker websocket connections and integrate with the PubSub layer for message broadcasting. You can think of channels as controllers, with two differences: they are bidirectional and the connection stays alive after a reply.\n\nWe can implement a channel by creating a module in the _channels_ directory and by using `Phoenix.Channel`:\n\n```elixir\ndefmodule App.MyChannel do\n  use Phoenix.Channel\nend\n```\n\nThe first thing to do is to implement the join function to authorize sockets on this Channel's topic:\n\n\n```elixir\ndefmodule App.MyChannel do\n  use Phoenix.Channel\n\n  def join(socket, \"topic\", message) do\n    {:ok, socket}\n  end\n\n  def join(socket, _no, _message) do\n    {:error, socket, :unauthorized}\n  end\n\nend\n```\n\n`join` events are specially treated. When `{:ok, socket}` is returned from the Channel, the socket is subscribed to the channel and authorized to pubsub on the channel/topic pair. When `{:error, socket, reason}` is returned, the socket is denied pubsub access.\n\nNote that we must join a topic before you can send and receive events on a channel. This will become clearer when we look at the JavaScript code, hang tight!\n\nA channel will use a socket underneath to send responses and receive events. As said, sockets are bidirectional, which mean you can receive events (similar to requests in your controller). You handle events with pattern matching directly on the event name and message map, for example:\n\n\n```elixir\ndefmodule App.MyChannel do\n  use Phoenix.Channel\n\n  def event(socket, \"user:active\", %{user_id: user_id}) do\n    socket\n  end\n\n  def event(socket, \"user:idle\", %{user_id: user_id}) do\n    socket\n  end\n\nend\n```\n\nWe can send replies directly to a single authorized socket with `reply/3`\n\n```elixir\ndefmodule App.MyChannel do\n  use Phoenix.Channel\n\n  def event(socket, \"incoming:event\", message) do\n    reply socket, \"response:event\", %{message: \"Echo: \" <> message.content}\n    socket\n  end\n\nend\n```\n\nNote that, for added clarity, events should be prefixed with their subject and a colon (i.e. \"subject:event\"). Instead of `reply/3`, you may also use `broadcast/3`. In the previous case, this would publish a message to all clients who previously joined the current socket's topic.\n\nWhen sending process messages directly to a socket like `send socket.pid \"pong\"`, the\n`\"pong\"` message triggers the `\"info\"` event for _all the authorized channels_ for that socket. Instead of receiving a map like normal socket events, the `info` event receives the literal message sent to the process. Below is an example:\n\n```elixir\ndef event(socket, \"ping\", message) do\n  IO.puts \"sending myself pong\"\n  send socket.pid, \"pong\"\n  socket\nend\n\ndef event(socket, \"info\", \"pong\") do\n  IO.puts \"Got pong from my own ping\"\n  socket\nend\n```\n\nRemember that a client first has to join a topic before it can send events. On the JavaScript side, this is how it would be done (don't forget to include _/js/phoenix.js_) :\n\n```js\nvar socket = new Phoenix.Socket(\"/ws\");\n\nsocket.join(\"channel\", \"topic\", {some_auth_token: \"secret\"}, callback);\n```\n\nFirst you should create a socket, which uses `/ws` route name. This route's name is for you to decide in your router :\n\n```elixir\ndefmodule App.Router do\n  use Phoenix.Router\n  use Phoenix.Router.Socket, mount: \"/ws\"\n\n  channel \"channel\", App.MyChannel\nend\n```\n\nThis mounts the socket router at `/ws` and also registers the above channel as `channel`. Let's recap:\n\n * The mountpoint for the socket in the router (/ws) has to match the route used on the JavaScript side when creating the new socket.\n * The channel name in the router has to match the first parameter on the JavaScript call to `socket.join`\n * The name of the topic used in `def join(socket, \"topic\", message)` function has to match the second parameter on the JavaScript call to `socket.join`\n\nNow that a channel exists and we have reached it, it's time to do something fun with it! The callback from the previous JavaScript example receives the channel as a parameter and uses that to either subscribe to topics or send events to the server. Here is a quick example of both :\n\n```js\nvar socket = new Phoenix.Socket(\"/ws\");\n\nsocket.join(\"channel\", \"topic\", {}, function(channel) {\n\n  channel.on(\"pong\", function(message) {\n    console.log(\"Got \" + message + \" while listening for event pong\");\n  });\n\n  onSomeEvent(function() {\n    channel.send(\"ping\", {data: \"json stuff\"});\n  });\n\n});\n```\nIf you wish, you can send a \"join\" event back to the client\n```elixir\ndef join(socket, topic, message) do\n  reply socket, \"join\", %{content: \"joined #{topic} successfully\"}\n  {:ok, socket}\nend\n```\nWhich you can handle after you get the channel object.\n``` javascript\nchannel.on(\"join\", function(message) {\n  console.log(\"Got \" + message.content);\n});\n```\nSimilarly, you can send an explicit message when denying conection.\n```elixir\ndef join(socket, topic, message) do\n  reply socket, \"error\", %{reason: \"failed to join #{topic}\"}\n  {:error, socket, :reason}\nend\n```\nand handle that like any other event\n``` javascript\nchannel.on(\"error\", function(error) {\n  console.log(\"Failed to join topic. Reason: \" + error.reason);\n});\n```\n\nIt should be noted that join and error messages are not returned by default, as the client implicitly knows whether it has successfuly subscribed to a channel: the socket will simply not receive any messages should the connection be denied.\n\n\n#### Holding state in socket connections\n\nEphemeral state can be stored on the socket and is available for the lifetime of the socket connection using the `assign/3` and `get_assign/2` imported functions. This is useful for fetching channel/topic related information a single time in `join/3` and having it available within each socket `event/3` function. Here's a basic example:\n\n```elixir\ndef join(socket, topic, %{\"token\" => token, \"user_id\" => user_id) do\n  if user = MyAuth.find_authorized_user(user_id, token) do\n    socket = assign(socket, :user, user)\n    {:ok, socket}\n  else\n    {:error, socket, :unauthorized}\n  end\nend\n\ndef event(socket, \"new:msg\", %{msg: msg}) do\n  user = get_assign(socket, :user)\n  broadcoast socket, \"new:msg\", %{user_id: user.id, name: user.name, msg: msg}\n  socket\nend\n```\n\n\nThere are a few other things not covered in this readme that might be worth exploring :\n\n * By default a socket uses the ws:// protocol and the host from the current location. If you mean to use a separate router on a host other than `location.host`, be sure to specify the full path when initializing the socket, i.e. `var socket = new Phoenix.Socket(\"//example.com/ws\")` or `var socket = new Phoenix.Socket(\"ws://example.com/ws\")`\n * Both the client and server side allow for leave events (as opposed to join)\n * In JavaScript, you may manually `.trigger()` events which can be useful for testing","excerpt":"","slug":"channels","type":"basic","title":"Channels"}
[block:callout] { "type": "danger", "title": "This guide is under construction", "body": "Here is something copied from the Phoenix README" } [/block] ### PubSub The PubSub module provides a simple publish/subscribe mechanism that can be used to facilitate messaging between components in an application. To subscribe a process to a given topic, call `subscribe/2` passing in the PID and a string to identify the topic: ```elixir Phoenix.PubSub.subscribe self, "foo" ``` Then, to broadcast messages to all subscribers to that topic: ```elixir Phoenix.PubSub.broadcast "foo", { :message_type, some: 1, data: 2 } ``` For example, let's look at a rudimentary logger that prints messages when a controller action is invoked: ```elixir defmodule Logger do def start_link do sub = spawn_link &(log/0) Phoenix.PubSub.subscribe(sub, "logging") {:ok, sub} end def log do receive do { :action, params } -> IO.puts "Called action #{params[:action]} in controller #{params[:controller]}" _ -> end log end end ``` With this module added as a worker to the app's supervision tree, we can broadcast messages to the `"logging"` topic, and they will be handled by the logger: ```elixir def index(conn, _params) do Phoenix.PubSub.broadcast "logging", { :action, controller: "pages", action: "index" } render conn, "index" end ``` ### Channels Channels broker websocket connections and integrate with the PubSub layer for message broadcasting. You can think of channels as controllers, with two differences: they are bidirectional and the connection stays alive after a reply. We can implement a channel by creating a module in the _channels_ directory and by using `Phoenix.Channel`: ```elixir defmodule App.MyChannel do use Phoenix.Channel end ``` The first thing to do is to implement the join function to authorize sockets on this Channel's topic: ```elixir defmodule App.MyChannel do use Phoenix.Channel def join(socket, "topic", message) do {:ok, socket} end def join(socket, _no, _message) do {:error, socket, :unauthorized} end end ``` `join` events are specially treated. When `{:ok, socket}` is returned from the Channel, the socket is subscribed to the channel and authorized to pubsub on the channel/topic pair. When `{:error, socket, reason}` is returned, the socket is denied pubsub access. Note that we must join a topic before you can send and receive events on a channel. This will become clearer when we look at the JavaScript code, hang tight! A channel will use a socket underneath to send responses and receive events. As said, sockets are bidirectional, which mean you can receive events (similar to requests in your controller). You handle events with pattern matching directly on the event name and message map, for example: ```elixir defmodule App.MyChannel do use Phoenix.Channel def event(socket, "user:active", %{user_id: user_id}) do socket end def event(socket, "user:idle", %{user_id: user_id}) do socket end end ``` We can send replies directly to a single authorized socket with `reply/3` ```elixir defmodule App.MyChannel do use Phoenix.Channel def event(socket, "incoming:event", message) do reply socket, "response:event", %{message: "Echo: " <> message.content} socket end end ``` Note that, for added clarity, events should be prefixed with their subject and a colon (i.e. "subject:event"). Instead of `reply/3`, you may also use `broadcast/3`. In the previous case, this would publish a message to all clients who previously joined the current socket's topic. When sending process messages directly to a socket like `send socket.pid "pong"`, the `"pong"` message triggers the `"info"` event for _all the authorized channels_ for that socket. Instead of receiving a map like normal socket events, the `info` event receives the literal message sent to the process. Below is an example: ```elixir def event(socket, "ping", message) do IO.puts "sending myself pong" send socket.pid, "pong" socket end def event(socket, "info", "pong") do IO.puts "Got pong from my own ping" socket end ``` Remember that a client first has to join a topic before it can send events. On the JavaScript side, this is how it would be done (don't forget to include _/js/phoenix.js_) : ```js var socket = new Phoenix.Socket("/ws"); socket.join("channel", "topic", {some_auth_token: "secret"}, callback); ``` First you should create a socket, which uses `/ws` route name. This route's name is for you to decide in your router : ```elixir defmodule App.Router do use Phoenix.Router use Phoenix.Router.Socket, mount: "/ws" channel "channel", App.MyChannel end ``` This mounts the socket router at `/ws` and also registers the above channel as `channel`. Let's recap: * The mountpoint for the socket in the router (/ws) has to match the route used on the JavaScript side when creating the new socket. * The channel name in the router has to match the first parameter on the JavaScript call to `socket.join` * The name of the topic used in `def join(socket, "topic", message)` function has to match the second parameter on the JavaScript call to `socket.join` Now that a channel exists and we have reached it, it's time to do something fun with it! The callback from the previous JavaScript example receives the channel as a parameter and uses that to either subscribe to topics or send events to the server. Here is a quick example of both : ```js var socket = new Phoenix.Socket("/ws"); socket.join("channel", "topic", {}, function(channel) { channel.on("pong", function(message) { console.log("Got " + message + " while listening for event pong"); }); onSomeEvent(function() { channel.send("ping", {data: "json stuff"}); }); }); ``` If you wish, you can send a "join" event back to the client ```elixir def join(socket, topic, message) do reply socket, "join", %{content: "joined #{topic} successfully"} {:ok, socket} end ``` Which you can handle after you get the channel object. ``` javascript channel.on("join", function(message) { console.log("Got " + message.content); }); ``` Similarly, you can send an explicit message when denying conection. ```elixir def join(socket, topic, message) do reply socket, "error", %{reason: "failed to join #{topic}"} {:error, socket, :reason} end ``` and handle that like any other event ``` javascript channel.on("error", function(error) { console.log("Failed to join topic. Reason: " + error.reason); }); ``` It should be noted that join and error messages are not returned by default, as the client implicitly knows whether it has successfuly subscribed to a channel: the socket will simply not receive any messages should the connection be denied. #### Holding state in socket connections Ephemeral state can be stored on the socket and is available for the lifetime of the socket connection using the `assign/3` and `get_assign/2` imported functions. This is useful for fetching channel/topic related information a single time in `join/3` and having it available within each socket `event/3` function. Here's a basic example: ```elixir def join(socket, topic, %{"token" => token, "user_id" => user_id) do if user = MyAuth.find_authorized_user(user_id, token) do socket = assign(socket, :user, user) {:ok, socket} else {:error, socket, :unauthorized} end end def event(socket, "new:msg", %{msg: msg}) do user = get_assign(socket, :user) broadcoast socket, "new:msg", %{user_id: user.id, name: user.name, msg: msg} socket end ``` There are a few other things not covered in this readme that might be worth exploring : * By default a socket uses the ws:// protocol and the host from the current location. If you mean to use a separate router on a host other than `location.host`, be sure to specify the full path when initializing the socket, i.e. `var socket = new Phoenix.Socket("//example.com/ws")` or `var socket = new Phoenix.Socket("ws://example.com/ws")` * Both the client and server side allow for leave events (as opposed to join) * In JavaScript, you may manually `.trigger()` events which can be useful for testing