haskellhaskell-waihaskell-warp

Stream stdin to a Wai.EventSource


I would like to stream stdin over an HTTP connection using text/event-stream. The Network.Wai.EventSource thing looks like a good candidate.

I tried using this code:

import Network.Wai
import Network.Wai.EventSource
import Network.Wai.Middleware.AddHeaders
import Network.Wai.Handler.Warp (run)
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as C
import Blaze.ByteString.Builder.ByteString

toEvent :: [L.ByteString] -> ServerEvent
toEvent s = ServerEvent {
    eventName = Nothing,
    eventId = Nothing,
    eventData = map fromLazyByteString s
}

createWaiApp :: IO L.ByteString -> Application
createWaiApp input = eventSourceAppIO $ fmap (toEvent . C.lines) input

main :: IO ()
main = run 1337 $ createWaiApp L.getContents

Which (I think) does:

When I run this (e.g. using ping -c 5 example.com | stack exec test-exe) it doesn't respond until the whole of stdin has been read.

How do I build a Wai application that flushes out the HTTP connection every time it reads a line from stdin?


Solution

  • L.getContents is a single IO action, so only one event will be created.

    Here is an example of eventSourcEventAppIO where multiple events are created:

    import Blaze.ByteString.Builder.Char8 (fromString)
    ...same imports as above...
    
    nextEvent :: IO ServerEvent
    nextEvent = do
      s <- getLine
      let event = if s == ""
                    then CloseEvent
                    else ServerEvent
                         { eventName = Nothing
                         , eventId = Nothing
                         , eventData = [ fromString s ]
                         }
      case event of
        CloseEvent ->     putStrLn "<close event>"
        ServerEvent _ _ _ -> putStrLn "<server event>"
      return event
    
    main :: IO ()
    main = run 1337 $ eventSourceAppIO nextEvent
    

    To test, in one window start up the server and in another run the command curl -v http://localhost:1337. For each line you enter in the server window you will get a data frame from curl. Entering a blank line will close the HTTP connection but the server will remain running allowing you to connect to it again.