-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Pass-through caching demo. #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Service Worker Sample: Pass-through Caching | ||
=== | ||
See https://googlechrome.github.io/samples/service-worker/pass-through-caching/index.html for a live demo. | ||
|
||
Learn more at http://www.chromestatus.com/feature/6561526227927040 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
<!doctype html> | ||
<!-- | ||
Copyright 2014 Google Inc. All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
--> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
|
||
<meta name="description" content="Sample of pass-through caching with Service Workers."> | ||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
|
||
<title>Service Worker Sample: Pass-through Caching</title> | ||
|
||
<!-- Add to homescreen for Chrome on Android --> | ||
<meta name="mobile-web-app-capable" content="yes"> | ||
<link rel="icon" sizes="192x192" href="../../images/touch/chrome-touch-icon-192x192.png"> | ||
|
||
<!-- Add to homescreen for Safari on iOS --> | ||
<!-- TODO: Replace PLACEHOLDER with feature name. --> | ||
<meta name="apple-mobile-web-app-title" content="Service Worker Sample: Pass-through Caching"> | ||
|
||
<meta name="apple-mobile-web-app-capable" content="yes"> | ||
<meta name="apple-mobile-web-app-status-bar-style" content="black"> | ||
<link rel="apple-touch-icon-precomposed" href="../../images/apple-touch-icon-precomposed.png"> | ||
|
||
<!-- Tile icon for Win8 (144x144 + tile color) --> | ||
<meta name="msapplication-TileImage" content="images/touch/ms-touch-icon-144x144-precomposed.png"> | ||
<meta name="msapplication-TileColor" content="#3372DF"> | ||
|
||
<link rel="icon" href="../../images/favicon.ico"> | ||
|
||
<link rel="stylesheet" href="../../styles/main.css"> | ||
</head> | ||
|
||
<body> | ||
<h1>Service Worker Sample: Pass-through Caching</h1> | ||
|
||
<p>Available in <a href="http://www.chromestatus.com/feature/6561526227927040">Chrome 40+</a></p> | ||
|
||
<p> | ||
This sample demonstrates basic Service Worker registration, in conjunction with pass-through | ||
caching. After the Service Worker starts controlling this page, the first time a specific | ||
resource is requested, it's <code>fetch()</code>ed from the network and a copy of the response | ||
is stored in the Service Worker's cache. All subsequent times that the same resource is | ||
requested, it's returned directly from the cache. | ||
</p> | ||
|
||
<p> | ||
<strong>Note:</strong> This is a very aggressive approach to caching, and might not be appropriate | ||
if your web application makes requests for arbitrary URLs as part of its normal operation | ||
(e.g. a RSS client or a news aggregator), as the cache could end up containing large responses | ||
that might not end up ever being accessed. Other approaches, like selectively caching based on | ||
response headers or only caching responses served from a specific domain, might be more | ||
appropriate for those use cases. | ||
</p> | ||
|
||
<p> | ||
Visit <code>chrome://inspect/#service-workers</code> and click on the "inspect" link below | ||
the registered Service Worker to view logging statements for the various actions the | ||
<code><a href="service-worker.js">service-worker.js</a></code> script is performing. | ||
</p> | ||
|
||
<!-- // [START code-block] --> | ||
<div class="output"> | ||
<div id="status"></div> | ||
|
||
<ul id="images" style="display: none"> | ||
<li><img src="http://www.chromium.org/_/rsrc/1302286216006/config/customLogo.gif"></li> | ||
<li><img src="http://www.chromium.org/_/rsrc/1365117468642/chromium-projects/chrome-64.png"></li> | ||
</ul> | ||
</div> | ||
|
||
<script> | ||
function showImages() { | ||
document.querySelector('#images').style.display = 'block'; | ||
} | ||
|
||
if ('serviceWorker' in navigator) { | ||
navigator.serviceWorker.register('./service-worker.js', {scope: './'}).then( | ||
function() { | ||
// Registration was successful. Now, check to see whether the Service Worker is controlling the page. | ||
if (navigator.serviceWorker.controller) { | ||
// If .controller is set, then this page is being actively controlled by the Service Worker. | ||
document.querySelector('#status').textContent = 'The Service Worker is currently handling network operations. ' + | ||
'If you reload the page, the images (and everything else) will be served from the Service Worker\'s cache.'; | ||
|
||
showImages(); | ||
} else { | ||
// If .controller isn't set, then prompt the user to reload the page so that the Service Worker can take | ||
// control. Until that happens, the Service Worker's fetch handler won't be used. | ||
document.querySelector('#status').textContent = 'Please reload this page to allow the Service Worker to handle network operations.'; | ||
} | ||
}, | ||
function(error) { | ||
// Something went wrong during registration. The service-worker.js file | ||
// might be unavailable or contain a syntax error. | ||
document.querySelector('#status').textContent = error; | ||
} | ||
); | ||
} else { | ||
// The current browser doesn't support Service Workers. | ||
var aElement = document.createElement('a'); | ||
aElement.href = 'http://www.chromium.org/blink/serviceworker/service-worker-faq'; | ||
aElement.textContent = 'Service Workers are not supported in the current browser.'; | ||
document.querySelector('#status').appendChild(aElement); | ||
} | ||
</script> | ||
<!-- // [END code-block] --> | ||
|
||
<script> | ||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | ||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); | ||
ga('create', 'UA-53563471-1', 'auto'); | ||
ga('send', 'pageview'); | ||
</script> | ||
<!-- Built with love using Web Starter Kit --> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// This sample illustrates an aggressive approach to caching, in which every valid response is | ||
// cached and every request is first checked against the cache. | ||
// This may not be an appropriate approach if your web application makes requests for | ||
// arbitrary URLs as part of its normal operation (e.g. a RSS client or a news aggregator), | ||
// as the cache could end up containing large responses that might not end up ever being accessed. | ||
// Other approaches, like selectively caching based on response headers or only caching | ||
// responses served from a specific domain, might be more appropriate for those use cases. | ||
|
||
self.addEventListener('fetch', function(event) { | ||
console.log('Handling fetch event for', event.request.url); | ||
|
||
event.respondWith( | ||
caches.open('pass-through-caching-sample').then(function(cache) { | ||
return cache.match(event.request).then(function(response) { | ||
if (response) { | ||
// If there is an entry in the cache for event.request, then response will be defined | ||
// and we can just return it. | ||
console.log(' Found response in cache:', response); | ||
|
||
return response; | ||
} else { | ||
// Otherwise, if there is no entry in the cache for event.request, response will be | ||
// undefined, and we need to fetch() the resource. | ||
console.log(' No response for %s found in cache. About to fetch from network...', event.request.url); | ||
|
||
return fetch(event.request.clone()).then(function(response) { | ||
console.log(' Response for %s from network is: %O', event.request.url, response); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd recommended checking the response was valid AND that you got a response.status == 200. If either of those are broken, then bail. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would checking that the response is valid entail—just a null check (are there any cases in which Additionally, since Chrome returns opaque filtered responses for non-CORS requests, it's possible that That being said, I will at least check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was discussing this point with a colleague and we both ended up agreeing that caching only 200 and 0 responses would be the safest bet for a recommended sample. It will save people a lot of pain should a bad response get cached. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I talked to @slightlyoff about this, and he was (somewhat?) surprised that opaque filtered responses always had a I'll keep caching whenever |
||
|
||
if (response && response.status < 400) { | ||
// This avoids caching responses that we know are errors (i.e. HTTP status code of 4xx or 5xx). | ||
// One limitation is that, for non-CORS requests, we get back a filtered opaque response | ||
// (https://fetch.spec.whatwg.org/#concept-filtered-response-opaque) which will always have a | ||
// .status of 0, regardless of whether the underlying HTTP call was successful. Since we're | ||
// blindly caching those opaque responses, we run the risk of caching a transient error response. | ||
// | ||
// We need to call .clone() on the response object to save a copy of it to the cache. | ||
// (https://fetch.spec.whatwg.org/#dom-request-clone) | ||
cache.put(event.request, response.clone()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Over time the cache could get quite plump. Is it worth noting that storage isn't unlimited and at least mention that you should have some strategy to get rid of old stuff? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, that's very true. What's the limitation on Cache API storage that we're planning on enforcing with Chrome 40? And is that limit enforced based on cache name, service worker registration, or something else? I'd like to include some actual numbers based on the Chrome implementation, and will add the caveat that other browsers or future versions of Chrome might enforce different limits. I'll also add something about how naive pass-through caching is appropriate for web apps that make a limited set of HTTP requests, but web apps that might end up making arbitrary HTTP requests (like, say, an RSS client or news client) shouldn't blindly pass-through cache everything and need to periodically prune their caches if they do cache aggressively. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Although apps that make a limited set of requests should probably be caching them up front in the install step There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've filed #42 to track the fact that this sample and probably a few others will need to address cache maintenance/versioning. I'll make those changes across all the samples in a consistent fashion in a follow-up PR. Agreed that in general, pre-fetching will be a best practice, and #32 will cover more selective caching scenarios. I can envision there being some use cases where blindly caching everything is appropriate, though I'll make it clear in the comments that developers need to clearly understand their use case. |
||
} | ||
|
||
// Return the original response object, which will be used to fulfill the resource request. | ||
return response; | ||
}); | ||
} | ||
}).catch(function(error) { | ||
// This catch() will handle exceptions that arise from the match() or fetch() operations. | ||
// Note that a HTTP error response (e.g. 404) will NOT trigger an exception. | ||
// It will return a normal response object that has the appropriate error code set. | ||
console.error(' Pass-through caching failed:', error); | ||
|
||
throw error; | ||
}); | ||
}) | ||
); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PLACEHOLDER seems to have been removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, thanks for catching that.