From 2500312cd5a927a449c4bb2b7b91bc07759d65f5 Mon Sep 17 00:00:00 2001 From: Andrew Wilcox Date: Wed, 20 Feb 2013 14:21:47 +0000 Subject: [PATCH] Allow static resources to be configured as online only. Add a route policy type "static-online" for files in public/ that shouldn't be cached offline. Put "static-online" files in the manifest NETWORK section instead of in the CACHE section. --- app/server/server.js | 4 ++-- packages/appcache/appcache-server.js | 25 ++++++++++++++++---- packages/routepolicy/routepolicy.js | 28 +++++++++++++++++++++-- packages/routepolicy/routepolicy_tests.js | 26 +++++++++++++++------ 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/app/server/server.js b/app/server/server.js index f58d581955..ef8322a18e 100644 --- a/app/server/server.js +++ b/app/server/server.js @@ -157,9 +157,9 @@ var appUrl = function (url) { if (url === '/app.manifest') return false; - // Avoid serving app HTML for declared network routes such as /sockjs/. + // Avoid serving app HTML for declared routes such as /sockjs/. if (__meteor_bootstrap__._routePolicy && - __meteor_bootstrap__._routePolicy.classify(url) === 'network') + __meteor_bootstrap__._routePolicy.classify(url)) return false; // we currently return app HTML on all URLs by default diff --git a/packages/appcache/appcache-server.js b/packages/appcache/appcache-server.js index 7a9400cdc1..98dc7d0a42 100644 --- a/packages/appcache/appcache-server.js +++ b/packages/appcache/appcache-server.js @@ -27,6 +27,11 @@ else if (_.contains(knownBrowsers, option)) { enabledBrowsers[option] = value; } + else if (option === 'onlineOnly') { + _.each(value, function (urlPrefix) { + Meteor._routePolicy.declare(urlPrefix, 'static-online'); + }); + } else { throw new Error('Invalid AppCache config option: ' + option) } @@ -93,7 +98,8 @@ manifest += "CACHE:" + "\n"; manifest += "/" + "\n"; _.each(bundle.manifest, function (resource) { - if (resource.where === 'client') { + if (resource.where === 'client' && + ! Meteor._routePolicy.classify(resource.url)) { manifest += resource.url + "\n"; } }); @@ -107,9 +113,15 @@ // TODO adding the manifest file to NETWORK should be unnecessary? // Want more testing to be sure. manifest += "/app.manifest" + "\n"; - _.each(Meteor._routePolicy.urlPrefixesFor('network'), function (urlPrefix) { - manifest += urlPrefix + "\n"; - }); + _.each( + [].concat( + Meteor._routePolicy.urlPrefixesFor('network'), + Meteor._routePolicy.urlPrefixesFor('static-online') + ), + function (urlPrefix) { + manifest += urlPrefix + "\n"; + } + ); manifest += "*" + "\n"; // content length needs to be based on bytes @@ -139,7 +151,10 @@ "** online as well as making it not cacheable for offline use).\n" + "**\n" + "** To avoid this problem we recommend keeping the size of your static\n" + - "** application assets under 5MB." + "** application assets under 5MB.\n" + + "**\n" + + "** If you have some larger assets that you'd like to make online only,\n" + + "** you can do that with the AppCache "onlineOnly" config option." ); } }; diff --git a/packages/routepolicy/routepolicy.js b/packages/routepolicy/routepolicy.js index ce27701568..72300fa479 100644 --- a/packages/routepolicy/routepolicy.js +++ b/packages/routepolicy/routepolicy.js @@ -1,3 +1,25 @@ +// In addition to listing specific files to be cached, the browser +// application cache manifest allows URLs to be designated as NETWORK +// (always fetched from the Internet) and FALLBACK (which we use to +// serve app HTML on arbitrary URLs). +// +// The limitation of the manifest file format is that the designations +// are by prefix only: if "/foo" is declared NETWORK then "/foobar" +// will also be treated as a network route. +// +// Meteor._routePolicy is a low-level API for declaring the route type +// of URL prefixes: +// +// "network": for network routes that should not conflict with static +// resources. (For example, if "/sockjs/" is a network route, we +// shouldn't have "/sockjs/red-sock.jpg" as a static resource). +// +// "static-online": for static resources which should not be cached in +// the app cache. This is implemented by also adding them to the +// NETWORK section (as otherwise the browser would receive app HTML +// for them because of the FALLBACK section), but static-online routes +// don't need to be checked for conflict with static resources. + (function () { // The route policy is a singleton in a running application, but we @@ -17,8 +39,8 @@ }, checkType: function (type) { - if (! _.contains(['network'], type)) - return 'the route type must be "network"'; + if (! _.contains(['network', 'static-online'], type)) + return 'the route type must be "network" or "static-online"'; return null; }, @@ -35,6 +57,8 @@ checkForConflictWithStatic: function (urlPrefix, type, _testManifest) { var self = this; + if (type === 'static-online') + return null; var manifest = _testManifest || __meteor_bootstrap__.bundle.manifest; var conflict = _.find(manifest, function (resource) { return (resource.type === 'static' && diff --git a/packages/routepolicy/routepolicy_tests.js b/packages/routepolicy/routepolicy_tests.js index 51c38eaf59..17db8f1e2f 100644 --- a/packages/routepolicy/routepolicy_tests.js +++ b/packages/routepolicy/routepolicy_tests.js @@ -2,9 +2,8 @@ Tinytest.add("routepolicy", function (test) { var policy = new Meteor.__RoutePolicyConstructor(); policy.declare('/sockjs/', 'network'); - // App routes might look like this... - // policy.declare('/posts/', 'app'); - // policy.declare('/about', 'app'); + policy.declare('/bigphoto.jpg', 'static-online'); + policy.declare('/anotherphoto.png', 'static-online'); test.equal(policy.classify('/'), null); test.equal(policy.classify('/foo'), null); @@ -13,11 +12,14 @@ Tinytest.add("routepolicy", function (test) { test.equal(policy.classify('/sockjs/'), 'network'); test.equal(policy.classify('/sockjs/foo'), 'network'); - // test.equal(policy.classify('/posts/'), 'app'); - // test.equal(policy.classify('/posts/1234'), 'app'); + test.equal(policy.classify('/bigphoto.jpg'), 'static-online'); + test.equal(policy.classify('/bigphoto.jpg.orig'), 'static-online'); test.equal(policy.urlPrefixesFor('network'), ['/sockjs/']); - // test.equal(policy.urlPrefixesFor('app'), ['/about', '/posts/']); + test.equal( + policy.urlPrefixesFor('static-online'), + ['/anotherphoto.png', '/bigphoto.jpg'] + ); }); Tinytest.add("routepolicy - static conflicts", function (test) { @@ -26,9 +28,14 @@ Tinytest.add("routepolicy - static conflicts", function (test) { "path": "static/sockjs/socks-are-comfy.jpg", "type": "static", "where": "client", - "cacheable": false, "url": "/sockjs/socks-are-comfy.jpg" }, + { + "path": "static/bigphoto.jpg", + "type": "static", + "where": "client", + "url": "/bigphoto.jpg" + } ]; var policy = new Meteor.__RoutePolicyConstructor(); @@ -36,4 +43,9 @@ Tinytest.add("routepolicy - static conflicts", function (test) { policy.checkForConflictWithStatic('/sockjs/', 'network', manifest), "static resource /sockjs/socks-are-comfy.jpg conflicts with network route /sockjs/" ); + + test.equal( + policy.checkForConflictWithStatic('/bigphoto.jpg', 'static-online', manifest), + null + ); });