Issue #83

从上传到访问:OSS 文件的响应头如何影响浏览器行为AI 主写

近 30 天12 次浏览12 位访客统计截至 2026-10-19 · GA4

业务系统生成 PDF 后,需要将它上传到 OSS,再向前端提供访问 URL。前端拿到 URL,并不意味着浏览器一定会直接预览 PDF。

用户最终看到在线预览、文件下载还是跨域错误,取决于上传时保存的 Metadata、URL 使用的访问域名、OSS 返回的响应头,以及前端怎样使用这个 URL。

本文按照实际链路,梳理从上传 PDF 到浏览器预览的流程,以及其中遇到的几个问题。

1. 上传 PDF

上传文件前,服务端需要确定 Bucket、Object Key、访问凭证和文件内容。

下面使用 OSS Node.js SDK 上传一份 PDF,并在上传请求中设置与浏览器预览有关的请求头:

OSS Node.js SDK 官方文档

const OSS = require("ali-oss");
const path = require("path");

async function uploadPdf() {
  const client = new OSS({
    region: "oss-<region-id>",
    accessKeyId: process.env.OSS_ACCESS_KEY_ID,
    accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
    authorizationV4: true,
    bucket: "example-bucket",
  });

  const headers = {
    "Content-Type": "application/pdf",
    "Content-Disposition": "inline; filename=\"document.pdf\"",
    "x-oss-object-acl": "private",
    "x-oss-forbid-overwrite": "true",
  };

  const objectKey = "files/document.pdf";
  const localFilePath = path.normalize("document.pdf");

  const result = await client.put(objectKey, localFilePath, { headers });
  console.log("文件上传完成", result);
}

uploadPdf().catch(console.error);

Content-Type: application/pdf 告诉客户端文件内容是 PDF。Content-Disposition: inline 表示这份内容适合在浏览器中展示,filename 则提供建议文件名。

PutObject 官方文档列出了上传时可以指定的请求头:

Pasted image 20260720102245

在 PutObject 请求中设置的 Content-Type、Content-Disposition 等字段,会被 OSS 保存为 Object Metadata。以后读取 Object 时,相应值会成为 HTTP 响应头。不过Object Metadata 不是浏览器最终响应的唯一来源。

OSS Metadata 到 HTTP 响应头

2. 获取访问 URL

公共读 Bucket 中的 Object 可以通过公开 URL 访问。私有 Bucket 则要求请求携带有效签名,通常由服务端生成有时效的预签名 URL。

使用预签名 URL 下载(Node.js SDK)

const OSS = require("ali-oss");

async function generateSignatureUrl(fileName) {
  const client = new OSS({
    endpoint: "https://files.example.com", // 可以指定自定义域名
    accessKeyId: process.env.OSS_ACCESS_KEY_ID,
    accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
    bucket: "example-bucket",
    region: "oss-<region-id>",
    authorizationV4: true,
    cname: true,
  });

  return client.signatureUrlV4(
    "GET",
    3600,
    { headers: {} },
    fileName,
  );
}

generateSignatureUrl("files/document.pdf")
  .then((url) => console.log("Generated URL:", url))
  .catch(console.error);

预签名 URL 解决的是私有 Object 的临时访问权限,不决定文件应该预览还是下载。浏览器最终怎样处理 PDF,仍取决于访问域名和最终响应头。

3. 访问域名如何改变最终响应

同一份 Object 可以通过 OSS 默认域名或已经绑定到 Bucket 的自定义域名访问。两类域名可能得到不同的最终响应。

3.1. OSS 默认域名可能增加强制下载头

阿里云文件预览排查文档说明了 OSS 默认域名的强制下载规则。

为降低特定文件通过默认域名直接执行的安全风险,命中规则的请求会收到:

x-oss-force-download: true
Content-Disposition: attachment

是否命中强制下载规则,取决于 Bucket、账号、地域和文件类型等条件。OSS 默认域名并不是在所有情况下都会强制下载全部文件,具体范围应以阿里云文档中的规则为准。

如果 Object Metadata 原本保存的是:

Content-Disposition: inline

请求命中默认域名的强制下载策略后,浏览器最终可能收到:

x-oss-force-download: true
Content-Disposition: attachment

这意味着问题不一定出在上传代码。即使上传时保存了 inline,OSS 仍可能在形成最终响应时加入 attachment。

3.2. 自定义域名不会增加默认域名下载头

自定义域名需要先绑定到 Bucket,再通过 CNAME 指向 OSS。它不是简单地把预签名 URL 中的域名替换掉。

阿里云自定义域名文档说明,通过自定义域名访问时,OSS 不会增加默认域名使用的强制下载响应头。

但自定义域名不会主动添加 Content-Disposition: inline。它只是不会触发默认域名的强制下载规则,最终响应仍取决于 Object Metadata、Content-Type 和浏览器支持情况。

4. 浏览器如何处理 PDF 响应

浏览器收到响应后,主要通过 Content-Type 和 Content-Disposition 判断内容是什么,以及应该怎样处理。

4.1. Content-Type 描述内容是什么

Content-Type 描述响应内容的媒体类型:

Content-Type: application/pdf

浏览器据此知道响应内容是 PDF。如果该字段缺失或被设置为 application/octet-stream,浏览器只能将它识别为通用二进制内容,可能无法选择正确的展示方式。

4.2. Content-Disposition 描述怎样处理内容

Content-Disposition 用于说明响应内容应该内联展示,还是作为附件下载。

