2015-11-26 6 views
7

Ich habe eine Elixir/Phoenix Server App und die Clients verbinden sich über das eingebaute Kanalsystem über Websockets. Jetzt möchte ich erkennen, wenn ein Benutzer einen Kanal verlässt.Wie erkennt man, ob ein Benutzer aufgrund einer Netzwerkunterbrechung einen Phoenix-Kanal verlassen hat?

Hinweis: Ich verwende die Javascript-Client-Bibliothek in einer Google Chrome-Erweiterung. Dazu habe ich den ES6-Code von Phoenix extrahiert, in Javascript umgewandelt und ein wenig optimiert, damit er eigenständig läuft.

Jetzt, wenn ich nur das Popup schließen, löst der Server sofort die terminate/2 Funktion mit reason = {:shutdown, :closed}. Es gibt keine Art von Close-Callback auf der Erweiterungsseite, also ist das großartig!

Aber wenn der Client einfach Netzwerkverbindung verliert (ich habe einen zweiten Computer angeschlossen und nur den Netzwerkstecker gezogen), wird terminate/2 nicht ausgelöst.

Warum und wie behebe ich das?

Ich spielte mit der timeout Möglichkeit transport :websocket, Phoenix.Transports.WebSocket aber heraus funktionierte nicht.

Update: Mit den neuen ehrfürchtigen Phoenix 1.2 Presence Sachen, soll dies nicht mehr benötigt werden.

+0

Ich habe gerade festgestellt, dass der Server nicht immer erkennt, wenn das Popup geschlossen wurde. Ich hoffe also, dass eine Lösung für meine Frage das auch löst. –

Antwort

16

Der richtige Weg, um dies zu tun, ist nicht Exits in Ihrem Kanal zu fangen, und stattdessen haben Sie einen anderen Prozess überwachen. Wenn Sie nach unten gehen, kann es einen Rückruf aufrufen. Unten ist ein Ausschnitt, um loszulegen:

# 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

Ich habe es zur Arbeit und ich habe zwei Follow-up-Fragen: 1. Warum ist das nicht im Kern (es ist einfach zu gut)? 2. Die Zeit zwischen dem Trennen des Netzwerks und dem Auslösen der Verbindung beträgt etwa 90 Sekunden. Ist das in irgendeiner Weise anpassbar? (Ich habe mir überlegt, das Transport-Timeout auf 20 Sekunden zu setzen und alle 10 Sekunden auf den Server zu pingen ... aber natürlich werden zusätzliche Ressourcen gebrannt) –

Verwandte Themen