如何使用 CacheStorage 导出保存在浏览器中的缓存文件?

How can I export cached files saved in a browser using CacheStorage?

我有一个网站使用 CacheStorage API 通过 Service Worker 保存各种文件。由于我无法控制的原因,许多这些文件已经从它们加载的服务器上丢失了。但是,我刚刚意识到数百个文件已在本地缓存在浏览器中,该浏览器多年来一直访问该站点(幸运的是该站点并未自行正确清除缓存)。我可以使用 chrome 的开发工具预览文件,但是当我单击“下载”时,它会尝试从服务器(不再存在)下载副本,而不是给我本地缓存的版本。

一次性导出这些文件(要记住有几百个文件)的最简单方法是什么?我可以完全访问浏览器 运行 所在的计算机,以及站点/服务工作者 运行 所在的域。它不需要是一个漂亮的解决方案,因为一旦文件恢复,我计划吸取大量教训以防止将来发生类似的事情。

CacheStorage API 可以从普通网页 JavaScript 以及 service worker 访问,因此如果您在访问 window.caches 的服务器上创建网页,你应该能够从缓存中取出东西并做任何你想做的事。一旦你有了 cache.keys() 你就可以遍历它并使用 match() 其中 returns 该请求的响应。然后您可以将它们打印出来进行复制和粘贴(可能不理想),POST 每个都保存到服务器,或类似的。

这是我在 traintimes.org.uk 上的一些普通 JS;仅显示离线页面列表,但如果需要,它大概可以获取实际的缓存条目。

<script>
// Open the page cache
caches.open("pages")
    // Fetch its keys (cached requests)
    .then(cache => cache.keys())
    // We only want the URLs of each request
    .then(reqs => reqs.map(r => r.url))
    // We want most recent one first (reverse is in-place)
    .then(urls => (urls.reverse(), urls))
    // We don't care about the domain name
    .then(urls => urls.map(u => u.replace(/^.*?uk/, '')))
    // We want them to be clickable links
    .then(urls => urls.map(u => [
        '<a href="', u, '">',
        u.replace(/\?cookie=[^;&]*/, ''),
        '</a>'].join("")))
    // We want them to be visible on the page
    .then(urls =>
        document.getElementById('offline-list').innerHTML =
            '<li>' + urls.join('</li><li>') + '</li>'
    );
</script>

添加到 CacheStorage API 的响应存储在磁盘上。例如,chrome on Mac OSX 将它们存储在 ~/Library/Application Support/Google/Chrome/Default/Service Worker/CacheStorage。在该目录中,每个域都有一个目录,在这些目录中,该域使用的每个特定缓存都有单独的目录。这些目录(在两个级别)的名称似乎不是 human-readable,因此您可能需要搜索内容以找到您要查找的特定缓存。

在每个缓存的目录中,每个响应都保存在不同的文件中。这些是二进制文件,包含各种信息,包括请求的 URL(靠近顶部)和 HTTP 响应 headers(接近尾部)。在它们之间,您会找到 HTTP 响应的 body。

提取尸体并将它们保存到其他地方可用的文件的确切逻辑会根据 URL 模式、文件格式等而有所不同。这个 bash 脚本对我有用:

#!/bin/bash

mkdir -p export
for file in *_0
do
    output=`LC_ALL=C sed -nE 's%^.*/music/images/artists/542x305/([^\.]*\.jpg).*%%p;/jpg/q' $file`
    if [ -z "$output" ]
    then
        echo "file $file missing music URL"
        continue
    fi

    if [[ $(LC_ALL=C sed -n '/x-backend-status.*404/,/.*/p' $file) ]]
    then
        echo "$file returned a 404"
        continue
    fi

    path="export/$output"

    cat $file | LC_ALL=C sed -n '/music\/images\/artists/,$p' | LC_ALL=C sed 's%^.*/music/images/artists/542x305/[^\.]*\.jpg%%g' | LC_ALL=C sed -n '/GET.*$/q;p' > $path
    echo "$file -> $path"
done