例如,同一域有 2 条路径:
some_domain.com/first
和 some_domain.com/second
。
这两个路径都返回相同的文件,比如说 -
contents.html
。是否可以以打开 some_domain.com/first
后不会执行 some_domain.com/second
加载的方式配置缓存 - 因此它只会重用为 some_domain.com/first
准备的缓存?
是否应该通过返回一些特殊的假设的
Cache-Key: key_value
标头来在服务器端完成?可以找到的最相关的事情是这个问题:
尽管有查询字符串,是否可以在浏览器中缓存文件?
更新 - 我可以稍微改一下。我正在使用 React 及其 Router。服务器配置为针对所有可能的路径返回
index.html
,然后允许客户端 JS 处理路由。假设,配置了一些缓存来防止服务器返回的所有内容过期。但这里有一个问题。一旦我打开some_domain.com/first
,它就会被缓存,所以下次我访问同一路径时,它不会再次下载。但即使 some_domain.com/second
返回相同的 index.html
,打开 some_domain.com/first
后它也不会被缓存 - 我还需要打开第二个路径(即通过在浏览器中重新加载相应页面)至少一次以使其缓存(虽然这显然毫无意义,因为两条路线返回相同的index.html
)。
我已经设法解决了问题中描述的问题。如果很快,它是由服务工作人员检查每个
event.request.referrer
事件的 "fetch"
来完成的 - 在我的例子中,每次为 SPA 获取根 index.html
时,它总是没有定义。因此,每次没有引用值时,我都会对同一个 /
目的地执行刷新和缓存。
如果您对特定实现感兴趣,这里是 Ktor 完成的一个非常简单的服务器配置示例(这里的关键点是引入
"Last-Modified"
标头来处理缓存):
fun main() {
embeddedServer(Netty) {
install(Compression) {
gzip()
}
install(ConditionalHeaders) // adds proper "Last-Modified" headers according to states of served files
routing {
singlePageApplication {
react("build/dist/js/productionExecutable")
}
}
}.start(wait = true)
}
这是我的 Service Worker 的代码,里面有注释:
// Configs.
/** Key to store HTTP responses. */
const RESPONSE_CACHE = "responses-v1";
// Fetching.
/** An entry point to handle all requests. */
const fetchStrategy = event => {
const destination = getDestinationToBeProcessedByServiceWorker(event);
if (destination) {
event.respondWith(fetchModifiedWithFallbackToCached(destination));
}
};
/**
* Prepares a destination to be processed by the service worker
* or returns `null` if the provided `event` should not be handled by the service worker.
*/
const getDestinationToBeProcessedByServiceWorker = event => {
// No referrer value implies an attempt to fetch the root `index.html`:
// it can have different paths, but to avoid unnecessary requests for each of these paths,
// only `/` will be used to fetch and cache its contents.
if (!event.request.referrer) {
return "/";
}
// All other types of requests will be processed by default browser means (including caching).
return null;
};
/**
* Tries to fetch a `destination` and cache it if it was modified,
* returns its cached version if there are no updates.
*
* It seems inevitable to perform at least one additional network request inside a service worker
* for each response has been already fetched by the browser before the service worker's activation:
* there is no straightforward way to share service worker caches with default browser ones
* or get response data for some static request performed by the browser to fetch a resource.
*/
const fetchModifiedWithFallbackToCached = async destination => {
const cachedResponse = await getCachedResponse(destination);
const request = createRequestWithProperCacheHeader(destination, cachedResponse);
let networkResponse;
try {
networkResponse = await fetch(request);
} catch (error) {
if (!cachedResponse) {
throw error; // there could be some other fallback instead of just propagating the error
}
}
if (await cacheNetworkResponseIfModified(destination, networkResponse)) {
return networkResponse;
}
return cachedResponse;
};
/**
* If there is some `cachedResponse` available,
* creates a request with a header to fetch the `destination` only if it was modified.
*
* Returns just a pure `destination` otherwise.
*/
const createRequestWithProperCacheHeader = (destination, cachedResponse) => {
if (cachedResponse) {
return new Request(destination, {
headers: new Headers({
"If-Modified-Since": cachedResponse.headers.get("Last-Modified")
})
});
} else {
return destination;
}
};
/**
* Caches a provided `networkResponse` only if its status implies that it was modified:
* returns `true` in this case, `false` - otherwise.
*/
const cacheNetworkResponseIfModified = async (destination, networkResponse) => {
if (networkResponse && networkResponse.ok) {
await cacheResponse(destination, networkResponse.clone());
return true;
} else {
return false;
}
};
// Caching.
const getCachedResponse = async destination => {
return await (await openResponseCache()).match(destination);
};
const cacheResponse = async (destination, response) => {
await (await openResponseCache()).put(destination, response);
};
const openResponseCache = async () => await caches.open(RESPONSE_CACHE);
// Service worker's setup.
// Seems like there is no reason to perform any caching during the installation phase,
// because methods like `Cache.add(...)` are still performing fetching under the hood
// and do not allow to avoid one more additional request per each resource expected to be cached
// after the browser has already fetched it.
self.addEventListener("fetch", fetchStrategy);
作为奖励,可以很容易地扩展此 Service Worker 以包含静态资源缓存,从而拥有一种 SPA 离线模式。