阿里云 CDN EdgeScript 配置:带 query 的 PDF 删除 Content-Disposition

环境和目标

这次要解决的是一个 PDF 下载头的问题:源站对象自带 Content-Disposition: Attachment,浏览器访问 PDF 时会下载;业务希望在 URL 带 query string 时删除这个响应头,让浏览器直接预览 PDF。

示例环境如下,真实落地时替换成自己的域名和路径:

项目 示例值
CDN 阿里云 CDN
源站 OSS
加速域名 static.example.com
PDF 路径 /pdf/<date>/<file>.pdf
预览 URL /pdf/<date>/<file>.pdf?time=123
下载 URL /pdf/<date>/<file>.pdf

最终目标不是“所有 PDF 都去掉下载头”,而是下面这个精确行为:

带 query:删除 Content-Disposition,浏览器预览
不带 query:保留 Content-Disposition: Attachment,浏览器下载

EdgeScript 配置位置

这张图的关键点是:规则要放在 CDN 节点返回客户端之前执行,而不是放在 OSS 回源响应阶段。

一、先看 CloudFront 是怎么做的

CloudFront 上这类需求通常放在 Behavior 的 Function association 里:

CloudFront
→ Distributions
→ <DISTRIBUTION_ID>
→ Behaviors
→ /pdf/*/*.pdf
→ Edit
→ Function associations
→ Viewer response

Viewer response 的含义是:CloudFront 边缘节点已经拿到响应,准备返回给浏览器之前执行函数。这个阶段修改响应头,不会改源站对象,也不会把修改后的 header 写回缓存对象。

等价逻辑如下:

function handler(event) {
    var request = event.request;
    var response = event.response;
    var querystring = request.querystring || {};

    for (var key in querystring) {
        if (querystring.hasOwnProperty(key)) {
            delete response.headers['content-disposition'];
            break;
        }
    }

    return response;
}

这段逻辑只有一个判断:只要 URL 里有任意 query 参数,就删除 Content-Disposition

二、阿里云 CDN 的第一个坑:不要配到回源响应头

一开始很容易误选下面这个入口:

回源配置
→ 修改入站响应头
→ 删除 Content-Disposition

这个配置看起来也能删除响应头,但它处理的是:

OSS → CDN

也就是源站返回 CDN 的阶段。

问题在于,很多 CDN 配置里 query string 不参与缓存 key。这样带 query 的请求一旦回源,并在回源响应阶段删掉 Content-Disposition,CDN 后续缓存下来的就是“没有 Content-Disposition 的对象”。结果会变成:

带 query:没有 Content-Disposition
不带 query:也没有 Content-Disposition

这和目标不一致。我们要的是“只在返回客户端前改响应头”,不是在回源阶段改缓存对象。

错误位置和正确位置的差异

三、正确方案:EdgeScript 放在 foot 阶段

阿里云 CDN 里更接近 CloudFront Viewer response 的位置是:

域名管理
→ 目标加速域名
→ EdgeScript自定义策略
→ 模拟环境
→ 添加规则

规则建议先加在模拟环境,测试通过后再发布到生产环境。

规则参数:

规则名称:remove_content_disposition_for_pdf_query
执行位置:foot
优先级:10
启用:on

foot 表示在 CDN 标准配置后执行,更适合做“返回客户端前”的响应处理。

规则代码:

if and(req_uri('re:^/pdf/[^/]+/[^/]+[.]pdf$'), req_uri_query_string('re:.+')) {
    del_rsp_header('Content-Disposition')
}

如果你的真实路径是 /app/pdf/<date>/<file>.pdf 这种两层结构,可以把路径正则替换成自己的目录。公开文章里不建议直接贴真实业务路径。

这段脚本做了两件事:

req_uri(...)                 # 限定只匹配目标 PDF 路径
req_uri_query_string('re:.+') # 限定 query string 非空

满足两个条件后,执行:

del_rsp_header('Content-Disposition')

这几个函数都来自阿里云 CDN EdgeScript 官方文档:

  • del_rsp_header:删除响应头
  • req_uri:按 URI 做匹配
  • req_uri_query_string:读取或匹配 query string
  • foot:脚本执行位置之一

四、模拟环境验证

模拟环境里用同一个 PDF 测两条 URL。

带 query:

curl -I 'https://static.example.com/pdf/20260722/demo.pdf?time=123'

预期结果里没有 Content-Disposition

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 108579
X-Cache: HIT TCP_MEM_HIT

不带 query:

curl -I 'https://static.example.com/pdf/20260722/demo.pdf'

预期结果保留下载头:

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: Attachment;filename=demo.pdf
X-Cache: HIT TCP_MEM_HIT

只有这两个结果同时成立,才说明规则真的符合需求。

五、发布生产和回滚点

发布前做三件事:

1. 备份当前 CDN 配置
2. 确认没有旧的回源响应头删除规则
3. EdgeScript 在模拟环境验证通过

如果之前误配过“修改入站响应头 / 回源 HTTP 响应头”,要先删除它,否则它会继续干扰缓存对象。

生产发布后再验证一遍:

# 带 query:没有 Content-Disposition
curl -I 'https://static.example.com/pdf/20260722/demo.pdf?time=123'

# 不带 query:保留 Content-Disposition
curl -I 'https://static.example.com/pdf/20260722/demo.pdf'

回滚也很直接:

EdgeScript自定义策略
→ 生产环境
→ 禁用或删除 remove_content_disposition_for_pdf_query

如果之前刷新过对象缓存,回滚后建议再刷新一次目标 PDF 路径,避免验证时看到旧缓存。

六、这次排查里的判断规则

遇到类似问题时,不要只看“某条响应头有没有了”,还要看它是在哪个阶段被删的。

几个关键判断:

origin response / 回源响应阶段:
可能改变 CDN 缓存对象里的响应头。

viewer response / 客户端响应阶段:
只改这一次返回给浏览器的响应,不污染缓存对象。

query 不参与 cache key:
不同 ?time=xxx 也可能命中同一个 CDN 缓存对象。

如果看到:

Age: 100
X-Cache: HIT TCP_MEM_HIT

说明当前结果很可能来自缓存。验证精确规则时,最好同时测试带 query 和不带 query,并在必要时刷新目标对象缓存。

快速参考

推荐配置:

入口:EdgeScript自定义策略
环境:先模拟环境,后生产环境
执行位置:foot
动作:del_rsp_header('Content-Disposition')
条件:目标 PDF 路径 + query string 非空

EdgeScript 模板:

if and(req_uri('re:^/pdf/[^/]+/[^/]+[.]pdf$'), req_uri_query_string('re:.+')) {
    del_rsp_header('Content-Disposition')
}

验证标准:

带 query:没有 Content-Disposition
不带 query:有 Content-Disposition: Attachment

不要这样配:

回源配置 → 修改入站响应头 → 删除 Content-Disposition

这会在回源阶段改响应头,容易污染缓存对象,最终变成带 query 和不带 query 都可预览。

posted @ 2026-07-22 20:15  Hello_worlds  阅读(4)  评论(0)    收藏  举报