4

I've been using reflex and reflex-dom to recreate a web version of a boardgame, and I quite like it so far, but I require a websocket to alert a player when the other player has made a move.

Everything works great but if the server goes down, I can't find a way to detect that it happened and reconnect. Additionally, if you send an event to a server while it is down, it is just is eaten up without any error.

I'm using a stripped down version of the websockets example from https://github.com/reflex-frp/reflex-examples/blob/master/websocket-echo/src/Main.hs

{-# LANGUAGE RecursiveDo #-}
module Lib where

import Data.Monoid 
import Reflex.Dom
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8, decodeUtf8)


wsurl = "ws://127.0.0.1:5714"         
-- wsurl = "ws://echo.websocket.org"

someFunc = mainWidget $ do
  rec t <- textInput $ def & setValue .~ fmap (const "") newMessage
      b <- button "Send"
      text $ "Sending to " <> wsurl
      let newMessage = fmap ((:[]) . encodeUtf8 . T.pack) $ tag (current $ value t) $ leftmost [b, textInputGetEnter t]
  ws <- webSocket wsurl $ def & webSocketConfig_send .~ newMessage
  receivedMessages <- foldDyn (\m ms -> ms ++ [m]) [] $ _webSocket_recv ws
  el "p" $ text "Responses from :"
  _ <- el "ul" $ simpleList receivedMessages $ \m -> el "li" $ dynText =<< mapDyn (T.unpack . decodeUtf8) m
  return ()

I feel like there should be a way to do this with tickLossy to send pings with timeout, like some dynamic which returns websockets and then reconnects if a ping goes a certain amount of time without a response? But I'm having trouble envisioning what the code to reconnect would look like.

Edit: It was an issue with reflex-dom sending an event while a websocket was still in the pending state. I made a pull request, although I feel there is a better solution somewhere.

David McHealy
  • 2,471
  • 18
  • 34

2 Answers2

1

It looks like on when websocket is closed, the library tries to reconnect:

  start = do
    ws <- liftIO $ newWebSocket wv url onMessage onOpen $ do
      void $ forkIO $ do --TODO: Is the fork necessary, or do event handlers run in their own threads automatically?
        liftIO $ writeIORef currentSocketRef Nothing
        liftIO $ threadDelay 1000000
        start
    liftIO $ writeIORef currentSocketRef $ Just ws
    return ()

(newWebSocket takes onClose event handler at the last argument)

And all the messages you are sending while reconnecting are ignored:

  performEvent_ $ ffor (_webSocketConfig_send config) $ \payloads -> forM_ payloads $ \payload -> do
    mws <- liftIO $ readIORef currentSocketRef
    case mws of
      Nothing -> return () -- Discard --TODO: should we do something better here? probably buffer it, since we handle reconnection logic; how do we verify that the server has received things?
      Just ws -> do
        liftIO $ webSocketSend ws payload

You probably should open an issue on their issue tracker. Or just find better library.

Yuras
  • 13,856
  • 1
  • 45
  • 58
  • I saw this in the code but it did not reconnect for me. It turns out if you send a message while the socket server is down, it causes an exception to be thrown client side, and then even if the server comes back, it will not continue executing. So it is definitely an issue with the library. – David McHealy Mar 04 '16 at 18:27
1

Edit: It was an issue with reflex-dom sending an event while a websocket was still in the pending state. I made a pull request, although I feel there is a better solution somewhere.

Just FYI, since the question was posted there have been some quite relevant extensions to the WebSocket API merged into reflex-dom:

I believe the close event is exactly what you were looking for. It was just not available at the time.

user2847643
  • 2,893
  • 14
  • 22