diff --git a/packages/webapp/.npm/package/npm-shrinkwrap.json b/packages/webapp/.npm/package/npm-shrinkwrap.json index 8b3977779b..887dcb5e04 100644 --- a/packages/webapp/.npm/package/npm-shrinkwrap.json +++ b/packages/webapp/.npm/package/npm-shrinkwrap.json @@ -43,6 +43,23 @@ } } }, + "send": { + "version": "0.1.0", + "dependencies": { + "debug": { + "version": "0.7.2" + }, + "mime": { + "version": "1.2.6" + }, + "fresh": { + "version": "0.1.0" + }, + "range-parser": { + "version": "0.0.4" + } + } + }, "useragent": { "version": "2.0.1" } diff --git a/packages/webapp/package.js b/packages/webapp/package.js index e22134dfc5..351762533a 100644 --- a/packages/webapp/package.js +++ b/packages/webapp/package.js @@ -4,6 +4,7 @@ Package.describe({ }); Npm.depends({connect: "2.7.10", + send: "0.1.0", useragent: "2.0.1"}); Package.on_use(function (api) { diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 0b49d60e60..90094d1261 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -9,6 +9,7 @@ var url = Npm.require("url"); var connect = Npm.require('connect'); var optimist = Npm.require('optimist'); var useragent = Npm.require('useragent'); +var send = Npm.require('send'); // @export WebApp WebApp = {}; @@ -215,26 +216,68 @@ var runWebAppServer = function () { // Auto-compress any json, javascript, or text. app.use(connect.compress()); - if (clientJson.staticCacheable) { - // cacheable files are files that should never change. Typically - // named by their hash (eg meteor bundled js and css files). - // cache them ~forever (1yr) - app.use(connect.static(path.join(clientDir, clientJson.staticCacheable), - {maxAge: 1000 * 60 * 60 * 24 * 365})); - } + var staticFiles = {}; + _.each(clientJson.manifest, function (item) { + if (item.url && item.where === "client") + staticFiles[url.parse(item.url).pathname] = item; + }); - // cache non-cacheable file anyway. This isn't really correct, as - // users can change the files and changes won't propogate - // immediately. However, if we don't cache them, browsers will - // 'flicker' when rerendering images. Eventually we will probably want - // to rewrite URLs of static assets to include a query parameter to - // bust caches. That way we can both get good caching behavior and - // allow users to change assets without delay. - // https://github.com/meteor/meteor/issues/773 - if (clientJson.static) { - app.use(connect.static(path.join(clientDir, clientJson.static), - {maxAge: 1000 * 60 * 60 * 24})); - } + // Serve static files from the manifest. + // This is inspired by the 'static' middleware. + app.use(function (req, res, next) { + if ('GET' != req.method && 'HEAD' != req.method) { + next(); + return; + } + var path = connect.utils.parseUrl(req).pathname; + + try { + path = decodeURIComponent(path); + } catch (e) { + next(); + return; + } + if (!_.has(staticFiles, path)) { + next(); + return; + } + + // We don't need to call pause because, unlike 'static', once we call into + // 'send' and yield to the event loop, we never call another handler with + // 'next'. + + var info = staticFiles[path]; + + // Cacheable files are files that should never change. Typically + // named by their hash (eg meteor bundled js and css files). + // We cache them ~forever (1yr). + // + // We cache non-cacheable files anyway. This isn't really correct, as users + // can change the files and changes won't propagate immediately. However, if + // we don't cache them, browsers will 'flicker' when rerendering + // images. Eventually we will probably want to rewrite URLs of static assets + // to include a query parameter to bust caches. That way we can both get + // good caching behavior and allow users to change assets without delay. + // https://github.com/meteor/meteor/issues/773 + var maxAge = info.cacheable + ? 1000 * 60 * 60 * 24 * 365 + : 1000 * 60 * 60 * 24; + + send(req, path.join(clientDir, info.path)) + .maxage(maxAge) + .hidden(true) // if we specified a dotfile in the manifest, serve it + .on('error', function (err) { + Log.error("Error serving static file " + err); + res.writeHead(500); + res.end(); + }) + .on('directory', function () { + Log.error("Unexpected directory " + info.path); + res.writeHead(500); + res.end(); + }) + .pipe(res); + }); // Packages and apps can add handlers to this via WebApp.connectHandlers. // They are inserted before our default handler. diff --git a/tools/bundler.js b/tools/bundler.js index da0fd0fb57..c7d5fcf439 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -76,20 +76,9 @@ // trigger HTML5 appcache reloads at the right time (if the // 'appcache' package is being used.) // -// - static: a path, relative to program.json, to a directory. If the -// server is too dumb to read 'manifest', it can just serve all of -// the files in this directory (with a relatively short cache -// expiry time.) -// XXX do not use this. It will go away soon. -// -// - static_cacheable: just like 'static' but resources that can be -// cached aggressively (cacheable: true in the manifest) -// XXX do not use this. It will go away soon. -// // Convention: // -// page is 'app.html', static is 'static', and staticCacheable is -// 'static_cacheable'. +// page is 'app.html'. // // // == Format of a program when arch is "native.*" == @@ -914,17 +903,8 @@ _.extend(ClientTarget.prototype, { // Control file builder.writeJson('program.json', { format: "browser-program-pre1", - manifest: manifest, page: 'app.html', - - // XXX the following are for use by 'legacy' (read: current) - // server.js implementations which aren't smart enough to read - // the manifest and instead want all of the resources in a - // directory together so they can just point gzippo at it. we - // should remove this and make the server work from the - // manifest. - static: 'static', - staticCacheable: 'static_cacheable' + manifest: manifest }); return "program.json"; }