过滤与 Servant 中的静态段匹配的请求路径部分
Filter the parts of a Request Path which match against a Static Segment in Servant
假设我是 运行 一个仆人网络服务器,有两个端点,类型如下所示:
type BookAPI =
"books" :> Get '[JSON] (Map Text Text)
:<|> "book" :> Capture "Name" Text :> ReqBody '[JSON] (Text) :> Post '[JSON] (Text)
λ:T.putStrLn $ layout (Proxy :: Proxy BookAPI)
/
├─ book/
│ └─ <capture>/
│ └─•
└─ books/
└─•
我可能想使用 Network.Wai.Middleware.Prometheus's instrumentHandlerValue 之类的东西来生成每次调用此 API 时都会触发的 Prometheus 指标,并将处理程序值设置为请求的路径。
但是,如果我执行以下操作:
prometheusMiddlware = instrumentHandlerValue (T.intercalate "\" . pathInfo)
这很糟糕,因为对book/<Name>
端点的不同请求,例如book/great-expectations
和book/vanity-fair
导致不同的标签,如果书籍数量少,这很好,但如果它非常大,那么这些指标使用的数据量就非常大,要么我的服务崩溃,要么我的监控账单变得非常大。
我非常喜欢一个函数,它接受一个 Servant API 和一个 Wai Request,如果它匹配,return以相同的形式编辑一个段列表对于每个端点。
也就是说,对 /books
的请求会 return Just ["books"]
,对 /book/little-dorrit
的请求会 return Just ["book", "Name"]
,对 /films
会 return Nothing
.
我可以看到你如何通过 Servant.Server.Internal.Router 上 Router'
的模式匹配来写这篇文章,但我不清楚依靠内部包来做到这一点是个好主意。
有没有更好的方法?
pathInfo
function returns all the path segments for a Request
。也许我们可以定义一个类型类,给定一个 Servant API,为段列表生成一个“解析器”,其结果将是列表的格式化版本。
解析器类型可能类似于:
import Data.Text
import Control.Monad.State.Strict
import Control.Applicative
type PathParser = StateT ([Text],[Text]) Maybe ()
其中状态中第一个[Text]
是尚未解析的路径段,第二个是我们目前积累的格式化路径段。
此类型有一个 Alternative
实例,其中失败丢弃状态(基本上是回溯)和 MonadFail
instance that returns mzero
在 do
-块内的模式匹配失败。
类型类:
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Data ( Proxy )
import GHC.TypeLits
class HasPathParser (x :: k) where
pathParser :: Proxy x -> PathParser
Symbol
的实例将路径片段从待处理列表移至已处理列表:
instance KnownSymbol piece => HasPathParser (piece :: Symbol) where
pathParser _ = do
(piece : rest, found) <- get -- we are using MonadFail here
guard (piece == Data.Text.pack (symbolVal (Proxy @piece)))
put (rest, piece : found)
Capture
的实例将路径变量的名称(而不是值)放在已处理的列表中:
instance KnownSymbol name => HasPathParser (Capture name x) where
pathParser _ = do
(_ : rest, found) <- get -- we are using MonadFail here
put (rest, Data.Text.pack (symbolVal (Proxy @name)) : found)
当我们到达 Verb
(GET
、POST
...) 时,我们要求不应保留任何挂起的路径片段:
instance HasPathParser (Verb method statusCode contextTypes a) where
pathParser _ = do
([], found) <- get -- we are using MonadFail here
put ([], found)
其他一些实例:
instance HasPathParser (ReqBody x y) where
pathParser _ = pure ()
instance (HasPathParser a, HasPathParser b) => HasPathParser (a :> b) where
pathParser _ = pathParser (Proxy @a) *> pathParser (Proxy @b)
instance (HasPathParser a, HasPathParser b) => HasPathParser (a :<|> b) where
pathParser _ = pathParser (Proxy @a) <|> pathParser (Proxy @b)
投入使用:
main :: IO ()
main = do
do let Just ([], result) = execStateT (pathParser (Proxy @BookAPI)) (["books"],[])
print result
-- ["books"]
do let Just ([], result) = execStateT (pathParser (Proxy @BookAPI)) (["book", "somebookid"],[])
print result
-- ["Name","book"]
假设我是 运行 一个仆人网络服务器,有两个端点,类型如下所示:
type BookAPI =
"books" :> Get '[JSON] (Map Text Text)
:<|> "book" :> Capture "Name" Text :> ReqBody '[JSON] (Text) :> Post '[JSON] (Text)
λ:T.putStrLn $ layout (Proxy :: Proxy BookAPI)
/
├─ book/
│ └─ <capture>/
│ └─•
└─ books/
└─•
我可能想使用 Network.Wai.Middleware.Prometheus's instrumentHandlerValue 之类的东西来生成每次调用此 API 时都会触发的 Prometheus 指标,并将处理程序值设置为请求的路径。
但是,如果我执行以下操作:
prometheusMiddlware = instrumentHandlerValue (T.intercalate "\" . pathInfo)
这很糟糕,因为对book/<Name>
端点的不同请求,例如book/great-expectations
和book/vanity-fair
导致不同的标签,如果书籍数量少,这很好,但如果它非常大,那么这些指标使用的数据量就非常大,要么我的服务崩溃,要么我的监控账单变得非常大。
我非常喜欢一个函数,它接受一个 Servant API 和一个 Wai Request,如果它匹配,return以相同的形式编辑一个段列表对于每个端点。
也就是说,对 /books
的请求会 return Just ["books"]
,对 /book/little-dorrit
的请求会 return Just ["book", "Name"]
,对 /films
会 return Nothing
.
我可以看到你如何通过 Servant.Server.Internal.Router 上 Router'
的模式匹配来写这篇文章,但我不清楚依靠内部包来做到这一点是个好主意。
有没有更好的方法?
pathInfo
function returns all the path segments for a Request
。也许我们可以定义一个类型类,给定一个 Servant API,为段列表生成一个“解析器”,其结果将是列表的格式化版本。
解析器类型可能类似于:
import Data.Text
import Control.Monad.State.Strict
import Control.Applicative
type PathParser = StateT ([Text],[Text]) Maybe ()
其中状态中第一个[Text]
是尚未解析的路径段,第二个是我们目前积累的格式化路径段。
此类型有一个 Alternative
实例,其中失败丢弃状态(基本上是回溯)和 MonadFail
instance that returns mzero
在 do
-块内的模式匹配失败。
类型类:
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Data ( Proxy )
import GHC.TypeLits
class HasPathParser (x :: k) where
pathParser :: Proxy x -> PathParser
Symbol
的实例将路径片段从待处理列表移至已处理列表:
instance KnownSymbol piece => HasPathParser (piece :: Symbol) where
pathParser _ = do
(piece : rest, found) <- get -- we are using MonadFail here
guard (piece == Data.Text.pack (symbolVal (Proxy @piece)))
put (rest, piece : found)
Capture
的实例将路径变量的名称(而不是值)放在已处理的列表中:
instance KnownSymbol name => HasPathParser (Capture name x) where
pathParser _ = do
(_ : rest, found) <- get -- we are using MonadFail here
put (rest, Data.Text.pack (symbolVal (Proxy @name)) : found)
当我们到达 Verb
(GET
、POST
...) 时,我们要求不应保留任何挂起的路径片段:
instance HasPathParser (Verb method statusCode contextTypes a) where
pathParser _ = do
([], found) <- get -- we are using MonadFail here
put ([], found)
其他一些实例:
instance HasPathParser (ReqBody x y) where
pathParser _ = pure ()
instance (HasPathParser a, HasPathParser b) => HasPathParser (a :> b) where
pathParser _ = pathParser (Proxy @a) *> pathParser (Proxy @b)
instance (HasPathParser a, HasPathParser b) => HasPathParser (a :<|> b) where
pathParser _ = pathParser (Proxy @a) <|> pathParser (Proxy @b)
投入使用:
main :: IO ()
main = do
do let Just ([], result) = execStateT (pathParser (Proxy @BookAPI)) (["books"],[])
print result
-- ["books"]
do let Just ([], result) = execStateT (pathParser (Proxy @BookAPI)) (["book", "somebookid"],[])
print result
-- ["Name","book"]