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
相关产品推荐
相关产品推荐

