如何改造mtl风格的Servant服务实现流式响应避免请求超时
实现方案
1. 调整API定义(二选一即可)
你可以直接在API中使用带自定义monad的SourceT,不需要强制使用SourceIO:
type ServiceAPI = "maintenance" :> StreamGet NewlineFraming PlainText (SourceT m Text)
如果希望API类型保持和IO绑定的SourceIO规范,也可以后续通过monad转换适配,两种方式都支持你的自定义mtl栈。
2. 改造myServer逻辑
你只需要将原来生成完整Text的逻辑拆分为逐块生成的逻辑,通过lift即可将自定义monad中的操作(日志、指标上报、配置读取等)抬到SourceT上下文里使用:
import Conduit (sourceList, yield) -- 如果你用的是conduit实现的SourceT import Servant.API.Stream import Data.Text (splitAt, null) -- 辅助函数:把大Text切成指定大小的块,不需要修改核心业务逻辑可以用这个 chunkText :: Int -> Text -> [Text] chunkText chunkSize t | null t = [] | otherwise = let (chunk, rest) = splitAt chunkSize t in chunk : chunkText chunkSize rest myServer :: MonadIO m => MonadLog m => MonadMetrics m => MonadRandom m => Config -> Client -> ServerT ServiceAPI m -- 对应上面的API定义就是 SourceT m Text myServer cfg client = do -- 可以直接lift调用你自定义monad里的所有操作 lift $ logInfo "开始生成流式响应" -- 方案A:复用原有返回完整Text的逻辑,切分后流式返回 fullText <- lift $ getOriginalFullText cfg client -- 你原来返回m Text的业务逻辑 sourceList $ chunkText 1024 fullText -- 按1KB大小切分返回 -- 方案B:逐块生成不需要预先生成整个内容,性能更好 -- forM_ [1..100] $ \i -> do -- chunk <- lift $ generateSingleChunk cfg client i -- yield chunk -- lift $ incrementMetric "chunk_sent" 1
你担心的MonadIO []错误完全不会出现,因为[Text]只是普通的列表数据,你是把它作为参数传给sourceList转为流,并没有把列表当作monad使用。
3. 适配SourceIO(如果API定义用了SourceIO)
如果你的API定义用了SourceIO Text,只需要在服务注册时通过mapSourceT把自定义monad的运行器套进去即可,假设你有个自定义monad的运行函数runMyMonad :: m a -> IO a:
-- 服务注册时的示例代码 mainServer :: Config -> Client -> Server ServiceAPI mainServer cfg client = mapSourceT runMyMonad (myServer cfg client)
内容的提问来源于stack exchange,提问作者Kiko Fernandez
相关产品推荐
相关产品推荐

