如何为servant定义服务器发送事件(SSE)端点。文件似乎不包括这个病例。如果Servant不是为实时用例设计的,那么哪个Haskell服务器框架支持SSE?
krcsximq1#
servant使用WAI,并且您始终可以使用Raw组合子深入了解普通的WAI应用程序和所有为其存在的库。因此,您可以使用wai-extra中的Network.Wai.EventSource来创建Application,这是Raw端点的处理程序类型。例如:
servant
WAI
Raw
wai-extra
Network.Wai.EventSource
Application
type MyApi = "normalapi" :> NormalApi :<|> "sse" :> RawmyServer :: Server MyAPImyServer = normalServer :<|> eventSourceAppChan myChan
type MyApi = "normalapi" :> NormalApi
:<|> "sse" :> Raw
myServer :: Server MyAPI
myServer = normalServer :<|> eventSourceAppChan myChan
字符串
lnxxn5zx2#
感谢user2141650的回答,我设法获得了使用通道的服务器发送事件的working example。解决方案的要点如下。假设我们有一个echo服务器,它只回显消息:
newtype Message = Message { msgText :: Text }
字符串然后我们将定义三个端点,一个用于创建会话,一个用于向会话发送消息,另一个用于使用服务器发送的事件检索会话的消息:
# Create a new sessioncurl -v -XPOST http://localhost:8081/session/new# And subscribe to its eventscurl -v http://localhost:8081/events/0# And from another terminalcurl -v -XPOST http://localhost:8081/session/0/echo\ -H "Content-Type: application/json" -d '{"msgText": "Hello"}'
# Create a new session
curl -v -XPOST http://localhost:8081/session/new
# And subscribe to its events
curl -v http://localhost:8081/events/0
# And from another terminal
curl -v -XPOST http://localhost:8081/session/0/echo\
-H "Content-Type: application/json" -d '{"msgText": "Hello"}'
型现在让我们看看如何实现端点,将给定会话的消息写入通道:
sendH :: SessionId -> Message -> Handler NoContentsendH sid msg = do -- lookupChannel :: Env -> SessionId -> IO (Maybe (Chan ServerEvent)) mCh <- liftIO $ lookupChannel env sid case mCh of Nothing -> throwError err404 Just ch -> do liftIO $ writeChan ch (asServerEvent msg) return NoContent
sendH :: SessionId -> Message -> Handler NoContent
sendH sid msg = do
-- lookupChannel :: Env -> SessionId -> IO (Maybe (Chan ServerEvent))
mCh <- liftIO $ lookupChannel env sid
case mCh of
Nothing ->
throwError err404
Just ch -> do
liftIO $ writeChan ch (asServerEvent msg)
return NoContent
型将Message转换为ServerEvent的函数如下所示:
Message
ServerEvent
import Data.Text.Encoding as TEimport qualified Data.Text.Lazy as TasServerEvent :: Message -> ServerEventasServerEvent msg = ServerEvent { eventName = Just eName , eventId = Nothing , eventData = [msg'] } where eName :: Builder eName = fromByteString "Message arrived" msg' :: Builder msg' = fromByteString $ TE.encodeUtf8 $ T.toStrict $ msgText msg
import Data.Text.Encoding as TE
import qualified Data.Text.Lazy as T
asServerEvent :: Message -> ServerEvent
asServerEvent msg = ServerEvent
{ eventName = Just eName
, eventId = Nothing
, eventData = [msg']
}
where
eName :: Builder
eName = fromByteString "Message arrived"
msg' :: Builder
msg' = fromByteString $ TE.encodeUtf8 $ T.toStrict $ msgText msg
型最后,从服务器检索消息的处理程序可以使用evetSourceAppChan实现,如下所示:
evetSourceAppChan
eventsH sid = Tagged $ \req respond -> do mCh <- lookupChannel env sid case mCh of Nothing -> do let msg = "Could not find session with id: " <> TLE.encodeUtf8 (T.pack (show sid)) respond $ responseLBS status404 [] msg Just ch -> do ch' <- dupChan ch eventSourceAppChan ch req respond
eventsH sid = Tagged $ \req respond -> do
mCh <- lookupChannel env sid
Nothing -> do
let msg = "Could not find session with id: "
<> TLE.encodeUtf8 (T.pack (show sid))
respond $ responseLBS status404 [] msg
ch' <- dupChan ch
eventSourceAppChan ch req respond
型完整的解决方案可以在我的sanbox上找到。希望能帮上忙。
vmpqdwk33#
仆人只需一点样板文件就能很好地处理这个问题。在这种情况下,您需要一个新的内容类型(EventStream)和一个支持类来将类型呈现为SSE格式。
EventStream
{-# LANGUAGE NoImplicitPrelude #-}module Spencer.Web.Rest.ServerSentEvents whereimport RIOimport qualified RIO.ByteString.Lazy as BLimport Servantimport qualified Network.HTTP.Media as M-- imitate the Servant JSON and OctetStream implementationsdata EventStream deriving Typeableinstance Accept EventStream where contentType _ = "text" M.// "event-stream"instance ToSSE a => MimeRender EventStream a where mimeRender _ = toSSE-- imitate the ToJSON type classclass ToSSE a where toSSE :: a -> BL.ByteString-- my custom type with simple SSE renderdata Hello = Helloinstance ToSSE Hello where toSSE _ = "data: hello!\n\n"-- my simple SSE servertype MyApi = "sse" :> StreamGet NoFraming EventStream (SourceIO Hello)myServer :: Server MyAPImyServer = return $ source [Hello, Hello, Hello]
{-# LANGUAGE NoImplicitPrelude #-}
module Spencer.Web.Rest.ServerSentEvents where
import RIO
import qualified RIO.ByteString.Lazy as BL
import Servant
import qualified Network.HTTP.Media as M
-- imitate the Servant JSON and OctetStream implementations
data EventStream deriving Typeable
instance Accept EventStream where
contentType _ = "text" M.// "event-stream"
instance ToSSE a => MimeRender EventStream a where
mimeRender _ = toSSE
-- imitate the ToJSON type class
class ToSSE a where
toSSE :: a -> BL.ByteString
-- my custom type with simple SSE render
data Hello = Hello
instance ToSSE Hello where
toSSE _ = "data: hello!\n\n"
-- my simple SSE server
type MyApi = "sse" :> StreamGet NoFraming EventStream (SourceIO Hello)
myServer = return $ source [Hello, Hello, Hello]
字符串浏览器结果:
data: hello!data: hello!data: hello!
data: hello!
型
bgtovc5b4#
是的,我不确定servant中的服务器发送事件,但更全面的Web框架,如Yesod,支持这一点。查看yesod-eventsource包Yesod有很好的食谱,所以你可以在那里找到很好的example
4条答案
按热度按时间krcsximq1#
servant
使用WAI
,并且您始终可以使用Raw
组合子深入了解普通的WAI
应用程序和所有为其存在的库。因此,您可以使用wai-extra
中的Network.Wai.EventSource
来创建Application
,这是Raw
端点的处理程序类型。例如:字符串
lnxxn5zx2#
感谢user2141650的回答,我设法获得了使用通道的服务器发送事件的working example。
解决方案的要点如下。假设我们有一个echo服务器,它只回显消息:
字符串
然后我们将定义三个端点,一个用于创建会话,一个用于向会话发送消息,另一个用于使用服务器发送的事件检索会话的消息:
型
现在让我们看看如何实现端点,将给定会话的消息写入通道:
型
将
Message
转换为ServerEvent
的函数如下所示:型
最后,从服务器检索消息的处理程序可以使用
evetSourceAppChan
实现,如下所示:型
完整的解决方案可以在我的sanbox上找到。
希望能帮上忙。
vmpqdwk33#
仆人只需一点样板文件就能很好地处理这个问题。在这种情况下,您需要一个新的内容类型(
EventStream
)和一个支持类来将类型呈现为SSE格式。字符串
浏览器结果:
型
bgtovc5b4#
是的,我不确定servant中的服务器发送事件,但更全面的Web框架,如Yesod,支持这一点。
查看yesod-eventsource包
Yesod有很好的食谱,所以你可以在那里找到很好的example