packages feed

websockets 0.12.7.3 → 0.13.0.0

raw patch · 13 files changed

+247/−146 lines, 13 filesdep +mtldep −bytestring-builderdep −clockdep ~QuickCheckdep ~basedep ~bytestringnew-component:exe:websockets-client-examplenew-component:exe:websockets-server-example

Dependencies added: mtl

Dependencies removed: bytestring-builder, clock

Dependency ranges changed: QuickCheck, base, bytestring, containers, network, random, text

Files

CHANGELOG view
@@ -1,5 +1,20 @@ # CHANGELOG +- 0.13.0.0 (2023-12-30)+    * **BREAKING**: Remove `serverRequirePong` option in favor of the new+      implementation.+    * **BREAKING**: Client: Rejecting request raises+      `RequestRejected RequestHead ResponseHead`+    * Timeout initial socket connection after 30s.+    * If the socket is closed unexpectedly, raise `ConnectionClosed`.+    * Added a way to manually send a Pong message.+    * `runServer` now cleans up threads correctly.+    * Remove redundant bytestring-builder dependency.+    * Introduce `Network.WebSockets.Connection.PingPong` to+      handle ping pong for any Connection, be it Client or Server.+    * Bump `text `dependency upper bound to 2.2+    * Bump `random `dependency lower bound to 1.0.1+ - 0.12.7.3 (2021-10-26)     * Bump `attoparsec` dependency upper bound to 0.15 
+ example/client.hs view
@@ -0,0 +1,40 @@+--------------------------------------------------------------------------------+{-# LANGUAGE OverloadedStrings #-}+module Main+    ( main+    ) where+++--------------------------------------------------------------------------------+import           Control.Concurrent  (forkIO)+import           Control.Monad       (forever, unless)+import           Control.Monad.Trans (liftIO)+import           Network.Socket      (withSocketsDo)+import           Data.Text           (Text)+import qualified Data.Text           as T+import qualified Data.Text.IO        as T+import qualified Network.WebSockets  as WS+++--------------------------------------------------------------------------------+app :: WS.ClientApp ()+app conn = do+    putStrLn "Connected!"++    -- Fork a thread that writes WS data to stdout+    _ <- forkIO $ forever $ do+        msg <- WS.receiveData conn+        liftIO $ T.putStrLn msg++    -- Read from stdin and write to WS+    let loop = do+            line <- T.getLine+            unless (T.null line) $ WS.sendTextData conn line >> loop++    loop+    WS.sendClose conn ("Bye!" :: Text)+++--------------------------------------------------------------------------------+main :: IO ()+main = withSocketsDo $ WS.runClient "echo.websocket.org" 80 "/" app
src/Network/WebSockets.hs view
@@ -83,6 +83,9 @@     , newClientConnection        -- * Utilities+    , PingPongOptions(..)+    , defaultPingPongOptions+    , withPingPong     , withPingThread     , forkPingThread     ) where@@ -91,6 +94,7 @@ -------------------------------------------------------------------------------- import           Network.WebSockets.Client import           Network.WebSockets.Connection+import           Network.WebSockets.Connection.PingPong import           Network.WebSockets.Http import           Network.WebSockets.Server import           Network.WebSockets.Types
src/Network/WebSockets/Client.hs view
@@ -20,11 +20,13 @@ -------------------------------------------------------------------------------- import qualified Data.ByteString.Builder       as Builder import           Control.Exception             (bracket, finally, throwIO)+import           Control.Concurrent.MVar       (newEmptyMVar) import           Control.Monad                 (void) import           Data.IORef                    (newIORef) import qualified Data.Text                     as T import qualified Data.Text.Encoding            as T import qualified Network.Socket                as S+import           System.Timeout                (timeout)   --------------------------------------------------------------------------------@@ -74,11 +76,13 @@     S.setSocketOption sock S.NoDelay 1      -- Connect WebSocket and run client-    res <- finally-        (S.connect sock (S.addrAddress addr) >>-         runClientWithSocket sock fullHost path opts customHeaders app)-        (S.close sock)+    res <- bracket+        (timeout (connectionTimeout opts * 1000 * 1000) $ S.connect sock (S.addrAddress addr))+        (const $ S.close sock) $ \maybeConnected -> case maybeConnected of+            Nothing -> throwIO $ ConnectionTimeout+            Just () -> runClientWithSocket sock fullHost path opts customHeaders app +     -- Clean up     return res @@ -157,12 +161,14 @@                 (connectionMessageDataSizeLimit opts) stream     write   <- encodeMessages protocol ClientConnection stream     sentRef <- newIORef False+    heartbeat <- newEmptyMVar     return $ Connection         { connectionOptions   = opts         , connectionType      = ClientConnection         , connectionProtocol  = protocol         , connectionParse     = parse         , connectionWrite     = write+        , connectionHeartbeat = heartbeat         , connectionSentClose = sentRef         }   where
src/Network/WebSockets/Connection.hs view
@@ -1,6 +1,5 @@ ----------------------------------------------------------------------------------- | This module exposes connection internals and should only be used if you--- really know what you are doing.+-- | This module exposes connection internals {-# LANGUAGE OverloadedStrings #-} module Network.WebSockets.Connection     ( PendingConnection (..)@@ -31,6 +30,7 @@     , sendClose     , sendCloseCode     , sendPing+    , sendPong      , withPingThread     , forkPingThread@@ -49,6 +49,7 @@ import           Control.Concurrent                              (forkIO,                                                                   threadDelay) import qualified Control.Concurrent.Async                        as Async+import           Control.Concurrent.MVar                         (MVar, newEmptyMVar, tryPutMVar) import           Control.Exception                               (AsyncException,                                                                   fromException,                                                                   handle,@@ -178,13 +179,15 @@         write <- foldM (\x ext -> extWrite ext x) writeRaw exts         parse <- foldM (\x ext -> extParse ext x) parseRaw exts -        sentRef    <- newIORef False+        sentRef <- newIORef False+        heartbeat <- newEmptyMVar         let connection = Connection                 { connectionOptions   = options                 , connectionType      = ServerConnection                 , connectionProtocol  = protocol                 , connectionParse     = parse                 , connectionWrite     = write+                , connectionHeartbeat = heartbeat                 , connectionSentClose = sentRef                 } @@ -238,6 +241,7 @@   --------------------------------------------------------------------------------+-- | Requires calling 'pendingStream' and 'Stream.close'. rejectRequest     :: PendingConnection  -- ^ Connection to reject     -> B.ByteString       -- ^ Rejection response body@@ -251,6 +255,9 @@     { connectionOptions   :: !ConnectionOptions     , connectionType      :: !ConnectionType     , connectionProtocol  :: !Protocol+    , connectionHeartbeat :: !(MVar ())+    -- ^ This MVar is filled whenever a pong is received.  This is used by+    -- 'withPingPong' to timeout the connection if a pong is not received.     , connectionParse     :: !(IO (Maybe Message))     , connectionWrite     :: !([Message] -> IO ())     , connectionSentClose :: !(IORef Bool)@@ -293,6 +300,7 @@                 unless hasSentClose $ send conn msg                 throwIO $ CloseRequest i closeMsg             Pong _    -> do+                _ <- tryPutMVar (connectionHeartbeat conn) ()                 connectionOnPong (connectionOptions conn)                 receiveDataMessage conn             Ping pl   -> do@@ -388,6 +396,10 @@ sendPing :: WebSocketsData a => Connection -> a -> IO () sendPing conn = send conn . ControlMessage . Ping . toLazyByteString +--------------------------------------------------------------------------------+-- | Send a pong+sendPong :: WebSocketsData a => Connection -> a -> IO ()+sendPong conn = send conn . ControlMessage . Pong . toLazyByteString  -------------------------------------------------------------------------------- -- | Forks a ping thread, sending a ping message every @n@ seconds over the@@ -396,6 +408,9 @@ -- This is useful to keep idle connections open through proxies and whatnot. -- Many (but not all) proxies have a 60 second default timeout, so based on that -- sending a ping every 30 seconds is a good idea.+--+-- Note that usually you want to use 'Network.WebSockets.Connection.PingPong.withPingPong'+-- to timeout the connection if a pong is not received. withPingThread     :: Connection     -> Int    -- ^ Second interval in which pings should be sent.@@ -404,7 +419,6 @@     -> IO a   -- ^ Executes application and kills ping thread when done. withPingThread conn n action app =     Async.withAsync (pingThread conn n action) (\_ -> app)-  -------------------------------------------------------------------------------- -- | DEPRECATED: Use 'withPingThread' instead.
src/Network/WebSockets/Connection/Options.hs view
@@ -31,6 +31,8 @@     { connectionOnPong                :: !(IO ())       -- ^ Whenever a 'pong' is received, this IO action is executed. It can be       -- used to tickle connections or fire missiles.+    , connectionTimeout               :: !Int+      -- ^ Timeout for connection establishment in seconds. Only used in the client.     , connectionCompressionOptions    :: !CompressionOptions       -- ^ Enable 'PermessageDeflate'.     , connectionStrictUnicode         :: !Bool@@ -59,9 +61,11 @@ -- * Nothing happens when a pong is received. -- * Compression is disabled. -- * Lenient unicode decoding.+-- * 30 second timeout for connection establishment. defaultConnectionOptions :: ConnectionOptions defaultConnectionOptions = ConnectionOptions     { connectionOnPong                = return ()+    , connectionTimeout               = 30     , connectionCompressionOptions    = NoCompression     , connectionStrictUnicode         = False     , connectionFramePayloadSizeLimit = mempty
+ src/Network/WebSockets/Connection/PingPong.hs view
@@ -0,0 +1,62 @@+module Network.WebSockets.Connection.PingPong+    ( withPingPong+    , PingPongOptions(..)+    , PongTimeout(..)+    , defaultPingPongOptions+    ) where ++import Control.Concurrent.Async as Async+import Control.Exception+import Control.Monad (void)+import Network.WebSockets.Connection (Connection, connectionHeartbeat, pingThread)+import Control.Concurrent.MVar (takeMVar)+import System.Timeout (timeout)+++-- | Exception type used to kill connections if there+-- is a pong timeout.+data PongTimeout = PongTimeout deriving Show++instance Exception PongTimeout+++-- | Options for ping-pong+-- +-- Make sure that the ping interval is less than the pong timeout,+-- for example N/2.+data PingPongOptions = PingPongOptions {+    pingInterval :: Int, -- ^ Interval in seconds+    pongTimeout :: Int, -- ^ Timeout in seconds+    pingAction :: IO () -- ^ Action to perform after sending a ping+}++-- | Default options for ping-pong+-- +--   Ping every 15 seconds, timeout after 30 seconds+defaultPingPongOptions :: PingPongOptions+defaultPingPongOptions = PingPongOptions {+    pingInterval = 15,+    pongTimeout = 30,+    pingAction = return ()+}++-- | Run an application with ping-pong enabled. Raises PongTimeout if a pong is not received.+-- +-- Can used with Client and Server connections.+withPingPong :: PingPongOptions -> Connection -> (Connection -> IO ()) -> IO ()+withPingPong options connection app = void $ +    withAsync (app connection) $ \appAsync -> do+        withAsync (pingThread connection (pingInterval options) (pingAction options)) $ \pingAsync -> do+            withAsync (heartbeat >> throwIO PongTimeout) $ \heartbeatAsync -> do+                waitAnyCancel [appAsync, pingAsync, heartbeatAsync]+    where+        heartbeat = whileJust $ timeout (pongTimeout options * 1000 * 1000) +           $ takeMVar (connectionHeartbeat connection)++        -- Loop until action returns Nothing+        whileJust :: IO (Maybe a) -> IO ()+        whileJust action = do+            result <- action+            case result of+                Nothing -> return ()+                Just _ -> whileJust action
src/Network/WebSockets/Http.hs view
@@ -100,7 +100,9 @@     | MalformedResponse ResponseHead String     -- | The request was well-formed, but the library user rejected it.     -- (e.g. "unknown path")-    | RequestRejected Request String+    | RequestRejected RequestHead ResponseHead+    -- | The connection timed out+    | ConnectionTimeout     -- | for example "EOF came too early" (which is actually a parse error)     -- or for your own errors. (like "unknown path"?)     | OtherHandshakeException String
src/Network/WebSockets/Hybi13.hs view
@@ -78,6 +78,8 @@     -- - Switching Protocols     --     -- But we don't check it for now+    when (responseCode response == 400) $ Left $+        RequestRejected request response      when (responseCode response /= 101) $ Left $         MalformedResponse response "Wrong response status or message." 
src/Network/WebSockets/Server.hs view
@@ -19,21 +19,17 @@   ---------------------------------------------------------------------------------import           Control.Concurrent            (threadDelay) import qualified Control.Concurrent.Async      as Async-import           Control.Exception             (Exception, allowInterrupt,-                                                bracket, bracketOnError,-                                                finally, mask_, throwIO)-import           Control.Monad                 (forever, void, when)-import qualified Data.IORef                    as IORef-import           Data.Maybe                    (isJust)+import           Control.Exception             (bracket,+                                                bracketOnError, finally, mask_,+                                                throwIO) import           Network.Socket                (Socket) import qualified Network.Socket                as S-import qualified System.Clock                  as Clock   -------------------------------------------------------------------------------- import           Network.WebSockets.Connection+import           Network.WebSockets.Connection.PingPong (PongTimeout(..)) import           Network.WebSockets.Http import qualified Network.WebSockets.Stream     as Stream import           Network.WebSockets.Types@@ -85,10 +81,6 @@     { serverHost              :: String     , serverPort              :: Int     , serverConnectionOptions :: ConnectionOptions-    -- | Require a pong from the client every N seconds; otherwise kill the-    -- connection.  If you use this, you should also use 'withPingThread' to-    -- send a ping at a smaller interval; for example N/2.-    , serverRequirePong       :: Maybe Int     }  @@ -98,7 +90,6 @@     { serverHost              = "127.0.0.1"     , serverPort              = 8080     , serverConnectionOptions = defaultConnectionOptions-    , serverRequirePong       = Nothing     }  @@ -110,59 +101,17 @@ runServerWithOptions :: ServerOptions -> ServerApp -> IO a runServerWithOptions opts app = S.withSocketsDo $     bracket-    (makeListenSocket host port)-    S.close $ \sock -> mask_ $ forever $ do-        allowInterrupt-        (conn, _) <- S.accept sock--        -- This IORef holds a time at which the thread may be killed.  This time-        -- can be extended by calling 'tickle'.-        killRef <- IORef.newIORef =<< (+ killDelay) <$> getSecs-        let tickle = IORef.writeIORef killRef =<< (+ killDelay) <$> getSecs--        -- Update the connection options to call 'tickle' whenever a pong is-        -- received.-        let connOpts'-                | not useKiller = connOpts-                | otherwise     = connOpts-                    { connectionOnPong = tickle >> connectionOnPong connOpts-                    }--        -- Run the application.-        appAsync  <- Async.asyncWithUnmask $ \unmask ->-            (unmask $ do-                runApp conn connOpts' app) `finally`-            (S.close conn)--        -- Install the killer if required.-        when useKiller $ void $ Async.async (killer killRef appAsync)-  where-    host     = serverHost opts-    port     = serverPort opts-    connOpts = serverConnectionOptions opts--    -- Get the current number of seconds on some clock.-    getSecs = Clock.sec <$> Clock.getTime Clock.Monotonic--    -- Parse the 'serverRequirePong' options.-    useKiller = isJust $ serverRequirePong opts-    killDelay = maybe 0 fromIntegral (serverRequirePong opts)--    -- Thread that reads the killRef, and kills the application if enough time-    -- has passed.-    killer killRef appAsync = do-        killAt   <- IORef.readIORef killRef-        now      <- getSecs-        appState <- Async.poll appAsync-        case appState of-            -- Already finished/killed/crashed, we can give up.-            Just _ -> return ()-            -- Should not be killed yet.  Wait and try again.-            Nothing | now < killAt -> do-                threadDelay (fromIntegral killDelay * 1000 * 1000)-                killer killRef appAsync-            -- Time to kill.-            _ -> Async.cancelWith appAsync PongTimeout+    (makeListenSocket (serverHost opts) (serverPort opts))+    S.close+    (\sock ->+        let+            mainThread = do+                (conn, _) <- S.accept sock+                Async.withAsyncWithUnmask+                    (\unmask -> unmask (runApp conn (serverConnectionOptions opts) app) `finally` S.close conn)+                    (\_ -> mainThread)+        in mask_ mainThread+    )   --------------------------------------------------------------------------------@@ -200,7 +149,7 @@  -------------------------------------------------------------------------------- -- | Turns a socket, connected to some client, into a 'PendingConnection'. The--- 'PendingConnection' should be closed using 'Stream.close' later.+-- 'PendingConnection' should be closed using 'pendingStream' and 'Stream.close' later. makePendingConnection     :: Socket -> ConnectionOptions -> IO PendingConnection makePendingConnection socket opts = do@@ -223,13 +172,3 @@             , pendingOnAccept = \_ -> return ()             , pendingStream   = stream             }-------------------------------------------------------------------------------------- | Internally used exception type used to kill connections if there--- is a pong timeout.-data PongTimeout = PongTimeout deriving Show------------------------------------------------------------------------------------instance Exception PongTimeout
src/Network/WebSockets/Stream.hs view
@@ -14,7 +14,7 @@  import           Control.Concurrent.MVar        (MVar, newEmptyMVar, newMVar,                                                  putMVar, takeMVar, withMVar)-import           Control.Exception              (SomeException, SomeAsyncException, throwIO, catch, fromException)+import           Control.Exception              (SomeException, SomeAsyncException, throwIO, catch, try, fromException) import           Control.Monad                  (forM_) import qualified Data.Attoparsec.ByteString     as Atto import qualified Data.Binary.Get                as BIN@@ -31,6 +31,7 @@ #else import qualified Network.Socket.ByteString      as SB (sendAll) #endif+import           System.IO.Error                (isResourceVanishedError)  import           Network.WebSockets.Types @@ -59,7 +60,7 @@ --   writing from the stream will be thread-safe, i.e. this function will create --   a receive and write lock to be used internally. ----- - Reading from or writing or to a closed 'Stream' will always throw an+-- - Reading from or writing to a closed 'Stream' will always throw an --   exception, even if the underlying "receive" and "send" functions do not --   (we do the bookkeeping). --@@ -116,8 +117,13 @@ makeSocketStream socket = makeStream receive send   where     receive = do-        bs <- SB.recv socket 8192-        return $ if B.null bs then Nothing else Just bs+        bs <- try $ SB.recv socket 8192+        case bs of+            -- If the resource vanished, the socket was closed+            Left e | isResourceVanishedError e -> return Nothing+                   | otherwise                 -> throwIO e+            Right bs' | B.null bs'             -> return Nothing+                      | otherwise              -> return $ Just bs'      send Nothing   = return ()     send (Just bs) = do
tests/haskell/Network/WebSockets/Server/Tests.hs view
@@ -10,6 +10,7 @@ import           Control.Applicative            ((<$>), (<|>)) import           Control.Concurrent             (forkIO, killThread,                                                  threadDelay)+import           Control.Concurrent.Async       (Async, async, cancel) import           Control.Exception              (SomeException, catch, handle) import           Control.Monad                  (forever, replicateM, unless) import           Data.IORef                     (IORef, newIORef, readIORef,@@ -39,6 +40,7 @@     , testCase "bulk server/client"   testBulkServerClient     , testCase "onPong"               testOnPong     , testCase "ipv6 server"          testIpv6Server+    , testCase "reject request"       testRejectRequest      ]  @@ -69,7 +71,7 @@ testServerClient :: String -> (Connection -> [BL.ByteString] -> IO ()) -> Assertion testServerClient host sendMessages = withEchoServer host 42940 "Bye" $ do     texts  <- map unArbitraryUtf8 <$> sample-    texts' <- retry $ runClient host 42940 "/chat" $ client texts+    texts' <- runClient host 42940 "/chat" $ client texts     texts @=? texts'   where     client :: [BL.ByteString] -> ClientApp [BL.ByteString]@@ -80,8 +82,30 @@         expectCloseException conn "Bye"         return texts' +--------------------------------------------------------------------------------+testRejectRequest :: Assertion+testRejectRequest = withRejectingServer+  where+    client :: ClientApp ()+    client _ = error "Client should not be able to connect" +    server :: ServerApp+    server pendingConnection = rejectRequest pendingConnection "Bye" +    withRejectingServer :: IO ()+    withRejectingServer = do+        serverThread <- async $ runServer "127.0.0.1" 42940 server+        waitSome+        () <- runClient "127.0.0.1" 42940 "/chat" client `catch` handler+        waitSome+        cancel serverThread+        return ()++    handler :: HandshakeException -> IO ()+    handler (RequestRejected _ response) = do+        responseCode response @=? 400+    handler exc  = error $ "Unexpected exception " ++ show exc+ -------------------------------------------------------------------------------- testOnPong :: Assertion testOnPong = withEchoServer "127.0.0.1" 42941 "Bye" $ do@@ -115,29 +139,15 @@ waitSome :: IO () waitSome = threadDelay $ 200 * 1000 - ----------------------------------------------------------------------------------- HOLY SHIT WHAT SORT OF ATROCITY IS THIS?!?!?!------ The problem is that sometimes, the server hasn't been brought down yet--- before the next test, which will cause it not to be able to bind to the--- same port again. In this case, we just retry.------ The same is true for our client: possibly, the server is not up yet--- before we run the client. We also want to retry in that case.-retry :: IO a -> IO a-retry action = (\(_ :: SomeException) -> waitSome >> action) `handle` action----------------------------------------------------------------------------------- withEchoServer :: String -> Int -> BL.ByteString -> IO a -> IO a withEchoServer host port expectedClose action = do     cRef <- newIORef False-    serverThread <- forkIO $ retry $ runServer host port (\c -> server c `catch` handleClose cRef)+    serverThread <- async $ runServer host port (\c -> server c `catch` handleClose cRef)     waitSome     result <- action     waitSome-    killThread serverThread+    cancel serverThread     closeCalled <- readIORef cRef     unless closeCalled $ error "Expecting the CloseRequest exception"     return result
websockets.cabal view
@@ -1,5 +1,5 @@ Name:    websockets-Version: 0.12.7.3+Version: 0.13.0.0  Synopsis:   A sensible and clean way to write WebSocket-capable servers in Haskell.@@ -31,6 +31,7 @@                Jasper Van der Jeugt <m@jaspervdj.be>                Steffen Schuldenzucker <steffen.schuldenzucker@googlemail.com>                Alex Lang <lang@tsurucapital.com>+               Domen Kožar Maintainer:    Jasper Van der Jeugt <m@jaspervdj.be> Stability:     experimental Category:      Network@@ -62,6 +63,7 @@     Network.WebSockets     Network.WebSockets.Client     Network.WebSockets.Connection+    Network.WebSockets.Connection.PingPong     Network.WebSockets.Extensions     Network.WebSockets.Stream     -- Network.WebSockets.Util.PubSub TODO@@ -82,19 +84,17 @@   Build-depends:     async             >= 2.2    && < 2.3,     attoparsec        >= 0.10   && < 0.15,-    base              >= 4.8    && < 5,+    base              >= 4.14   && < 5,     base64-bytestring >= 0.1    && < 1.3,     binary            >= 0.8.1  && < 0.11,-    bytestring        >= 0.9    && < 0.12,-    bytestring-builder             < 0.11,+    bytestring        >= 0.9    && < 0.13,     case-insensitive  >= 0.3    && < 1.3,-    clock             >= 0.8    && < 0.9,     containers        >= 0.3    && < 0.7,     network           >= 2.3    && < 3.2,-    random            >= 1.0    && < 1.3,+    random            >= 1.0.1  && < 1.3,     SHA               >= 1.5    && < 1.7,     streaming-commons >= 0.1    && < 0.3,-    text              >= 0.10   && < 1.3,+    text              >= 0.10   && < 2.2,     entropy           >= 0.2.1  && < 0.5  Test-suite websockets-tests@@ -110,6 +110,7 @@     Network.WebSockets.Client     Network.WebSockets.Connection     Network.WebSockets.Connection.Options+    Network.WebSockets.Connection.PingPong     Network.WebSockets.Extensions     Network.WebSockets.Extensions.Description     Network.WebSockets.Extensions.PermessageDeflate@@ -142,22 +143,20 @@     -- Copied from regular dependencies...     async             >= 2.2    && < 2.3,     attoparsec        >= 0.10   && < 0.15,-    base              >= 4      && < 5,+    base              >= 4.14   && < 5,     base64-bytestring >= 0.1    && < 1.3,     binary            >= 0.8.1  && < 0.11,-    bytestring        >= 0.9    && < 0.12,-    bytestring-builder             < 0.11,+    bytestring        >= 0.9    && < 0.13,     case-insensitive  >= 0.3    && < 1.3,-    clock             >= 0.8    && < 0.9,     containers        >= 0.3    && < 0.7,     network           >= 2.3    && < 3.2,     random            >= 1.0    && < 1.3,     SHA               >= 1.5    && < 1.7,     streaming-commons >= 0.1    && < 0.3,-    text              >= 0.10   && < 1.3,+    text              >= 0.10   && < 2.2,     entropy           >= 0.2.1  && < 0.5 -Executable websockets-example+Executable websockets-server-example   If !flag(Example)     Buildable: False @@ -167,24 +166,26 @@   Default-language: Haskell2010    Build-depends:+    base,     websockets,-    -- Copied from regular dependencies...-    async             >= 2.2    && < 2.3,-    attoparsec        >= 0.10   && < 0.15,-    base              >= 4      && < 5,-    base64-bytestring >= 0.1    && < 1.3,-    binary            >= 0.8.1  && < 0.11,-    bytestring        >= 0.9    && < 0.12,-    bytestring-builder             < 0.11,-    case-insensitive  >= 0.3    && < 1.3,-    clock             >= 0.8    && < 0.9,-    containers        >= 0.3    && < 0.7,-    network           >= 2.3    && < 3.2,-    random            >= 1.0    && < 1.3,-    SHA               >= 1.5    && < 1.7,-    text              >= 0.10   && < 1.3,-    entropy           >= 0.2.1  && < 0.5+    text +Executable websockets-client-example+  If !flag(Example)+    Buildable: False++  Hs-source-dirs:   example+  Main-is:          client.hs+  Ghc-options:      -Wall+  Default-language: Haskell2010++  Build-depends:+    base,+    websockets,+    text,+    network,+    mtl+ Executable websockets-autobahn   If !flag(Example)     Buildable: False@@ -202,18 +203,16 @@     -- Copied from regular dependencies...     async             >= 2.2    && < 2.3,     attoparsec        >= 0.10   && < 0.15,-    base              >= 4      && < 5,+    base              >= 4.14     && < 5,     base64-bytestring >= 0.1    && < 1.3,     binary            >= 0.8.1  && < 0.11,-    bytestring        >= 0.9    && < 0.12,-    bytestring-builder             < 0.11,+    bytestring        >= 0.9    && < 0.13,     case-insensitive  >= 0.3    && < 1.3,-    clock             >= 0.8    && < 0.9,     containers        >= 0.3    && < 0.7,     network           >= 2.3    && < 3.2,     random            >= 1.0    && < 1.3,     SHA               >= 1.5    && < 1.7,-    text              >= 0.10   && < 1.3,+    text              >= 0.10   && < 2.2,     entropy           >= 0.2.1  && < 0.5  Benchmark bench-mask@@ -231,16 +230,14 @@     -- Copied from regular dependencies...     async             >= 2.2    && < 2.3,     attoparsec        >= 0.10   && < 0.15,-    base              >= 4      && < 5,+    base              >= 4.14   && < 5,     base64-bytestring >= 0.1    && < 1.3,     binary            >= 0.8.1  && < 0.11,-    bytestring        >= 0.9    && < 0.12,-    bytestring-builder             < 0.11,+    bytestring        >= 0.9    && < 0.13,     case-insensitive  >= 0.3    && < 1.3,-    clock             >= 0.8    && < 0.9,     containers        >= 0.3    && < 0.7,     network           >= 2.3    && < 3.2,     random            >= 1.0    && < 1.3,     SHA               >= 1.5    && < 1.7,-    text              >= 0.10   && < 1.3,+    text              >= 0.10   && < 2.2,     entropy           >= 0.2.1  && < 0.5