PostgREST Vary 响应头解析:缓存代理/CDN 协作与 response.headers GUC 覆盖机制
2026/9/10 14:17:38 网站建设 项目流程

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

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

PostgREST 默认会在每个 HTTP 响应中附带值为Accept, Prefer, RangeVary响应头,用于告知缓存代理与 CDN 哪些请求头会改变响应内容,从而避免缓存串扰。本篇指南以 docs/references/api/vary_header.rst 为主线,讲解该默认头的来源、覆盖方式(response.headersGUC 变量)以及背后的源码实现与注意事项,读完即可在自己的 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.hs 的toWaiResponse中可以看到完整逻辑:

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.rst 与 docs/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 invalidresponse.headerswas 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.headersGUC 提供了无需改代码、纯 SQL 即可完成的定制入口。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询