PostgREST Vary 响应头解析:缓存代理/CDN 协作与 response.headers GUC 覆盖机制

PostgREST Vary 响应头解析:缓存代理/CDN 协作与 response.headers GUC 覆盖机制

【免费下载链接】postgrest REST API for any Postgres database 【免费下载链接】postgrest 项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

PostgREST 默认会在每个 HTTP 响应中附带值为 Accept, Prefer, RangeVary 响应头,用于告知缓存代理与 CDN 哪些请求头会改变响应内容,从而避免缓存串扰。本篇指南以 docs/references/api/vary_header.rst 为主线,讲解该默认头的来源、覆盖方式(response.headers GUC 变量)以及背后的源码实现与注意事项,读完即可在自己的 PostgREST 部署中正确地定制缓存相关响应头。

为什么 PostgREST 要发送 Vary 头

Vary 是 HTTP 协议中用于协商缓存键(cache key)的标准响应头。它告诉中间缓存(如 CDN、反向代理)以及浏览器缓存:响应的内容会随请求中列出的请求头不同而不同,因此缓存时必须把这些请求头的值一并纳入缓存键计算。

PostgREST 返回的资源表示取决于三个请求头,因此默认在响应中固定输出:

Vary: Accept, Prefer, Range
  • Accept:客户端通过 Accept 协商响应媒体类型(JSON、OpenAPI、CSV 等自定义媒体类型);
  • Prefer:客户端通过 Prefer 请求头控制返回表示(如 Prefer: count=exactPrefer: return=representation 等);
  • Range:客户端通过 Range 请求头做分页范围请求。

PostgREST 官方认为这一组合"should fit most of the bills"(满足大多数场景),即对该三个请求头的任一变体,缓存代理都应区分缓存。默认行为不要求任何配置,开箱即用。

默认 Vary 头的源码实现

默认 Vary 头并非配置项,而是代码内置的固定响应头。在 src/library/PostgREST/App.hstoWaiResponse 中可以看到完整逻辑:

toWaiResponse timing warnMsgs (Response.PgrstResponse st hdrs bod) =
  Wai.responseLBS st (hdrs ++ serverTimingHeaders timing ++ warningHeaders warnMsgs ++ [varyHeader | not $ varyHeaderPresent hdrs]) bod

varyHeader :: HTTP.Header
varyHeader = (hVary, "Accept, Prefer, Range")

varyHeaderPresent :: [HTTP.Header] -> Bool
varyHeaderPresent = any (\(h, _v) -> h == hVary)

这段代码揭示了两个关键实现事实:

  1. 默认 Vary 头是**追加(append)**在响应头列表末尾的,而不是覆盖;
  2. 追加前会先检查响应头列表中是否已存在 VaryvaryHeaderPresent 按头名匹配,值不参与比较)。只要响应中已经存在任意一个 Vary 头,PostgREST 就不再追加默认值

后一点正是原文档所说"available for override"的机制基础——通过 response.headers 设置自定义 Vary 后,内置默认值会自动让位。

用 response.headers GUC 覆盖 Vary 头

PostgREST 暴露了一组用于定制 HTTP 响应的 GUC(Grand Unified Configuration)变量,response.headers 是其中之一。在数据库函数内部,可以通过 set_config 把它设置为一个 JSON 数组,数组中的每个元素是"单键对象",即一个响应头:

-- Override the Vary header to include Accept, Prefer and X-Test-Vary headers
perform set_config('response.headers', '[{"Vary": "Accept, Prefer, X-Test-Vary"}]', true);

执行上述语句后,PostgREST 会原样使用("use provided value verbatim")这个 Vary 值,即响应头变为:

Vary: Accept, Prefer, X-Test-Vary

set_config 的第三个参数传 true 表示 is_local,即该设置只在当前事务内生效,事务结束自动还原——这与 PostgREST 的"每请求一个事务"模型(见 docs/references/transactions.rst)配合良好,适合放在被调用的存储函数中使用。

为什么必须是"数组 + 单键对象"

response.headers 的取值有严格结构约束:必须是单键对象的数组,而不能是单个多键对象。原因在于像 Cache-ControlSet-Cookie 这类头需要重复出现才能携带多个值,而 JSON 对象无法表达重复键。

这一约束在源码中有直接印证。响应头 GUC 的解析器位于 src/library/PostgREST/Response/GucHeader.hs

