Phoenix Channel
Channel 은 Phoenix 에서 지원하는 실시간 양방향 통신을 위한 기술이다.
Chat Channel
채팅을 할 수있는 아주 간단한 channel 을 만들어보자.
mix phx.new ex_channel
terminal 에 나오는 대로 프로젝트 설정 후 아래 명령어를 실행하자.
mix phx.gen.channel Chat
아래 chat_channel.ex 파일이 생성된다.
defmodule ExChannelWeb.ChatChannel do use ExChannelWeb, :channel @impl true def join("chat:lobby", payload, socket) do if authorized?(payload) do {:ok, socket} else {:error, %{reason: "unauthorized"}} end end # Channels can be used in a request/response fashion # by sending replies to requests from the client @impl true def handle_in("ping", payload, socket) do {:reply, {:ok, payload}, socket} end # It is also common to receive messages from the client and # broadcast to everyone in the current topic (chat:lobby). @impl true def handle_in("shout", payload, socket) do broadcast(socket, "shout", payload) {:noreply, socket} end # Add authorization logic here as required. defp authorized?(_payload) do true end end
join / ping / shout 메세지를 처리하는 로직이 자동으로 생성되었다.
이 메세지를 정상적으로 처리하는지 확인해보자.
먼저 js 를 통해서 확인해보자.
아래 chat_socket.ex 를 생성하자. 위치는 assets/js 디렉토리 하위에 둔다.
import { Socket } from "phoenix" let socket = new Socket("/socket", { params: { token: window.userToken } }) socket.connect() let channel = socket.channel("chat:lobby", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket
같은 디렉토리의 app.js 최상단에 아래 import 를 추가하자.
import "./chat_socket.js" // 나머지 내용
마지막으로 lib/ex_channel_web 하위의 endpoint.ex 파일에 아래 코드를 추가한다.
socket "/socket", ExChannelWeb.UserSocket, websocket: true, longpoll: false
mix phx.server 로 서버 실행 후 index 페이지로 간 뒤 console 을 실행하면 정상작동 하는것을 확인할 수 있다.
그럼 메세지를 전송하는건 어떻게 확인할까?
js 로 input 을 만들고 channel 이벤트를 발송해서 확인해야 하는가?
쉽지않을 것 같다. 특히 Backend 개발만 한다면 더 어렵다.
Channel Case
이전에 ChatChannel 을 생성할 때 생성된 channel_case.ex 파일이 있다.
test 디렉토리 하위에 생겼는데, 어떤 역할을 하는지 알아보자.
아래는 ChatChannel 생성 시 자동으로 생성된 또 다른 파일인 chat_channel_test.ex 파일이다.
defmodule ExChannelWeb.ChatChannelTest do use ExChannelWeb.ChannelCase setup do {:ok, _, socket} = ExChannelWeb.UserSocket |> socket("user_id", %{some: :assign}) |> subscribe_and_join(ExChannelWeb.ChatChannel, "chat:lobby", %{"secret" => "Merong"}) %{socket: socket} end test "ping replies with status ok", %{socket: socket} do ref = push(socket, "ping", %{"hello" => "there"}) assert_reply ref, :ok, %{"hello" => "there"} end test "shout broadcasts to chat:lobby", %{socket: socket} do push(socket, "shout", %{"hello" => "all"}) assert_broadcast "shout", %{"hello" => "all"} end test "broadcasts are pushed to the client", %{socket: socket} do broadcast_from!(socket, "broadcast", %{"some" => "data"}) assert_push "broadcast", %{"some" => "data"} end end
기본으로 구현되어있는 메세지 핸들링 함수에 대한 테스트 코드가 작성되어있다.
setup 함수가 있다. subscribe_and_join 이라는 함수를 내부에서 호출한다.
이는 Phoenix channel 에 join 하는 동작을 미리 구현한 함수이다.
갑자기 이 함수는 어디서 튀어나왔는지 확인하려면 ChannelCase 를 보면된다.
defmodule ExChannelWeb.ChannelCase do @moduledoc """ This module defines the test case to be used by channel tests. Such tests rely on `Phoenix.ChannelTest` and also import other functionality to make it easier to build common data structures and query the data layer. Finally, if the test case interacts with the database, we enable the SQL sandbox, so changes done to the database are reverted at the end of every test. If you are using PostgreSQL, you can even run database tests asynchronously by setting `use ExChannelWeb.ChannelCase, async: true`, although this option is not recommended for other databases. """ use ExUnit.CaseTemplate using do quote do # Import conveniences for testing with channels import Phoenix.ChannelTest import ExChannelWeb.ChannelCase # The default endpoint for testing @endpoint ExChannelWeb.Endpoint end end setup tags do ExChannel.DataCase.setup_sandbox(tags) :ok end end
중간에 using do 부분을 보면 Phoenix.ChannelTest 가 있다.
이는 Phoenix 진영에서 미리 개발자들을 위해 구현해 둔 Channel 을 테스트하기 위한 코드들이 작성되어있다.
ChannelCase 는 Phoenix.ChannelTest 의 모든 함수를 사용할 수 있게 해준다.
그리고 해당 모듈 하위의 모든 함수도 사용할 수 있다.
현재는 구현된게 없지만 추가하면 use ExChannelWeb.ChannelCase 를 명시한 어떤 파일에서도 사용가능하다.