You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Servant 0.19中如何在Raw处理函数中执行IO操作?

在Servant 0.19中为Raw端点添加IO操作(及RawM版本对比)

Servant 0.19(无RawM)的实现方式

在Servant 0.19中,Raw端点的处理函数类型是ServerT Raw Handler = Tagged Handler Application——本质是返回一个包裹在Tagged中的WAI Application(Application本身定义为Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived,天然支持IO操作)。

要在处理函数中执行IO(比如读取IORef),有两种常见实现方式:

方式1:每次请求时执行IO

如果需要在每次请求处理时获取最新的IO结果(比如IORef的值可能动态变化),可以自行构造Application,先执行IO操作,再委托文件服务器的Application处理请求:

import Servant.API.Raw (Raw)
import Servant.Server (ServerT, Tagged(..))
import Servant.Server.StaticFiles (defaultFileServerSettings, serveDirectoryWith)
import Network.Wai (Application)
import Data.IORef (IORef, readIORef)

hnd :: Something -> ServerT Raw Handler
hnd Something { somePath } = Tagged $ \req respond -> do
  -- 执行IO操作:读取IORef获取动态路径
  path <- readIORef somePath
  -- 根据路径生成文件服务器配置
  let settings = defaultFileServerSettings path
      -- 取出serveDirectoryWith返回的Application
      (Tagged fileServerApp) = serveDirectoryWith settings
  -- 委托文件服务器处理当前请求
  fileServerApp req respond

方式2:仅初始化时执行IO

如果IO操作的结果不会变化(比如仅在服务器启动时读取一次路径),可以提前执行IO生成Application,再返回Tagged包裹的结果:

import Servant.API.Raw (Raw)
import Servant.Server (ServerT, Tagged(..))
import Servant.Server.StaticFiles (defaultFileServerSettings, serveDirectoryWith)
import Data.IORef (IORef, readIORef)

-- 在IO monad中完成初始化操作,生成处理函数
mkHnd :: Something -> IO (ServerT Raw Handler)
mkHnd Something { somePath } = do
  path <- readIORef somePath
  let settings = defaultFileServerSettings path
  pure $ serveDirectoryWith settings

-- 服务器启动时调用mkHnd,将结果转换为Handler兼容的类型
hnd :: Something -> ServerT Raw Handler
hnd something = unsafePerformIO $ mkHnd something
-- 注意:unsafePerformIO仅在初始化场景下安全,若需更优雅实现可结合依赖注入或ReaderT

使用RawM的实现方式(Servant 0.20+)

Servant 0.20引入了RawM类型,它简化了Raw端点的monad支持,处理函数直接返回m Application(无需Tagged包裹),可以直接在Handler monad中执行IO:

1. 修改API类型

将原来的"some" :> Raw替换为:

import Servant.API.Raw (RawM)

type API = ... :<|> "some" :> RawM

2. 实现处理函数

直接在Handler monad中执行IO,再返回文件服务器的Application:

import Servant.API.Raw (RawM)
import Servant.Server (ServerT)
import Servant.Server.StaticFiles (defaultFileServerSettings, serveDirectoryWith)
import Data.IORef (IORef, readIORef)
import Control.Monad.IO.Class (liftIO)

hnd :: Something -> ServerT RawM Handler
hnd Something { somePath } = do
  -- 直接在Handler中用liftIO执行IO操作
  path <- liftIO $ readIORef somePath
  -- 根据路径生成并返回文件服务器Application
  serveDirectoryWith $ defaultFileServerSettings path

RawM的设计更符合Servant的monad风格,无需手动构造Application或处理Tagged类型,代码更简洁直观。


内容的提问来源于stack exchange,提问作者RandomB

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 01:27:11