取值或参数示例含义
inlineContent-Disposition: inline内容可以在页面中展示
attachmentContent-Disposition: attachment内容应该作为附件下载
filenameattachment; filename="document.pdf"建议使用的 ASCII 文件名
filename*attachment; filename*=UTF-8''...使用编码表示非 ASCII 文件名

filename 和 filename* 是附加参数,不是与 inline、attachment 并列的处理方式。

下面两组响应的 Content-Type 相同,但处理语义不同:

Content-Type: application/pdf
Content-Disposition: inline

浏览器支持 PDF 时,通常会直接展示内容。

Content-Type: application/pdf
Content-Disposition: attachment

浏览器知道内容是 PDF,但仍会按照附件语义处理。

4.3. CORS

如果前端通过 fetch 读取不同源的 pdfUrl,浏览器会 CORS 校验:

项目发起跨域 fetch
→ 浏览器发送 Origin
→ OSS 根据 Bucket 的 CORS 规则返回响应头
→ 浏览器检查当前 Origin 是否被允许
→ 通过后 JavaScript 才能读取响应

OSS 的 CORS 规则通常需要关注:

配置作用
Allowed Origin允许哪些页面来源读取 Object
Allowed Methods允许 GET、HEAD 等哪些方法
Allowed Headers允许请求携带哪些请求头
Expose Headers允许 JavaScript 读取哪些响应头

配置入口是:进入阿里云 OSS 控制台的 Bucket 列表,选择目标 Bucket,再打开 数据安全 → 跨域设置,单击 创建规则。

具体参数见阿里云 OSS 跨域设置文档。

5. 选择 PDF 预览方式

前端拿到 PDF URL 后,可以让 JavaScript 读取文件并生成 Blob URL,也可以直接让浏览器加载远程 URL。两种方式经过的链路不同。

5.1. 方式一:JavaScript 读取 PDF,再生成 Blob URL

如果远程 URL 返回 attachment,仍可以先读取文件,再用本地 Blob URL 打开 PDF:

const response = await fetch(pdfUrl);

if (!response.ok) {
  throw new Error(`PDF 请求失败:${response.status}`);
}

const pdfBlob = await response.blob();
const blobUrl = URL.createObjectURL(pdfBlob);
const previewWindow = window.open(blobUrl, "_blank");

if (!previewWindow) {
  URL.revokeObjectURL(blobUrl);
  throw new Error("浏览器阻止了预览窗口");
}

// 预览页面不再使用该 URL 后,再调用 URL.revokeObjectURL(blobUrl)。

这个方案重新构造了一个本地资源,能够避开远程 attachment 对顶层导航的影响,但没有改变远程响应本身。

它还要求 fetch 通过 CORS 校验,并需要等待整份 PDF 被读取。对于大文件,这会增加内存占用,也失去了浏览器直接加载远程 PDF 时可能使用的分段请求能力。

5.1.1. 踩坑:保存文件名退化为 UUID

Blob URL 形如 blob:https://example.com/<uuid>,不会自动继承远程响应中的 Content-Disposition 和文件名。

在本项目使用的浏览器中,打开 Blob URL 后,地址栏、页签和 PDF 查看器使用 UUID。保存文件时,默认文件名同样退化为 UUID,而不是远程响应建议的文件名。

Blob URL 文件名退化为 UUID 示意图

左:Blob URL、页签与 PDF 查看器使用 UUID;右:“另存为”没有获得远程 PDF 的建议文件名。

如果项目需要提供指定文件名的下载操作,可以另外创建带 download 属性的链接:

const downloadLink = document.createElement("a");
downloadLink.href = blobUrl;
downloadLink.download = "document.pdf";
downloadLink.click();

具体默认文件名和扩展名仍可能因浏览器而异。这里记录的是项目实测结果,不将它扩大为所有浏览器的统一行为。

5.2. 方式二:浏览器直接加载远程 PDF

如果远程 URL 返回正确的 Content-Type 和 Content-Disposition,更合适的方式是让浏览器直接处理 PDF:

window.open(pdfUrl, "_blank");

这条路径由浏览器直接请求远程 PDF,业务 JavaScript 不读取响应体,因此不依靠 CORS 完成预览。

配置 CORS 也不会把 Content-Disposition: attachment 改成 inline。如果直接打开 URL 仍然下载,需要检查 Object Metadata、实际访问域名和最终响应头。

6. 总结

OSS PDF 从上传到浏览器处理流程

排查 PDF 无法预览时,可以按同一顺序检查:

  1. 上传时是否保存了正确的 Content-Type 和 Content-Disposition。
  2. 访问的是 OSS 默认域名还是已经绑定的自定义域名。
  3. 浏览器实际收到的响应头是 inline 还是 attachment。
  4. 前端是在直接打开 URL,还是通过 fetch 读取文件。
  5. 如果使用 fetch,Bucket 的 CORS 规则是否允许当前页面来源。

不要用 Blob URL 或 CORS 掩盖响应头配置问题。对于单纯的 PDF 在线预览,优先让远程 URL 返回正确语义,再交给浏览器直接处理,链路更短,也更容易排查。

引用链接

  1. OSS Node.js SDK 官方文档
  2. PutObject 官方文档
  3. 使用预签名 URL 下载(Node.js SDK)
  4. 阿里云文件预览排查文档
  5. 阿里云自定义域名文档
  6. Content-Type
  7. Content-Disposition
  8. 阿里云 OSS 跨域设置文档

讨论

0 条留言

去 GitHub 留言 ↗

还没有留言,欢迎来打个招呼。

回到顶部