仆人客户端如何处理收到的 cookie?

How does a Servant client handle received cookies?

我想使用 Servant 客户端首先调用登录端点以获取 session 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 是登录请求 body 并且类型 Servant.Client.ClientEnv, the cookie could part of the result, it could be updated in the cookieJar TVar inside clientEnv, or both. I would assume the TVar to be updated, so that a subsequent request with the same clientEnv would send the received cookies along. However, my attempt to read the TVar and inspect its contents using Network.HTTP.Client.destroyCookieJarclientEnv 显示了一个空数组。这是故意的吗?我在文档中找不到任何内容。

因此,要进行经过身份验证的调用,我需要从 result 中的 header 中提取 cookie(如何?),更新 TVar,创建一个新的 clientEnv 引用此 TVar,并使用此新环境进行经过身份验证的调用。这确实是建议的程序吗?我问是因为我认为用例非常标准,应该有一个更简化的解决方案。有没有?我错过了什么吗?

经过一些实验,我发现 Servant 客户端确实在属于 clientEnvcookieJar 中维护 cookie。更准确地说,clientEnv 包含字段 cookieJar,其类型为 Maybe (TVar CookieJar)。是client根据后续请求的Set-Cookie指令更新的TVar。由开发人员在发出第一个请求之前创建和初始化该 TVar;否则,Servant 客户端将在请求之间丢弃 cookie。

另外,可以像请求一样获取cookies body。为此,必须将要检索的 cookie 定义为 API 类型的一部分,就像我最初问题的示例:

type LoginEndpoint = "login" :> SV.ReqBody '[SV.JSON] Login :> SV.Verb 'SV.POST 204 '[SV.JSON] CookieHeader

起初拆卸 returned 有点棘手,因为我需要弄清楚 Servant 的 type-level 机器产生的最终类型。最终,我做了以下事情:

SV.Headers resp h <- tryRequest clientEnv (loginClient (Api.Login "user" "pwd"))
let headers = SV.getHeaders h

其中 tryRequest 是执行 runClientM 和提取 Right 部分的助手。模式匹配 resp 包含 return 值(此处为 NoContent),而 h 是不同 header 的 HList。可以用Servant的getHeaders函数转换成Network.HTTP.Types.Header的正则列表

然后可以更改或生成新的 header 并通过向 cookieJar TVar 添加新的 header 来提交新的请求(参见 cookie-manipulating functions 在 Network.HTTP.Client).