instance JSON.FromJSON GucHeader where
  parseJSON (JSON.Object o) =
    case KM.toList o of
      [(k, JSON.String s)] -> pure $ GucHeader (CI.mk $ toUtf8 $ K.toText k, toUtf8 s)
      _ -> mzero
  parseJSON _ = mzero

可见每个 JSON 对象必须恰好有一个键值对KM.toList o 解构后是 [(k, s)] 单元素列表),且值必须是字符串;对象为空、含多个键或非字符串值都会解析失败。头名会被转为大小写不敏感(CI,即 CaseInsensitive)的字节串,这也解释了为什么覆盖时 Vary 头名大小写不影响匹配。

事务作用域与典型用法

response.headers 必须在产生该请求响应的同一事务内设置。PostgREST 将每个请求包装在事务中执行,因此最常见的做法是在被调用的数据库函数内部设置:

create or replace function get_items()
returns json as $$
  perform set_config('response.headers', '[{"Vary": "Accept, Prefer, X-Test-Vary"}]', true);
  return (select coalesce(json_agg(row_to_json(t)), '[]') from items t);
$$ language plpgsql;

在数据库端(如 ALTER ROLEpostgrest.confdb-pre-request 钩子)全局设置 response.headers 同样可行,但需注意其作用范围与事务边界,避免对不需要的响应也施加自定义 Vary。

覆盖范围与注意事项

可覆盖的头部范围

response.headers 并不只用于 Vary。如 docs/references/transactions.rst 所述,PostgREST 提供的 Content-TypeLocation 等头部均可通过该 GUC 覆盖。例如向客户端下发缓存指令:

-- tell client to cache response for two days
SELECT set_config('response.headers',
  '[{"Cache-Control": "public"}, {"Cache-Control": "max-age=259200"}]', true);

需要特别注意的是:即使覆盖了 Content-Type,响应体仍会被转换为 JSON,除非配合自定义媒体类型处理(见 docs/references/api/media_type_handlers.rstdocs/how-tos/providing-images-for-img.rst 中的实际用法)。

与缓存语义相关的建议

修改 Vary 头属于"影响缓存键"的操作,应谨慎为之:

  • 扩展 Vary 值(如在默认值上追加 X-Test-Vary)会让缓存为更多请求头变体分别保存副本,可能增大缓存占用,但能保证正确性;
  • 收窄或替换 Vary 值可能造成不同表示的响应被混用,需要确保缓存代理确实了解并区分所依赖的请求头;
  • 默认值 Accept, Prefer, Range 之外,若你的 API 通过其他请求头(如自定义鉴权头、Accept-Profile 等)影响响应内容,应将这些头一并加入 Vary,否则中间缓存可能返回错误表示。

与 CORS 的交互

需要注意的是,PostgREST 的 CORS 策略实现(src/library/PostgREST/Cors.hs)中 corsVaryOrigin 被设置为 False,即 CORS 中间件默认不向 Vary 追加 Origin。若你的部署同时使用 CDN 且按 Origin 区分响应(如 CORS 允许列表配置不同),应在自定义 Vary 中显式考虑 Origin,避免跨域缓存串扰。

错误排查:PGRST111

如果 response.headers 的 JSON 结构不符合"单键对象数组"的约束(例如写成单个对象、多键对象或非字符串值),PostgREST 会返回 500 错误,错误码为 PGRST111("An invalid response.headers was set",见 docs/references/errors.rst)。遇到该错误时,优先检查:

  1. 外层是否为 JSON 数组([...]);
  2. 每个元素是否为单键对象 {"Header-Name": "value"}
  3. 值是否为字符串(JSON 字符串字面量,须转义内部引号)。

小结

PostgREST 对 Vary 头的处理体现了"合理默认 + 显式覆盖"的设计:

  • 默认输出 Vary: Accept, Prefer, Range,由 src/library/PostgREST/App.hs 内置并在检测到已有 Vary 头时自动跳过;
  • 通过事务内的 set_config('response.headers', ...) 可完全接管 Vary 的取值(原样输出);
  • 取值必须为单键对象数组,解析细节见 src/library/PostgREST/Response/GucHeader.hs
  • 非法结构会触发 PGRST111 错误。

对于任何位于 CDN 或反向代理之后的 PostgREST 服务,理解并合理定制 Vary 头是保证缓存正确性的前提,而 response.headers GUC 提供了无需改代码、纯 SQL 即可完成的定制入口。

【免费下载链接】postgrest REST API for any Postgres database 【免费下载链接】postgrest 项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值