2015-11-26 3 views
7

У меня есть приложение сервера Elixir/Phoenix, и клиенты подключаются через систему сборки в каналах через websockets. Теперь я хочу определить, когда пользователь покидает канал.Как определить, покинул ли пользователь канал Phoenix из-за отключения сети?

Sidenote: Я использую клиентскую библиотеку javascript внутри расширения Google Chrome. Для этого я извлек код ES6 из Phoenix, перевел его на javascript и немного изменил его, чтобы он работал автономно.

Теперь, когда я просто закрываю всплывающее окно, сервер немедленно запускает функцию terminate/2 с помощью reason = {:shutdown, :closed}. На стороне расширения нет какого-либо близкого обратного вызова, так что это здорово!

Но когда клиент просто теряет сетевое соединение (я подключил второй компьютер и вытащил сетевой штепсель), то terminate/2 не будет запускаться.

Почему и как это исправить?

Я играл с опцией timeouttransport :websocket, Phoenix.Transports.WebSocket, но это не сработало.

Update: С новым удивительным Phoenix 1,2 Presence вещи, это не должно быть нужны.

+0

Я просто заметил, что сервер не всегда распознает, когда всплывающее окно было закрыто. Поэтому я надеюсь, что решение для моего вопроса также решит это. –

ответ

16

Правильный способ сделать это - не задерживать выходы в вашем канале, а вместо этого следить за другим процессом. Когда вы спускаетесь, он может вызывать обратный вызов. Ниже приведен фрагмент, который поможет вам начать работу:

# lib/my_app.ex 

children = [ 
    ... 
    worker(ChannelWatcher, [:rooms]) 
] 

# web/channels/room_channel.ex 

def join("rooms:", <> id, params, socket) do 
    uid = socket.assigns.user_id] 
    :ok = ChannelWatcher.monitor(:rooms, self(), {__MODULE__, :leave, [id, uid]}) 

    {:ok, socket} 
end 

def leave(room_id, user_id) do 
    # handle user leaving 
end 

# lib/my_app/channel_watcher.ex 

defmodule ChannelWatcher do 
    use GenServer 

    ## Client API 

    def monitor(server_name, pid, mfa) do 
    GenServer.call(server_name, {:monitor, pid, mfa}) 
    end 

    def demonitor(server_name, pid) do 
    GenServer.call(server_name, {:demonitor, pid}) 
    end 

    ## Server API 

    def start_link(name) do 
    GenServer.start_link(__MODULE__, [], name: name) 
    end 

    def init(_) do 
    Process.flag(:trap_exit, true) 
    {:ok, %{channels: HashDict.new()}} 
    end 

    def handle_call({:monitor, pid, mfa}, _from, state) do 
    Process.link(pid) 
    {:reply, :ok, put_channel(state, pid, mfa) 
    end 

    def handle_call({:demonitor, pid}, _from, state) do 
    case HashDict.fetch(state.channels, pid) do 
     :error  -> {:reply, :ok, state} 
     {:ok, _mfa} -> 
     Process.unlink(pid) 
     {:reply, :ok, drop_channel(state, pid)} 
    end 
    end 

    def handle_info({:EXIT, pid, _reason}, state) do 
    case HashDict.fetch(state.channels, pid) do 
     :error -> {:noreply, state} 
     {:ok, {mod, func, args}} -> 
     Task.start_link(fn -> apply(mod, func, args) end) 
     {:noreply, drop_channel(state, pid)} 
    end 
    end 

    defp drop_channel(state, pid) do 
    %{state | channels: HashDict.delete(state.channels, pid)}} 
    end 

    defp put_channel(state, pid, mfa) do 
    %{state | channels: HashDict.put(channels, pid, mfa)}} 
    end 
end 
+1

Я получил его на работу, и у меня есть два вопроса о последующих действиях: 1. Почему это не в основном (это слишком хорошо)? 2. Время между отключением сети и прекращением запуска составляет около 90 секунд. Является ли это каким-либо образом настраиваемым? (Я думал о том, чтобы установить тайм-аут транспорта, скажем, 20 секунд и пинговать сервер каждые 10 секунд ... но, конечно, дополнительные ресурсы, которые сжигаются) –