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 +15/−0
- example/client.hs +40/−0
- src/Network/WebSockets.hs +4/−0
- src/Network/WebSockets/Client.hs +10/−4
- src/Network/WebSockets/Connection.hs +18/−4
- src/Network/WebSockets/Connection/Options.hs +4/−0
- src/Network/WebSockets/Connection/PingPong.hs +62/−0
- src/Network/WebSockets/Http.hs +3/−1
- src/Network/WebSockets/Hybi13.hs +2/−0
- src/Network/WebSockets/Server.hs +16/−77
- src/Network/WebSockets/Stream.hs +10/−4
- tests/haskell/Network/WebSockets/Server/Tests.hs +27/−17
- websockets.cabal +36/−39
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