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:
- Reads stdin as a Lazy ByteStream
- Splits the ByteStream into lines
- Produces one ServerEvent for all the lines (this feels wrong - there should presumably be multiple events?)
- Builds a WAI Application from the
IO ServerEvent
- Binds the Application to port 1337
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?
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:
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.