我想使用Servant客户端首先调用登录端点以获取会话cookie,然后针对需要cookie身份验证的端点发出请求。
API是(模拟(
import qualified Servant as SV
import qualified Servant.Auth.Server as AS
import qualified Servant.Client as SC
-- Authentication and X-CSRF cookies
type CookieHeader = ( SV.Headers '[SV.Header "Set-Cookie" AS.SetCookie
, SV.Header "Set-Cookie" AS.SetCookie]
SV.NoContent )
type LoginEndpoint = "login" :> SV.ReqBody '[SV.JSON] Login :> SV.Verb 'SV.POST 204 '[SV.JSON] CookieHeader
type ProtectedEndpoint = "protected" :> SV.Get '[SV.JSON]
-- The overall API
type Api = LoginEndpoint :<|> (AS.Auth '[AS.Cookie, AS.JWT] User :> ProtectedEndpoint)
apiProxy :: Proxy Api
apiProxy = Proxy
我对客户端的定义如下:
loginClient :: Api.Login -> SC.ClientM Api.CookieHeader
protectedClient :: AC.Token -> SC.ClientM Text :<|> SC.ClientM SV.NoContent
loginClient :<|> protectedClient = SC.client Api.apiProxy
客户端如何处理身份验证cookie?我能想到两种方法。在ClientM
monad中执行请求时,如
do
result <- SC.runClientM (loginClient (Login "user" "password")) clientEnv
[..]
其中Login
是登录请求主体,clientEnv
类型为Servant.Client.ClientEnv
,cookie可以是result
的一部分,也可以在clientEnv
内的cookieJar
TVar中更新,或者两者都更新。我假设TVar将被更新,这样具有相同clientEnv
的后续请求将同时发送接收到的cookie。然而,我尝试使用Network.HTTP.Client.destroyCookieJar
读取TVar并检查其内容,结果发现一个空数组。这是有意的吗?我在文件中找不到任何东西。
因此,要进行经过身份验证的调用,我需要从result
中的标头中提取cookie(如何?(,更新TVar,创建一个引用此TVar的新clientEnv
,并使用此新环境进行经过身份认证的调用。这真的是建议的程序吗?我这么问是因为我认为用例是如此标准,应该有一个更精简的解决方案。有吗?我是不是错过了什么?
经过一些实验,我发现Servant客户端确实在cookieJar
中维护了cookie,而CCD_12是clientEnv
的一部分。更准确地说,clientEnv
包含类型为Maybe (TVar CookieJar)
的字段cookieJar
。它是客户端根据后续请求的Set-Cookie
指令更新的TVar。在发出第一个请求之前,由开发人员创建并初始化TVar;否则,Servant客户端将在请求之间丢弃cookie。
此外,可以使用与请求正文相同的方式检索cookie。为此,必须将要检索的cookie定义为API类型的一部分,就像我最初的问题中的例子一样
type LoginEndpoint = "login" :> SV.ReqBody '[SV.JSON] Login :> SV.Verb 'SV.POST 204 '[SV.JSON] CookieHeader
一开始分解返回的有点棘手,因为我需要弄清楚Servant的类型级别机器产生的最终类型。最终,我做了以下事情:
SV.Headers resp h <- tryRequest clientEnv (loginClient (Api.Login "user" "pwd"))
let headers = SV.getHeaders h
其中CCD_ 18是执行CCD_。模式匹配resp
包含返回值(此处为NoContent
(,而h
是不同标头的HList
。可以使用Servant的getHeaders
功能将其转换为Network.HTTP.Types.Header
的常规列表。
然后,可以通过向cookieJar
TVar添加新的标头来更改或生成新的标头,并将其与新请求一起提交(请参阅Network.HTTP.Client中的cookie操作功能(