packages feed

websockets 0.4.1.0 → 0.5.0.0

raw patch · 18 files changed

+387/−451 lines, 18 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Network.WebSockets: BinaryFrame :: FrameType
- Network.WebSockets: CloseFrame :: FrameType
- Network.WebSockets: ContinuationFrame :: FrameType
- Network.WebSockets: Frame :: !Bool -> !FrameType -> !ByteString -> Frame
- Network.WebSockets: PingFrame :: FrameType
- Network.WebSockets: PongFrame :: FrameType
- Network.WebSockets: TextFrame :: FrameType
- Network.WebSockets: data Frame
- Network.WebSockets: data FrameType
- Network.WebSockets: frameFin :: Frame -> !Bool
- Network.WebSockets: framePayload :: Frame -> !ByteString
- Network.WebSockets: frameType :: Frame -> !FrameType
- Network.WebSockets: receiveFrame :: Protocol p => WebSockets p Frame
- Network.WebSockets: sendFrame :: Protocol p => Frame -> WebSockets p ()

Files

src/Network/WebSockets.hs view
@@ -106,8 +106,6 @@     , I.RequestHttpPart (..)     , I.Request (..)     , I.Response (..)-    , I.FrameType (..)-    , I.Frame (..)     , I.Message (..)     , I.ControlMessage (..)     , I.DataMessage (..)@@ -121,13 +119,11 @@     , I.getVersion        -- * Receiving-    , receiveFrame-    , receive+    , I.receive     , receiveDataMessage     , receiveData        -- * Sending-    , sendFrame     , I.send     , sendTextData     , sendBinaryData@@ -150,10 +146,8 @@     , I.ConnectionError(..)     ) where -import Control.Monad.State (put, get) import Control.Monad.Trans (liftIO) -import qualified Network.WebSockets.Demultiplex as I import qualified Network.WebSockets.Handshake as I import qualified Network.WebSockets.Handshake.Http as I import qualified Network.WebSockets.Monad as I@@ -168,31 +162,10 @@ -- determined by the request, we can't provide this as a WebSockets action. See -- the various flavours of runWebSockets. --- | Read a 'I.Frame' from the socket. Blocks until a frame is received. If the--- socket is closed, throws 'ConnectionClosed' (a 'ConnectionError')------ Note that a typical library user will want to use something like--- 'receiveByteStringData' instead.-receiveFrame :: I.Protocol p => I.WebSockets p I.Frame-receiveFrame = do-    proto <- I.getProtocol-    I.receiveWith $ I.decodeFrame proto---- | Receive a message-receive :: I.Protocol p => I.WebSockets p (I.Message p)-receive = I.WebSockets $ do-    f <- I.unWebSockets receiveFrame-    s <- get-    let (msg, s') = I.demultiplex s f-    put s'-    case msg of-        Nothing -> I.unWebSockets receive-        Just m  -> return m- -- | Receive an application message. Automatically respond to control messages. receiveDataMessage :: I.Protocol p => I.WebSockets p (I.DataMessage p) receiveDataMessage = do-    m <- receive+    m <- I.receive     case m of         (I.DataMessage am) -> return am         (I.ControlMessage cm) -> case cm of@@ -219,13 +192,7 @@  -- | Send a 'I.Response' to the socket immediately. sendResponse :: I.Protocol p => I.Response -> I.WebSockets p ()-sendResponse response = I.sendWith I.encodeResponse response---- | A low-level function to send an arbitrary frame over the wire.-sendFrame :: I.Protocol p => I.Frame -> I.WebSockets p ()-sendFrame frame = do-    proto <- I.getProtocol-    I.sendWith (I.encodeFrame proto) frame+sendResponse = I.sendBuilder . I.encodeResponse  -- | Send a text message sendTextData :: (I.TextProtocol p, I.WebSocketsData a) => a -> I.WebSockets p ()
− src/Network/WebSockets/Demultiplex.hs
@@ -1,54 +0,0 @@--- | Demultiplexing of frames into messages-module Network.WebSockets.Demultiplex-    ( DemultiplexState-    , emptyDemultiplexState-    , demultiplex-    ) where--import Blaze.ByteString.Builder (Builder)-import Data.Monoid (mappend)-import qualified Blaze.ByteString.Builder as B--import Network.WebSockets.Types---- | Internal state used by the demultiplexer-newtype DemultiplexState = DemultiplexState-    { unDemultiplexState :: Maybe (FrameType, Builder)-    }--emptyDemultiplexState :: DemultiplexState-emptyDemultiplexState = DemultiplexState Nothing--demultiplex :: DemultiplexState-            -> Frame-            -> (Maybe (Message p), DemultiplexState)-demultiplex state (Frame fin tp pl) = case tp of-    -- Return control messages immediately, they have no influence on the state-    CloseFrame  -> (Just (ControlMessage (Close pl)), state)-    PingFrame   -> (Just (ControlMessage (Ping pl)), state)-    PongFrame   -> (Just (ControlMessage (Pong pl)), state)-    -- If we're dealing with a continuation...-    ContinuationFrame -> case unDemultiplexState state of-        -- We received a continuation but we don't have any state. Let's ignore-        -- this fragment...-        Nothing -> (Nothing, DemultiplexState Nothing)-        -- Append the payload to the state-        -- TODO: protect against overflows-        Just (amt, b)-            | not fin   -> (Nothing, DemultiplexState (Just (amt, b')))-            | otherwise -> case amt of-                TextFrame   -> (Just (DataMessage (Text m)), e)-                BinaryFrame -> (Just (DataMessage (Binary m)), e)-                _           -> error "Demultiplex.demultiplex: Internal error"-          where-            b' = b `mappend` plb-            m = B.toLazyByteString b'-    TextFrame-        | fin       -> (Just (DataMessage (Text pl)), e)-        | otherwise -> (Nothing, DemultiplexState (Just (TextFrame, plb)))-    BinaryFrame-        | fin       -> (Just (DataMessage (Binary pl)), e)-        | otherwise -> (Nothing, DemultiplexState (Just (BinaryFrame, plb)))-  where-    e = emptyDemultiplexState-    plb = B.fromLazyByteString pl
src/Network/WebSockets/Handshake.hs view
@@ -2,41 +2,28 @@ {-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-} module Network.WebSockets.Handshake     ( HandshakeError (..)+    , handshake     , responseError-    , tryFinishRequest     ) where +import Data.List (find)+ import qualified Data.ByteString as B+import qualified Data.Enumerator as E  import Network.WebSockets.Handshake.Http import Network.WebSockets.Protocol-import Network.WebSockets.Types --- | Receives and checks the client handshake.--- --- * If this fails, we encountered a syntax error while processing the client's--- request. That is very bad.--- --- * If it returns @Left@, we either don't support the protocol requested by--- the client ('NotSupported') or the data the client sent doesn't match the--- protocol ('MalformedRequest')------ * Otherwise, we are guaranteed that the client handshake is valid and have--- generated a response ready to be sent back.----tryFinishRequest :: Protocol p-                 => RequestHttpPart-                 -> Decoder p (Either HandshakeError (Request, p))-tryFinishRequest httpReq = tryInOrder implementations-    -- NOTE that the protocols are tried in order, the first one first. So that-    -- should be the latest one. (only matters if we have overlaps in specs,-    -- though)-  where-    tryInOrder []       = return . Left $ NotSupported-    tryInOrder (p : ps) = finishRequest p httpReq >>= \res -> case res of-        (Left NotSupported) -> tryInOrder ps-        (Left e)            -> return (Left e)-        (Right req)         -> return . Right $ (req, p)+-- | Receives and checks the client handshake. If no suitable protocol is found+-- (or the client sends garbage), a 'HandshakeError' will be thrown.+handshake :: (Monad m, Protocol p)+          => RequestHttpPart+          -> E.Iteratee B.ByteString m (Request, p)+handshake rhp = case find (flip supported rhp) implementations of+    Nothing -> E.throwError NotSupported+    Just p  -> do+        rq <- finishRequest p rhp+        return (rq, p)  -- | Respond to errors encountered during handshake. First argument may be -- bottom.
src/Network/WebSockets/Handshake/Http.hs view
@@ -6,6 +6,7 @@     , Request (..)     , Response (..)     , HandshakeError (..)+    , getSecWebSocketVersion     , decodeRequest     , encodeResponse     , response101@@ -18,17 +19,14 @@ import Control.Exception (Exception) import Control.Monad.Error (Error (..)) -import Data.Attoparsec (string, takeWhile1, word8)-import Data.Attoparsec.Combinator (manyTill) import Data.ByteString.Char8 () import Data.ByteString.Internal (c2w)+import qualified Data.Attoparsec as A import qualified Blaze.ByteString.Builder as Builder import qualified Blaze.ByteString.Builder.Char.Utf8 as Builder import qualified Data.ByteString as B import qualified Data.CaseInsensitive as CI -import Network.WebSockets.Types- -- | Request headers type Headers = [(CI.CI B.ByteString, B.ByteString)] @@ -79,28 +77,32 @@  instance Exception HandshakeError +-- | Get the @Sec-WebSocket-Version@ header+getSecWebSocketVersion :: RequestHttpPart -> Maybe B.ByteString+getSecWebSocketVersion p = lookup "Sec-WebSocket-Version" (requestHttpHeaders p)+ -- | Parse an initial request-decodeRequest :: Decoder p RequestHttpPart+decodeRequest :: A.Parser RequestHttpPart decodeRequest = RequestHttpPart     <$> requestLine-    <*> manyTill header newline+    <*> A.manyTill header newline   where-    space = word8 (c2w ' ')-    newline = string "\r\n"+    space   = A.word8 (c2w ' ')+    newline = A.string "\r\n" -    requestLine = string "GET" *> space *> takeWhile1 (/= c2w ' ')+    requestLine = A.string "GET" *> space *> A.takeWhile1 (/= c2w ' ')         <* space-        <* string "HTTP/1.1" <* newline+        <* A.string "HTTP/1.1" <* newline      header = (,)-        <$> (CI.mk <$> takeWhile1 (/= c2w ':'))-        <*  string ": "-        <*> takeWhile1 (/= c2w '\r')+        <$> (CI.mk <$> A.takeWhile1 (/= c2w ':'))+        <*  A.string ": "+        <*> A.takeWhile1 (/= c2w '\r')         <*  newline  -- | Encode an HTTP upgrade response-encodeResponse :: Encoder p Response-encodeResponse _ (Response code msg headers body) =+encodeResponse :: Response -> Builder.Builder+encodeResponse (Response code msg headers body) =     Builder.copyByteString "HTTP/1.1 " `mappend`     Builder.fromString (show code)     `mappend`     Builder.fromChar ' '               `mappend`
− src/Network/WebSockets/Handshake/ShyIterParser.hs
@@ -1,40 +0,0 @@-module Network.WebSockets.Handshake.ShyIterParser-    ( shyIterParser-    ) where--import Data.Attoparsec.Enumerator (ParseError(..))-import qualified Data.Attoparsec as A-import qualified Data.ByteString as B-import qualified Data.Enumerator as E---- | Behaves almost the same as the 'iterParser' function from the--- attoparsec-enumerator package, with the exception that, while the iteratee--- created by 'iterParser' always starts in a 'Continue' state, this one will--- only ask for more data if the underlying parser needs it. That's important--- because e.g. network connection will block and wait for more data on a--- 'Continue'. For messages or frames the problem will not occur as we know the--- length of the payload and therefore can determine EOF (= end of the frame--- here).------ We use parsers that don't take data for validation in Protocols.Hybi10-shyIterParser :: (Monad m) => A.Parser a -> E.Iteratee B.ByteString m a-shyIterParser p = parseLoop (A.parse p) [B.empty]-    -- feed empty to the parse *once*.-  where-    -- from here: copied from attoparsec-enumerator-0.2.0.4-    step parse (E.Chunks xs) = parseLoop parse (notEmpty xs)-    step parse E.EOF = case A.feed (parse B.empty) B.empty of-        A.Done _ a -> E.yield a E.EOF-        A.Partial _ -> err [] "iterParser: divergent parser"-        A.Fail _ ctx msg -> err ctx msg--    parseLoop parse [] = E.continue (step parse)-    parseLoop parse (x:xs) = case parse x of-        A.Done extra a -> E.yield a $ if B.null extra-            then E.Chunks xs-            else E.Chunks (extra:xs)-        A.Partial parse' -> parseLoop parse' xs-        A.Fail _ ctx msg -> err ctx msg--    err ctx msg = E.throwError (ParseError ctx msg)-    notEmpty = filter (not . B.null)
− src/Network/WebSockets/Mask.hs
@@ -1,34 +0,0 @@--- | Masking of fragmes using a simple XOR algorithm-{-# LANGUAGE BangPatterns #-}-module Network.WebSockets.Mask-    ( Mask-    , maskPayload-    , randomMask-    ) where--import Control.Applicative ((<$>))-import Control.Monad (replicateM)-import Data.Bits (xor)-import System.Random (randomRIO)--import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL---- | ByteString should be exactly 4 bytes long-type Mask = Maybe B.ByteString---- | Apply mask-maskPayload :: Mask -> BL.ByteString -> BL.ByteString-maskPayload Nothing     = id-maskPayload (Just mask) = snd . BL.mapAccumL f 0-  where-    len = B.length mask-    f !i !c = let i' = (i + 1) `mod` len-                  m = mask `B.index` i-              in (i', m `xor` c)---- | Create a random mask-randomMask :: IO Mask-randomMask = Just . B.pack <$> replicateM 4 randomByte-  where-    randomByte = fromIntegral <$> randomRIO (0x00 :: Int, 0xff)
src/Network/WebSockets/Monad.hs view
@@ -1,5 +1,5 @@ -- | Provides a simple, clean monad to write websocket servers in-{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings,+{-# LANGUAGE BangPatterns, GeneralizedNewtypeDeriving, OverloadedStrings,         NoMonomorphismRestriction, Rank2Types, ScopedTypeVariables #-} module Network.WebSockets.Monad     ( WebSocketsOptions (..)@@ -10,8 +10,8 @@     , runWebSocketsHandshake     , runWebSocketsWithHandshake     , runWebSocketsWith'-    , receiveWith-    , sendWith+    , receive+    , sendBuilder     , send     , Sink     , sendSink@@ -26,27 +26,28 @@  import Control.Applicative (Applicative, (<$>)) import Control.Concurrent (forkIO, threadDelay)-import Control.Concurrent.MVar (newMVar, withMVar)+import Control.Concurrent.MVar (MVar, modifyMVar_, newMVar) import Control.Exception (Exception (..), SomeException, throw) import Control.Monad (forever) import Control.Monad.Reader (ReaderT, ask, runReaderT)-import Control.Monad.State (StateT, evalStateT, get) import Control.Monad.Trans (MonadIO, lift, liftIO)+import Data.Foldable (forM_)+import System.Random (newStdGen)  import Blaze.ByteString.Builder (Builder)-import Blaze.ByteString.Builder.Enumerator (builderToByteString) import Data.ByteString (ByteString)-import Data.Enumerator (Enumerator, Iteratee, ($$), (>>==))+import Data.Enumerator (Enumerator, Iteratee, ($$), (>>==), (=$))+import qualified Blaze.ByteString.Builder as BB+import qualified Data.Attoparsec as A+import qualified Data.ByteString.Lazy as BL import qualified Data.Attoparsec.Enumerator as AE import qualified Data.Enumerator as E+import qualified Data.Enumerator.List as EL -import Network.WebSockets.Demultiplex (DemultiplexState, emptyDemultiplexState) import Network.WebSockets.Handshake import Network.WebSockets.Handshake.Http-import Network.WebSockets.Handshake.ShyIterParser-import Network.WebSockets.Mask import Network.WebSockets.Protocol-import Network.WebSockets.Types as T+import Network.WebSockets.Types  -- | Options for the WebSocket program data WebSocketsOptions = WebSocketsOptions@@ -61,15 +62,20 @@  -- | Environment in which the 'WebSockets' monad actually runs data WebSocketsEnv p = WebSocketsEnv-    { options     :: WebSocketsOptions-    , sendBuilder :: Builder -> IO ()-    , protocol    :: p+    { envOptions     :: WebSocketsOptions+    , envSendBuilder :: Builder -> IO ()+    , envSink        :: Sink p+    , envProtocol    :: p     } +-- | Used for asynchronous sending.+newtype Sink p = Sink+    { unSink :: MVar (E.Iteratee (Message p) IO ())+    }+ -- | The monad in which you can write WebSocket-capable applications newtype WebSockets p a = WebSockets-    { unWebSockets :: ReaderT (WebSocketsEnv p)-        (StateT DemultiplexState (Iteratee ByteString IO)) a+    { unWebSockets :: ReaderT (WebSocketsEnv p) (Iteratee (Message p) IO) a     } deriving (Applicative, Functor, Monad, MonadIO)  -- | Receives the initial client handshake, then behaves like 'runWebSockets'.@@ -112,16 +118,18 @@                   -> (Request -> WebSockets p a)                   -> Iteratee ByteString IO ()                   -> Iteratee ByteString IO a-runWebSocketsWith opts httpReq goWs outIter = do-    mreq <- receiveIterateeShy $ tryFinishRequest httpReq-    case mreq of-        (Left err) -> do-            sendIteratee encodeResponse (responseError proto err) outIter-            E.throwError err-        (Right (r, p)) -> runWebSocketsWith' opts p (goWs r) outIter+runWebSocketsWith opts httpReq goWs outIter = E.catchError ok $ \e -> do+    -- If handshake went bad, send response+    forM_ (fromException e) $ \he ->+        let builder = encodeResponse $ responseError (undefined :: p) he+        in liftIO $ makeBuilderSender outIter builder+    -- Re-throw error+    E.throwError e   where-    proto :: p-    proto = undefined+    -- Perform handshake, call runWebSocketsWith'+    ok = do+        (rq, p) <- handshake httpReq+        runWebSocketsWith' opts p (goWs rq) outIter  runWebSocketsWith' :: Protocol p                    => WebSocketsOptions@@ -130,17 +138,24 @@                    -> Iteratee ByteString IO ()                    -> Iteratee ByteString IO a runWebSocketsWith' opts proto ws outIter = do-    sendLock <- liftIO $ newMVar () +    -- Create sink with a random source+    gen <- liftIO newStdGen+    let sinkIter = encodeMessages proto gen =$ builderToByteString =$ outIter+    sink <- Sink <$> liftIO (newMVar sinkIter) -    let sender = makeSend sendLock-        env    = WebSocketsEnv opts sender proto-        state  = runReaderT (unWebSockets ws) env-        iter   = evalStateT state emptyDemultiplexState-    iter-  where-    makeSend sendLock x = withMVar sendLock $ \_ ->-        builderSender outIter x+    let sender = makeBuilderSender outIter+        env    = WebSocketsEnv opts sender sink proto+        iter   = runReaderT (unWebSockets ws) env +    decodeMessages proto =$ iter++makeBuilderSender :: MonadIO m => Iteratee ByteString m b -> Builder -> m ()+makeBuilderSender outIter x = do+    ok <- E.run $ singleton x $$ builderToByteString $$ outIter+    case ok of+        Left err -> throw err+        Right _  -> return ()+ -- | @spawnPingThread n@ spawns a thread which sends a ping every @n@ seconds -- (if the protocol supports it). To be called after having sent the response. spawnPingThread :: BinaryProtocol p => Int -> WebSockets p ()@@ -154,96 +169,66 @@         sendSink sink $ ping ("Hi" :: ByteString)     return () --- | Receive some data from the socket, using a user-supplied parser.-receiveWith :: Decoder p a -> WebSockets p a-receiveWith = liftIteratee . receiveIteratee---- todo: move some stuff to another module. "Decode"?---- | Underlying iteratee version of 'receiveWith'.-receiveIteratee :: Decoder p a -> Iteratee ByteString IO a+-- | Receive arbitrary data.+receiveIteratee :: A.Parser a -> Iteratee ByteString IO a receiveIteratee parser = do     eof <- E.isEOF     if eof         then E.throwError ConnectionClosed         else wrappingParseError . AE.iterParser $ parser --- | Like receiveIteratee, but if the supplied parser is happy with no input,--- we don't supply any more. This is very, very important when we have parsers--- that don't necessarily read data, like hybi10's completeRequest.-receiveIterateeShy :: Decoder p a -> Iteratee ByteString IO a-receiveIterateeShy parser = wrappingParseError $ shyIterParser parser- -- | Execute an iteratee, wrapping attoparsec-enumeratee's ParseError into the -- ParseError constructor (which is a ConnectionError). wrappingParseError :: (Monad m) => Iteratee a m b -> Iteratee a m b wrappingParseError = flip E.catchError $ \e -> E.throwError $     maybe e (toException . ParseError) $ fromException e -sendIteratee :: Encoder p a -> a-             -> Iteratee ByteString IO ()-             -> Iteratee ByteString IO ()-sendIteratee enc resp outIter = do-    liftIO $ mkSend (builderSender outIter) enc resp+-- | Receive a message+receive :: Protocol p => WebSockets p (Message p)+receive = liftIteratee $ do+    mmsg <- EL.head+    case mmsg of+        Nothing  -> E.throwError ConnectionClosed+        Just msg -> return msg --- | Low-leven sending with an arbitrary 'Encoder'-sendWith :: Encoder p a -> a -> WebSockets p ()-sendWith encoder x = WebSockets $ do-    send' <- sendBuilder <$> ask-    liftIO $ mkSend send' encoder x+-- | Send an arbitrary 'Builder'+sendBuilder :: Builder -> WebSockets p ()+sendBuilder builder = WebSockets $ do+    sb <- envSendBuilder <$> ask+    liftIO $ sb builder  -- | Low-level sending with an arbitrary 'T.Message'-send :: Protocol p => T.Message p -> WebSockets p ()+send :: Protocol p => Message p -> WebSockets p () send msg = getSink >>= \sink -> liftIO $ sendSink sink msg --- | Used for asynchronous sending.-newtype Sink p = Sink {unSink :: Message p -> IO ()}- -- | Send a message to a sink. Might generate an exception if the underlying -- connection is closed. sendSink :: Sink p -> Message p -> IO ()-sendSink = unSink+sendSink sink msg = modifyMVar_ (unSink sink) $ \iter -> do+    step <- E.runIteratee $ singleton msg $$ iter+    return $ E.returnI step  -- | In case the user of the library wants to do asynchronous sending to the -- socket, he can extract a 'Sink' and pass this value around, for example, -- to other threads. getSink :: Protocol p => WebSockets p (Sink p)-getSink = WebSockets $ do-    proto <- unWebSockets getProtocol-    send' <- sendBuilder <$> ask-    return $ Sink $ mkSend send' $ encodeMessage $ encodeFrame proto-  where-    -- TODO: proper multiplexing?-    encodeMessage frame mask msg = frame mask $ case msg of-        (ControlMessage (Close pl)) -> Frame True CloseFrame pl-        (ControlMessage (Ping pl))  -> Frame True PingFrame pl-        (ControlMessage (Pong pl))  -> Frame True PongFrame pl-        (DataMessage (Text pl))     -> Frame True TextFrame pl-        (DataMessage (Binary pl))   -> Frame True BinaryFrame pl---- TODO: rename to mkEncodedSender?-mkSend :: (Builder -> IO ()) -> Encoder p a -> a -> IO ()-mkSend send' encoder x = do-    mask <- randomMask-    send' $ encoder mask x+getSink = WebSockets $ envSink <$> ask  singleton :: Monad m => a -> Enumerator a m b singleton c = E.checkContinue0 $ \_ f -> f (E.Chunks [c]) >>== E.returnI -builderSender :: MonadIO m => Iteratee ByteString m b -> Builder -> m ()-builderSender outIter x = do-    ok <- E.run $ singleton x $$ builderToByteString $$ outIter-    case ok of-        Left err -> throw err-        Right _  -> return ()+-- TODO: Figure out why Blaze.ByteString.Enumerator.builderToByteString doesn't+-- work, then inform Simon or send a patch.+builderToByteString :: Monad m => E.Enumeratee Builder ByteString m a+builderToByteString = EL.concatMap $ BL.toChunks . BB.toLazyByteString  -- | Get the current configuration getOptions :: WebSockets p WebSocketsOptions-getOptions = WebSockets $ ask >>= return . options+getOptions = WebSockets $ ask >>= return . envOptions  -- | Get the underlying protocol getProtocol :: WebSockets p p-getProtocol = WebSockets $ protocol <$> ask+getProtocol = WebSockets $ envProtocol <$> ask  -- | Find out the 'WebSockets' version used at runtime getVersion :: Protocol p => WebSockets p String@@ -259,14 +244,12 @@              -> WebSockets p a catchWsError act c = WebSockets $ do     env <- ask-    state <- get-    let it  = peelWebSockets state env $ act-        cit = peelWebSockets state env . c-    lift . lift $ it `E.catchError` cit+    let it  = peelWebSockets env $ act+        cit = peelWebSockets env . c+    lift $ it `E.catchError` cit   where-    peelWebSockets state env =-        flip evalStateT state . flip runReaderT env . unWebSockets+    peelWebSockets env = flip runReaderT env . unWebSockets  -- | Lift an Iteratee computation to WebSockets-liftIteratee :: Iteratee ByteString IO a -> WebSockets p a-liftIteratee = WebSockets . lift . lift+liftIteratee :: Iteratee (Message p) IO a -> WebSockets p a+liftIteratee = WebSockets . lift
src/Network/WebSockets/Protocol.hs view
@@ -11,7 +11,10 @@     , binaryData     ) where +import Blaze.ByteString.Builder (Builder)+import System.Random (RandomGen) import qualified Data.ByteString as B+import qualified Data.Enumerator as E  import Network.WebSockets.Types import Network.WebSockets.Handshake.Http@@ -26,16 +29,32 @@     -- "7", "8" or "17".     headerVersions  :: p -> [B.ByteString] -    encodeFrame     :: p -> Encoder p Frame-    decodeFrame     :: p -> Decoder p Frame+    -- | Determine if the protocol is compatible with a requested version. A+    -- default implementation exists which uses the @headerVersions@ of the+    -- protocol.+    supported       :: p -> RequestHttpPart -> Bool+    supported p h   = case getSecWebSocketVersion h of+        Just v -> v `elem` headerVersions p+        _      -> False +    -- | Encodes messages to binary 'Builder's. Takes a random source so it is+    -- able to do masking of frames (needed in some cases).+    encodeMessages  :: (Monad m, RandomGen g)+                    => p+                    -> g+                    -> E.Enumeratee (Message p) Builder m a++    -- | Decodes messages from binary 'B.ByteString's.+    decodeMessages  :: Monad m => p -> E.Enumeratee B.ByteString (Message p) m a+     -- | Parse and validate the rest of the request. For hybi10, this is just     -- validation, but hybi00 also needs to fetch a "security token"     ---    -- Todo: Maybe we should introduce our own simplified error type here. (to+    -- In case of failure, this function may throw a 'HandshakeError'.     -- be amended with the RequestHttpPart for the user)-    finishRequest   :: p -> RequestHttpPart-                    -> Decoder p (Either HandshakeError Request)+    finishRequest   :: Monad m+                    => p -> RequestHttpPart+                    -> E.Iteratee B.ByteString m Request      -- | Implementations of the specification     implementations :: [p]
src/Network/WebSockets/Protocol/Hybi00.hs view
@@ -4,18 +4,22 @@        , Hybi00        ) where +import Data.Enumerator ((=$))+import Data.Enumerator.List as EL import Network.WebSockets.Protocol import Network.WebSockets.Protocol.Hybi00.Internal import Network.WebSockets.Protocol.Hybi10.Internal+import Network.WebSockets.Protocol.Unsafe  data Hybi00 = forall p. Protocol p => Hybi00 p  instance Protocol Hybi00 where-    version        (Hybi00 p) = version p-    headerVersions (Hybi00 p) = headerVersions p-    encodeFrame    (Hybi00 p) = encodeFrame p-    decodeFrame    (Hybi00 p) = decodeFrame p-    finishRequest  (Hybi00 p) = finishRequest p-    implementations           = [Hybi00 Hybi10_, Hybi00 Hybi00_]+    version        (Hybi00 p)   = version p+    headerVersions (Hybi00 p)   = headerVersions p+    supported      (Hybi00 p) h = supported p h+    encodeMessages (Hybi00 p) g = (EL.map castMessage =$) . encodeMessages p g+    decodeMessages (Hybi00 p)   = (decodeMessages p =$) . EL.map castMessage+    finishRequest  (Hybi00 p)   = finishRequest p+    implementations             = [Hybi00 Hybi10_, Hybi00 Hybi00_]  instance TextProtocol Hybi00
src/Network/WebSockets/Protocol/Hybi00/Internal.hs view
@@ -5,8 +5,6 @@        ) where  import Control.Applicative ((<|>))-import Control.Monad.Error (ErrorT (..), MonadError, throwError)-import Control.Monad.Trans (lift) import Data.Char (isDigit)  import Data.Binary (encode)@@ -14,10 +12,10 @@ import Data.Int (Int32) import qualified Blaze.ByteString.Builder as BB import qualified Data.Attoparsec as A-#if MIN_VERSION_attoparsec(0,10,0)-import qualified Data.Attoparsec.Types as AT-#endif+import qualified Data.Attoparsec.Enumerator as A import qualified Data.ByteString as B+import qualified Data.Enumerator as E+import qualified Data.Enumerator.List as EL import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as BL import qualified Data.CaseInsensitive as CI@@ -29,35 +27,37 @@ data Hybi00_ = Hybi00_  instance Protocol Hybi00_ where-    version         Hybi00_ = "hybi00"-    headerVersions  Hybi00_ = ["0"]  -- but the client will elide it-    encodeFrame     Hybi00_ = encodeFrameHybi00-    decodeFrame     Hybi00_ = decodeFrameHybi00-    finishRequest   Hybi00_ = runErrorT . handshakeHybi00-    implementations         = [Hybi00_]+    version         Hybi00_   = "hybi00"+    headerVersions  Hybi00_   = []  -- The client will elide it+    supported       Hybi00_ h = getSecWebSocketVersion h == Nothing+    encodeMessages  Hybi00_ _ = EL.map encodeMessage+    decodeMessages  Hybi00_   = E.sequence (A.iterParser parseMessage)+    finishRequest   Hybi00_   = handshakeHybi00+    implementations           = [Hybi00_]  instance TextProtocol Hybi00_ -encodeFrameHybi00 :: Encoder p Frame-encodeFrameHybi00 _ (Frame True TextFrame pl) =+encodeMessage :: Message p -> BB.Builder+encodeMessage (DataMessage (Text pl))    =     BB.fromLazyByteString $ "\0" `BL.append` pl `BL.append` "\255"-encodeFrameHybi00 _ (Frame _ CloseFrame _) =+encodeMessage (ControlMessage (Close _)) =     BB.fromLazyByteString  "\255\0"-    -- TODO: prevent the user from doing this using type tags-encodeFrameHybi00 _ _ = error "Not supported"+encodeMessage msg                        = error $+    "Network.WebSockets.Protocol.Hybi00.encodeFrame: unsupported message: " +++    show msg -decodeFrameHybi00 :: Decoder p Frame-decodeFrameHybi00 = decodeTextFrame <|> decodeCloseFrame+parseMessage :: A.Parser (Message p)+parseMessage = parseText <|> parseClose   where-    decodeTextFrame = do+    parseText = do         _ <- A.word8 0x00         utf8string <- A.manyTill A.anyWord8 (A.try $ A.word8 0xff)-        return $ Frame True TextFrame $ BL.pack utf8string+        return $ DataMessage $ Text $ BL.pack utf8string -    decodeCloseFrame = do+    parseClose = do         _ <- A.word8 0xff         _ <- A.word8 0x00-        return $ Frame True CloseFrame ""+        return $ ControlMessage $ Close ""  divBySpaces :: String -> Maybe Int32 divBySpaces str@@ -67,22 +67,11 @@     number = read $ filter isDigit str :: Integer     spaces = fromIntegral . length $ filter (== ' ') str -handshakeHybi00 :: RequestHttpPart-#if MIN_VERSION_attoparsec(0,10,0)-                -> ErrorT HandshakeError (AT.Parser B.ByteString) Request-#else-                -> ErrorT HandshakeError A.Parser Request-#endif+handshakeHybi00 :: Monad m+                => RequestHttpPart+                -> E.Iteratee B.ByteString m Request handshakeHybi00 reqHttp@(RequestHttpPart path h) = do-    -- _ <- lift . A.word8 $ fromIntegral 0x0d-    -- _ <- lift . A.word8 $ fromIntegral 0x0a--    case getHeader "Sec-WebSocket-Version" of-        Left _    -> return ()-        Right "0" -> return ()-        Right _   -> throwError NotSupported--    keyPart3 <- lift $ A.take 8+    keyPart3 <- A.iterParser $ A.take 8     keyPart1 <- numberFromToken =<< getHeader "Sec-WebSocket-Key1"     keyPart2 <- numberFromToken =<< getHeader "Sec-WebSocket-Key2" @@ -102,10 +91,10 @@   where     getHeader k = case lookup k h of         Just t  -> return t-        Nothing -> throwError $ MalformedRequest reqHttp $+        Nothing -> E.throwError $ MalformedRequest reqHttp $             "Header missing: " ++ BC.unpack (CI.original k)      numberFromToken token = case divBySpaces (BC.unpack token) of         Just n  -> return $ encode n-        Nothing -> throwError $ MalformedRequest reqHttp+        Nothing -> E.throwError $ MalformedRequest reqHttp             "Security token does not contain enough spaces"
src/Network/WebSockets/Protocol/Hybi10.hs view
@@ -3,18 +3,22 @@     ( Hybi10     ) where +import Data.Enumerator ((=$))+import Data.Enumerator.List as EL import Network.WebSockets.Protocol import Network.WebSockets.Protocol.Hybi10.Internal+import Network.WebSockets.Protocol.Unsafe  data Hybi10 = forall p. Protocol p => Hybi10 p  instance Protocol Hybi10 where-    version        (Hybi10 p) = version p-    headerVersions (Hybi10 p) = headerVersions p-    encodeFrame    (Hybi10 p) = encodeFrame p-    decodeFrame    (Hybi10 p) = decodeFrame p-    finishRequest  (Hybi10 p) = finishRequest p-    implementations           = [Hybi10 Hybi10_]+    version        (Hybi10 p)   = version p+    headerVersions (Hybi10 p)   = headerVersions p+    supported      (Hybi10 p) h = supported p h+    encodeMessages (Hybi10 p) g = (EL.map castMessage =$) . encodeMessages p g+    decodeMessages (Hybi10 p)   = (decodeMessages p =$) . EL.map castMessage+    finishRequest  (Hybi10 p)   = finishRequest p+    implementations             = [Hybi10 Hybi10_]  instance TextProtocol Hybi10 instance BinaryProtocol Hybi10
+ src/Network/WebSockets/Protocol/Hybi10/Demultiplex.hs view
@@ -0,0 +1,74 @@+-- | Demultiplexing of frames into messages+module Network.WebSockets.Protocol.Hybi10.Demultiplex+    ( FrameType (..)+    , Frame (..)+    , DemultiplexState+    , emptyDemultiplexState+    , demultiplex+    ) where++import Blaze.ByteString.Builder (Builder)+import Data.Monoid (mappend)+import qualified Blaze.ByteString.Builder as B+import qualified Data.ByteString.Lazy as BL++import Network.WebSockets.Types++-- | A low-level representation of a WebSocket packet+data Frame = Frame+    { frameFin     :: !Bool+    , frameType    :: !FrameType+    , framePayload :: !BL.ByteString+    } deriving (Eq, Show)++-- | The type of a frame. Not all types are allowed for all protocols.+data FrameType+    = ContinuationFrame+    | TextFrame+    | BinaryFrame+    | CloseFrame+    | PingFrame+    | PongFrame+    deriving (Eq, Show)++-- | Internal state used by the demultiplexer+newtype DemultiplexState = DemultiplexState+    { unDemultiplexState :: Maybe (FrameType, Builder)+    }++emptyDemultiplexState :: DemultiplexState+emptyDemultiplexState = DemultiplexState Nothing++demultiplex :: DemultiplexState+            -> Frame+            -> (Maybe (Message p), DemultiplexState)+demultiplex state (Frame fin tp pl) = case tp of+    -- Return control messages immediately, they have no influence on the state+    CloseFrame  -> (Just (ControlMessage (Close pl)), state)+    PingFrame   -> (Just (ControlMessage (Ping pl)), state)+    PongFrame   -> (Just (ControlMessage (Pong pl)), state)+    -- If we're dealing with a continuation...+    ContinuationFrame -> case unDemultiplexState state of+        -- We received a continuation but we don't have any state. Let's ignore+        -- this fragment...+        Nothing -> (Nothing, DemultiplexState Nothing)+        -- Append the payload to the state+        -- TODO: protect against overflows+        Just (amt, b)+            | not fin   -> (Nothing, DemultiplexState (Just (amt, b')))+            | otherwise -> case amt of+                TextFrame   -> (Just (DataMessage (Text m)), e)+                BinaryFrame -> (Just (DataMessage (Binary m)), e)+                _           -> error "Demultiplex.demultiplex: Internal error"+          where+            b' = b `mappend` plb+            m = B.toLazyByteString b'+    TextFrame+        | fin       -> (Just (DataMessage (Text pl)), e)+        | otherwise -> (Nothing, DemultiplexState (Just (TextFrame, plb)))+    BinaryFrame+        | fin       -> (Just (DataMessage (Binary pl)), e)+        | otherwise -> (Nothing, DemultiplexState (Just (BinaryFrame, plb)))+  where+    e = emptyDemultiplexState+    plb = B.fromLazyByteString pl
src/Network/WebSockets/Protocol/Hybi10/Internal.hs view
@@ -1,30 +1,36 @@ {-# LANGUAGE OverloadedStrings #-} module Network.WebSockets.Protocol.Hybi10.Internal     ( Hybi10_ (..)+    , encodeFrameHybi10     ) where  import Control.Applicative (pure, (<$>)) import Data.Bits ((.&.), (.|.))-import Data.Monoid (mempty)+import Data.Maybe (maybeToList)+import Data.Monoid (mempty, mappend, mconcat)+import System.Random (RandomGen)  import Data.Attoparsec (anyWord8) import Data.Binary.Get (runGet, getWord16be, getWord64be) import Data.ByteString (ByteString) import Data.ByteString.Char8 ()+import Data.Digest.Pure.SHA (bytestringDigest, sha1) import Data.Int (Int64)+import Data.Enumerator ((=$)) import qualified Blaze.ByteString.Builder as B import qualified Data.Attoparsec as A-import qualified Data.ByteString.Lazy as BL-import Data.Digest.Pure.SHA (bytestringDigest, sha1)+import qualified Data.Attoparsec.Enumerator as A import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Char8 as BC+import qualified Data.ByteString.Lazy as BL import qualified Data.CaseInsensitive as CI-import Control.Monad.Error (throwError)-import Data.Monoid (mappend, mconcat)+import qualified Data.Enumerator as E+import qualified Data.Enumerator.List as EL  import Network.WebSockets.Handshake.Http-import Network.WebSockets.Mask import Network.WebSockets.Protocol+import Network.WebSockets.Protocol.Hybi10.Demultiplex+import Network.WebSockets.Protocol.Hybi10.Mask import Network.WebSockets.Types  data Hybi10_ = Hybi10_@@ -32,17 +38,64 @@ instance Protocol Hybi10_ where     version         Hybi10_ = "hybi10"     headerVersions  Hybi10_ = ["13", "8", "7"]-    encodeFrame     Hybi10_ = encodeFrameHybi10-    decodeFrame     Hybi10_ = decodeFrameHybi10+    encodeMessages  Hybi10_ = EL.mapAccum encodeMessageHybi10+    decodeMessages  Hybi10_ = decodeMessagesHybi10     finishRequest   Hybi10_ = handshakeHybi10     implementations         = [Hybi10_]  instance TextProtocol Hybi10_ instance BinaryProtocol Hybi10_ +encodeMessageHybi10 :: RandomGen g => g -> Message p -> (g, B.Builder)+encodeMessageHybi10 gen msg = (gen', builder)+  where+    (mask, gen') = randomMask gen+    builder      = encodeFrameHybi10 mask $ case msg of+        (ControlMessage (Close pl)) -> Frame True CloseFrame pl+        (ControlMessage (Ping pl))  -> Frame True PingFrame pl+        (ControlMessage (Pong pl))  -> Frame True PongFrame pl+        (DataMessage (Text pl))     -> Frame True TextFrame pl+        (DataMessage (Binary pl))   -> Frame True BinaryFrame pl++-- | Encode a frame+encodeFrameHybi10 :: Mask -> Frame -> B.Builder+encodeFrameHybi10 mask f = B.fromWord8 byte0 `mappend`+    B.fromWord8 byte1 `mappend` len `mappend` maskbytes `mappend`+    B.fromLazyByteString (maskPayload mask (framePayload f))+  where+    byte0  = fin .|. opcode+    fin    = if frameFin f then 0x80 else 0x00+    opcode = case frameType f of+        ContinuationFrame -> 0x00+        TextFrame         -> 0x01+        BinaryFrame       -> 0x02+        CloseFrame        -> 0x08+        PingFrame         -> 0x09+        PongFrame         -> 0x0a++    (maskflag, maskbytes) = case mask of+        Nothing -> (0x00, mempty)+        Just m  -> (0x80, B.fromByteString m)++    byte1 = maskflag .|. lenflag+    len'  = BL.length (framePayload f)+    (lenflag, len)+        | len' < 126     = (fromIntegral len', mempty)+        | len' < 0x10000 = (126, B.fromWord16be (fromIntegral len'))+        | otherwise      = (127, B.fromWord64be (fromIntegral len'))++decodeMessagesHybi10 :: Monad m => E.Enumeratee ByteString (Message p) m a+decodeMessagesHybi10 =+    (E.sequence (A.iterParser parseFrame) =$) . demultiplexEnum++demultiplexEnum :: Monad m => E.Enumeratee Frame (Message p) m a+demultiplexEnum = EL.concatMapAccum step emptyDemultiplexState+  where+    step s f = let (m, s') = demultiplex s f in (s', maybeToList m)+ -- | Parse a frame-decodeFrameHybi10 :: Decoder p Frame-decodeFrameHybi10 = do+parseFrame :: A.Parser Frame+parseFrame = do     byte0 <- anyWord8     let fin = byte0 .&. 0x80 == 0x80         opcode = byte0 .&. 0x0f@@ -73,7 +126,7 @@   where     runGet' g = runGet g . BL.fromChunks . return -    take64 :: Int64 -> Decoder p [ByteString]+    take64 :: Int64 -> A.Parser [ByteString]     take64 n         | n <= 0    = return []         | otherwise = do@@ -84,41 +137,10 @@         intMax :: Int64         intMax = fromIntegral (maxBound :: Int) --- | Encode a frame-encodeFrameHybi10 :: Encoder p Frame-encodeFrameHybi10 mask f = B.fromWord8 byte0 `mappend`-    B.fromWord8 byte1 `mappend` len `mappend` maskbytes `mappend`-    B.fromLazyByteString (maskPayload mask (framePayload f))-  where-    byte0  = fin .|. opcode-    fin    = if frameFin f then 0x80 else 0x00-    opcode = case frameType f of-        ContinuationFrame -> 0x00-        TextFrame         -> 0x01-        BinaryFrame       -> 0x02-        CloseFrame        -> 0x08-        PingFrame         -> 0x09-        PongFrame         -> 0x0a--    (maskflag, maskbytes) = case mask of-        Nothing -> (0x00, mempty)-        Just m  -> (0x80, B.fromByteString m)--    byte1 = maskflag .|. lenflag-    len'  = BL.length (framePayload f)-    (lenflag, len)-        | len' < 126     = (fromIntegral len', mempty)-        | len' < 0x10000 = (126, B.fromWord16be (fromIntegral len'))-        | otherwise      = (127, B.fromWord64be (fromIntegral len'))---handshakeHybi10 :: RequestHttpPart -> Decoder p (Either HandshakeError Request)-handshakeHybi10 reqHttp@(RequestHttpPart path h) = return $ do-    case getHeader "Sec-WebSocket-Version" of-        Right "7"  -> return ()-        Right "8"  -> return ()-        Right "13" -> return ()-        _          -> throwError NotSupported+handshakeHybi10 :: Monad m+                => RequestHttpPart+                -> E.Iteratee ByteString m Request+handshakeHybi10 reqHttp@(RequestHttpPart path h) = do     key <- getHeader "Sec-WebSocket-Key"     let hash = unlazy $ bytestringDigest $ sha1 $ lazy $ key `mappend` guid     let encoded = B64.encode hash@@ -129,6 +151,5 @@     unlazy = mconcat . BL.toChunks     getHeader k = case lookup k h of         Just t  -> return t-        Nothing -> throwError $-                   MalformedRequest reqHttp $ -                   "Header missing: " ++ BC.unpack (CI.original k)+        Nothing -> E.throwError $ MalformedRequest reqHttp $ +            "Header missing: " ++ BC.unpack (CI.original k)
+ src/Network/WebSockets/Protocol/Hybi10/Mask.hs view
@@ -0,0 +1,36 @@+-- | Masking of fragmes using a simple XOR algorithm+{-# LANGUAGE BangPatterns, ScopedTypeVariables #-}+module Network.WebSockets.Protocol.Hybi10.Mask+    ( Mask+    , maskPayload+    , randomMask+    ) where++import Data.Bits (shiftR, xor)+import System.Random (RandomGen, random)++import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL++-- | ByteString should be exactly 4 bytes long+type Mask = Maybe B.ByteString++-- | Apply mask+maskPayload :: Mask -> BL.ByteString -> BL.ByteString+maskPayload Nothing     = id+maskPayload (Just mask) = snd . BL.mapAccumL f 0+  where+    len = B.length mask+    f !i !c = let i' = (i + 1) `mod` len+                  m = mask `B.index` i+              in (i', m `xor` c)++-- | Create a random mask+randomMask :: forall g. RandomGen g => g -> (Mask, g)+randomMask gen = (Just (B.pack [b1, b2, b3, b4]), gen')+  where+    (!int, !gen') = random gen :: (Int, g)+    !b1           = fromIntegral $ int `mod` 0x100+    !b2           = fromIntegral $ int `shiftR` 8  `mod` 0x100+    !b3           = fromIntegral $ int `shiftR` 16 `mod` 0x100+    !b4           = fromIntegral $ int `shiftR` 24 `mod` 0x100
src/Network/WebSockets/Protocol/Unsafe.hs view
@@ -1,5 +1,6 @@ module Network.WebSockets.Protocol.Unsafe-    ( close+    ( castMessage+    , close     , ping     , pong     , textData@@ -7,6 +8,16 @@     ) where  import Network.WebSockets.Types++castMessage :: Message p1 -> Message p2+castMessage (ControlMessage m) = ControlMessage $ case m of+    Close b -> Close b+    Ping b  -> Ping b+    Pong b  -> Pong b+castMessage (DataMessage m)    = DataMessage $ case m of+    Text b   -> Text b+    Binary b -> Binary b+{-# INLINE castMessage #-}  close :: WebSocketsData a => a -> Message p close = ControlMessage . Close . toLazyByteString
src/Network/WebSockets/Socket.hs view
@@ -78,8 +78,8 @@   where     go (E.Chunks []) = E.continue go     go (E.Chunks cs) = do-      b <- liftIO $ S.sIsWritable s-      if b-        then E.tryIO (SB.sendMany s cs) >> E.continue go-        else E.throwError ConnectionClosed-    go E.EOF         = E.yield () E.EOF+        b <- liftIO $ S.sIsWritable s+        if b+            then E.tryIO (SB.sendMany s cs) >> E.continue go+            else E.throwError ConnectionClosed+    go E.EOF         = E.continue go
src/Network/WebSockets/Types.hs view
@@ -3,12 +3,7 @@  -- | Primary types module Network.WebSockets.Types-    ( Decoder-    , Encoder--    , FrameType (..)-    , Frame (..)-    , Message (..)+    ( Message (..)     , ControlMessage (..)     , DataMessage (..)     , WebSocketsData (..)@@ -19,8 +14,6 @@ import Control.Exception (Exception(..)) import Data.Typeable (Typeable) -import Data.Attoparsec (Parser)-import qualified Blaze.ByteString.Builder as B import qualified Data.Attoparsec.Enumerator as AE import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL@@ -28,14 +21,6 @@ import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TL -import Network.WebSockets.Mask---- | An alias so we don't have to import attoparsec everywhere-type Decoder p a = Parser a---- | The inverse of a parser-type Encoder p a = Mask -> a -> B.Builder- -- | The connection couldn't be established or broke down unexpectedly. thrown -- as an iteratee exception. data ConnectionError@@ -49,23 +34,6 @@     deriving (Show, Typeable)  instance Exception ConnectionError---- | A low-level representation of a WebSocket packet-data Frame = Frame-    { frameFin     :: !Bool-    , frameType    :: !FrameType-    , framePayload :: !BL.ByteString-    } deriving (Eq, Show)---- | The type of a frame. Not all types are allowed for all protocols.-data FrameType-    = ContinuationFrame-    | TextFrame-    | BinaryFrame-    | CloseFrame-    | PingFrame-    | PongFrame-    deriving (Eq, Show)  -- | The kind of message a server application typically deals with data Message p
websockets.cabal view
@@ -1,5 +1,5 @@ Name:    websockets-Version: 0.4.1.0+Version: 0.5.0.0  Synopsis:   A sensible and clean way to write WebSocket-capable servers in Haskell.@@ -47,17 +47,16 @@     Network.WebSockets    Other-modules:-    Network.WebSockets.Demultiplex     Network.WebSockets.Handshake     Network.WebSockets.Handshake.Http-    Network.WebSockets.Handshake.ShyIterParser-    Network.WebSockets.Mask     Network.WebSockets.Monad     Network.WebSockets.Protocol     Network.WebSockets.Protocol.Hybi00     Network.WebSockets.Protocol.Hybi00.Internal     Network.WebSockets.Protocol.Hybi10+    Network.WebSockets.Protocol.Hybi10.Demultiplex     Network.WebSockets.Protocol.Hybi10.Internal+    Network.WebSockets.Protocol.Hybi10.Mask     Network.WebSockets.Protocol.Unsafe     Network.WebSockets.Socket     Network.WebSockets.Types