Data URL 将字节或文本嵌入类似 URL 的字符串中。它适合少量、不可变资源,但不是 HTTP 资源的通用替代品。是否使用取决于字节大小、缓存策略、安全政策、Origin 行为、构建产物和资源生命周期。

核心要点

  • Data URL 遵循 RFC 2397 的 data:[<media-type>][;base64],<data> 形式。
  • 百分比编码表示文本,Base64 表示字节;两者都不提供机密性和完整性。
  • 内联字节与父文档或样式表绑定,缓存和更新行为不同于外部资源。
  • text/html 和可执行内容可能带来导航与注入风险;应校验 MIME、配置 CSP,并拒绝不可信 Data URL。
  • 不同浏览器和嵌入上下文没有统一安全大小阈值,应在目标设备上测量完整构建产物。
  • 大型、动态、可分享或需要独立缓存的资源应优先使用 blob: 或普通 URL。

语法与编码

text
data:[<media-type>][;base64],<data>

媒体类型可以省略;按照 RFC,省略时默认是 text/plain;charset=US-ASCII。文本最好显式声明字符集。base64 标志只改变解码方式,不是安全算法选择。

text
data:,Hello%2C%20World!
data:text/plain;charset=utf-8,你好
data:image/svg+xml;base64,PHN2ZyB4bWxucz0i...

短文本通常适合百分比编码;Base64 适合任意字节,但在未计入外围文档、压缩、转义和传输前,数据量大约会增加三分之一。不要假定所有资源都有相同结果,应测量实际构建产物。

安全创建 Data URL

浏览器文本与文件示例

javascript
function createTextDataUrl(text, mediaType = 'text/plain;charset=utf-8') {
  if (!/^[-\w.+]+\/[-\w.+]+(?:;[\w-]+=[^;]+)*$/i.test(mediaType)) {
    throw new TypeError('unsupported media type');
  }
  return `data:${mediaType},${encodeURIComponent(text)}`;
}

function readFileAsDataUrl(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(String(reader.result));
    reader.onerror = () => reject(reader.error ?? new Error('read failed'));
    reader.readAsDataURL(file);
  });
}

不要打印完整 Data URL。它可能意外包含私人文档、用户内容或凭据。只把它传给确实需要的组件;如果使用 blob: URL,在生命周期结束时调用撤销方法。

Python 与有界输入

python
from base64 import b64encode
from pathlib import Path
from urllib.parse import quote

def file_data_url(path: str, media_type: str, max_bytes: int = 64 * 1024) -> str:
    data = Path(path).read_bytes()
    if len(data) > max_bytes:
        raise ValueError("asset exceeds the inline budget")
    return f"data:{media_type};base64,{b64encode(data).decode('ascii')}"

def text_data_url(text: str) -> str:
    return f"data:text/plain;charset=utf-8,{quote(text, safe='')}"

媒体类型应来自允许列表或受信文件元数据,不能直接相信不可信文件名。一次性读入超大文件本身也会造成内存风险。

适用场景

Data URL 可能适合:

  • 组件需要的极小、不可变图标;
  • 测试或演示中的有界合成 fixture;
  • 构建工具测量后决定内联的少量 CSS 资源;
  • 受控、非生产流程中的自包含文档。

它通常不适合大型图片、多个页面共享的字体、视频、频繁更新资源、用户上传内容,或需要独立 CDN 缓存与可观测性的资源。HTTP/2 和 HTTP/3 降低了连接开销,但没有消除缓存失效和解析成本。

缓存与性能

外部资源可以拥有独立 URL、缓存键、过期时间、重新验证和 CDN 策略。Data URL 随父文档或样式表变化,不能作为同一个 HTTP 响应独立寻址;它可能改善单个小资源的冷启动,也可能让重复访问和资源更新更昂贵。

应测量实际构建:

  1. 比较压缩后的 HTML/CSS/JS 与外部资源传输体积;
  2. 在代表性设备上测量解析、解码、内存和渲染时间;
  3. 分别测试冷缓存和热缓存;
  4. 计入资源更新频率与发布失效成本;
  5. 检查生产产物中的 Source Map、CSP 和观测能力。

不要把“低于 N KB 就内联”写成通用规则。让构建管线基于版本化配置决定,并审阅最终产物。

安全、Origin 与 CSP

Data URL 不是安全边界。Base64 可逆,百分比编码也不是加密。不要把密钥、个人数据、Bearer Token 或私有配置放进去。

现代浏览器对 data: 文档有特殊 Origin 行为,通常表现为 Opaque Origin。不要依赖 data:text/htmldata:application/javascript 的同源假设。Data URL 还可能绕过用户对普通链接的安全预期,因此应限制不可信输入的 Scheme,并配置明确的 Content Security Policy。

处理不可信内容时:

  • 只允许功能确实需要的 Scheme 和媒体类型;
  • 除非沙箱设计明确允许,否则拒绝 text/html、可执行脚本和活跃 SVG;
  • 存储或渲染前校验大小和编码;
  • 使用 URL 解析与受信允许列表,而不是简单字符串前缀判断;
  • 分别测试导航、iframe、图片、CSS 和 Worker 上下文。

HTML 实体转义不是通用 Sanitizer,CSP 也不会自动让不安全输入变安全。

Data URL 与 Blob URL

属性 data: URL blob: URL
载荷 编码在字符串中 引用浏览器管理的 Blob
适合 极小的不可变数据 较大或动态生成的客户端数据
生命周期 随字符串/文档存在 直到撤销或文档结束
独立 HTTP 缓存 没有 本身没有网络缓存
常见风险 字符串过大、意外泄露 内存未释放、忘记撤销

两种 Scheme 都不能替代访问控制和内容校验。

常见陷阱

  • 假设所有浏览器都有相同的 URL 最大长度;
  • 使用未校验的 image/*text/* 媒体类型;
  • 在每个页面内联大型字体或图片;
  • 把完整 Data URL 写进日志;
  • 允许用户输入选择 data:text/html 或活跃 SVG;
  • 期待 Data URL 像普通、带描述性文件名的图片资源一样被索引;
  • 把 Base64 编码误认为加密、认证或完整性保护。

常见问题

Data URL 和 Blob URL 有什么区别?

Data URL 把载荷编码在字符串中,Blob URL 则引用浏览器管理的 Blob。较大或动态生成的客户端数据通常更适合 Blob URL,但仍要管理生命周期,它也不会自动赋予服务端资源访问权限。

Data URL 有统一大小限制吗?

没有。规范、浏览器、URL 使用方、文档解析器、框架和内存压力都会施加不同限制。应在准确的上下文和目标设备上测试,把内联预算作为应用决策,而不是引用某个浏览器固定数字。

可以内联视频或字体吗?

语法可能接受,但大型媒体和共享字体通常会失去独立缓存并增加文档或样式表成本。除非测量证明有界内联适合,否则使用外部资源或流式/存储方案。

Data URL 可以安全吗?

受限、受信资源可以被安全处理,但编码不会让数据保密或可信。仍需 MIME 与大小允许列表、CSP、Scheme 限制、输入清理和正常授权控制。

延伸阅读

结论

Data URL 是一种表示和嵌入选择。对于经过测量、很小且不可变的资源,在能够接受与父文档绑定时可以使用;其他资源应保留独立缓存、校验、观测和生命周期控制,选择外部 URL 或 Blob 设计。