From db69375e9c944a115e0d542485694e5529e1ddd7 Mon Sep 17 00:00:00 2001 From: MICHAEL JACKSON Date: Sat, 12 Aug 2017 17:23:40 -0700 Subject: [PATCH] Add Cache-Tag headers --- server.js | 14 +++--- server/{index.js => createServer.js} | 67 +++++++++++++--------------- server/middleware/checkBlacklist.js | 2 +- server/middleware/fetchPackage.js | 8 ++-- server/middleware/findFile.js | 6 +-- server/middleware/index.js | 37 --------------- server/middleware/parseURL.js | 4 +- server/middleware/serveFile.js | 23 +++++++--- 8 files changed, 66 insertions(+), 95 deletions(-) rename server/{index.js => createServer.js} (62%) delete mode 100644 server/middleware/index.js diff --git a/server.js b/server.js index 7d06288..74bff78 100644 --- a/server.js +++ b/server.js @@ -1,12 +1,16 @@ -const path = require('path') const throng = require('throng') -const { startServer } = require('./server/index') +const createServer = require('./server/createServer') const port = parseInt(process.env.PORT, 10) || 5000 -const publicDir = path.resolve(__dirname, 'build') throng({ workers: process.env.WEB_CONCURRENCY || 1, - start: (id) => startServer({ id, port, publicDir }), - lifetime: Infinity + lifetime: Infinity, + start: function (id) { + const server = createServer() + + server.listen(port, function () { + console.log('Server #%s listening on port %s, Ctrl+C to stop', id, port) + }) + } }) diff --git a/server/index.js b/server/createServer.js similarity index 62% rename from server/index.js rename to server/createServer.js index 9960b44..0bd98a5 100644 --- a/server/index.js +++ b/server/createServer.js @@ -1,23 +1,34 @@ -/*eslint-disable no-console*/ +const fs = require('fs') +const path = require('path') const http = require('http') const express = require('express') const cors = require('cors') const morgan = require('morgan') -const middleware = require('./middleware') + const { fetchStats } = require('./cloudflare') +const parseURL = require('./middleware/parseURL') +const checkBlacklist = require('./middleware/checkBlacklist') +const fetchPackage = require('./middleware/fetchPackage') +const findFile = require('./middleware/findFile') +const serveFile = require('./middleware/serveFile') -const fs = require('fs') -const path = require('path') +morgan.token('fwd', function (req) { + return req.get('x-forwarded-for').replace(/\s/g, '') +}) -const sendHomePage = (publicDir) => { +function sendHomePage(publicDir) { const html = fs.readFileSync(path.join(publicDir, 'index.html'), 'utf8') - return (req, res, next) => { - fetchStats((error, stats) => { + return function (req, res, next) { + fetchStats(function (error, stats) { if (error) { next(error) } else { - res.set('Cache-Control', 'public, max-age=60') + res.set({ + 'Cache-Control': 'public, max-age=60', + 'Cache-Tag': 'home' + }) + res.send( // Replace the __SERVER_DATA__ token that was added to the // HTML file in the build process (see scripts/build.js). @@ -30,15 +41,13 @@ const sendHomePage = (publicDir) => { } } -const errorHandler = (err, req, res, next) => { - res.status(500).send('

Internal Server Error

') +function errorHandler(err, req, res, next) { + res.status(500).type('text').send('Internal Server Error') console.error(err.stack) next(err) } -morgan.token('fwd', (req) => req.get('x-forwarded-for').replace(/\s/g, '')) - -const createServer = (config) => { +function createServer() { const app = express() app.disable('x-powered-by') @@ -53,20 +62,24 @@ const createServer = (config) => { app.use(errorHandler) app.use(cors()) - app.get('/', sendHomePage(config.publicDir)) + app.get('/', sendHomePage('build')) - app.use(express.static(config.publicDir, { + app.use(express.static('build', { maxAge: '365d' })) - app.use(middleware()) + app.use(parseURL) + app.use(checkBlacklist) + app.use(fetchPackage) + app.use(findFile) + app.use(serveFile) const server = http.createServer(app) // Heroku dynos automatically timeout after 30s. Set our // own timeout here to force sockets to close before that. // https://devcenter.heroku.com/articles/request-timeout - server.setTimeout(25000, (socket) => { + server.setTimeout(25000, function (socket) { const message = `Timeout of 25 seconds exceeded` socket.end([ @@ -83,22 +96,4 @@ const createServer = (config) => { return server } -const defaultServerConfig = { - id: 1, - port: parseInt(process.env.PORT, 10) || 5000, - publicDir: 'public' -} - -const startServer = (serverConfig = {}) => { - const config = Object.assign({}, defaultServerConfig, serverConfig) - const server = createServer(config) - - server.listen(config.port, () => { - console.log('Server #%s listening on port %s, Ctrl+C to stop', config.id, config.port) - }) -} - -module.exports = { - createServer, - startServer -} +module.exports = createServer diff --git a/server/middleware/checkBlacklist.js b/server/middleware/checkBlacklist.js index 892de53..bb90103 100644 --- a/server/middleware/checkBlacklist.js +++ b/server/middleware/checkBlacklist.js @@ -5,7 +5,7 @@ const blacklist = require('../PackageBlacklist').blacklist */ function checkBlacklist(req, res, next) { if (blacklist.includes(req.packageName)) { - res.status(403).send(`Package ${req.packageName} is blacklisted`) + res.status(403).type('text').send(`Package ${req.packageName} is blacklisted`) } else { next() } diff --git a/server/middleware/fetchPackage.js b/server/middleware/fetchPackage.js index ab2732c..5afbf77 100644 --- a/server/middleware/fetchPackage.js +++ b/server/middleware/fetchPackage.js @@ -11,11 +11,11 @@ function fetchPackage(req, res, next) { PackageInfo.get(req.packageName, function (error, packageInfo) { if (error) { console.error(error) - return res.status(500).send(`Cannot get info for package "${req.packageName}"`) + return res.status(500).type('text').send(`Cannot get info for package "${req.packageName}"`) } if (packageInfo == null || packageInfo.versions == null) - return res.status(404).send(`Cannot find package "${req.packageName}"`) + return res.status(404).type('text').send(`Cannot find package "${req.packageName}"`) req.packageInfo = packageInfo @@ -28,7 +28,7 @@ function fetchPackage(req, res, next) { PackageCache.get(req.packageConfig, function (error, outputDir) { if (error) { console.error(error) - res.status(500).send(`Cannot fetch package ${req.packageSpec}`) + res.status(500).type('text').send(`Cannot fetch package ${req.packageSpec}`) } else { req.packageDir = outputDir next() @@ -42,7 +42,7 @@ function fetchPackage(req, res, next) { if (maxVersion) { res.redirect(PackageURL.create(req.packageName, maxVersion, req.filename, req.search)) } else { - res.status(404).send(`Cannot find package ${req.packageSpec}`) + res.status(404).type('text').send(`Cannot find package ${req.packageSpec}`) } } }) diff --git a/server/middleware/findFile.js b/server/middleware/findFile.js index 86318bc..a281311 100644 --- a/server/middleware/findFile.js +++ b/server/middleware/findFile.js @@ -51,7 +51,7 @@ function findFile(req, res, next) { console.error(error) if (file == null) { - res.status(404).send(`Cannot find file "${req.filename}" in package ${req.packageSpec}`) + res.status(404).type('text').send(`Cannot find file "${req.filename}" in package ${req.packageSpec}`) } else if (stats.isDirectory() && req.pathname[req.pathname.length - 1] !== '/') { // Append / to directory URLs. res.redirect(`${req.pathname}/${req.search}`) @@ -71,7 +71,7 @@ function findFile(req, res, next) { if (queryMain) { if (!(queryMain in packageConfig)) - return res.status(404).send(`Cannot find field "${queryMain}" in ${req.packageSpec} package config`) + return res.status(404).type('text').send(`Cannot find field "${queryMain}" in ${req.packageSpec} package config`) mainFilename = packageConfig[queryMain] } else { @@ -93,7 +93,7 @@ function findFile(req, res, next) { console.error(error) if (file == null) { - res.status(404).send(`Cannot find main file "${mainFilename}" in package ${req.packageSpec}`) + res.status(404).type('text').send(`Cannot find main file "${mainFilename}" in package ${req.packageSpec}`) } else { req.file = file.replace(req.packageDir, '') req.stats = stats diff --git a/server/middleware/index.js b/server/middleware/index.js deleted file mode 100644 index 085e4f5..0000000 --- a/server/middleware/index.js +++ /dev/null @@ -1,37 +0,0 @@ -const express = require('express') -const parseURL = require('./parseURL') -const checkBlacklist = require('./checkBlacklist') -const fetchPackage = require('./fetchPackage') -const findFile = require('./findFile') -const serveFile = require('./serveFile') - -/** - * Creates and returns a function that can be used in the "request" - * event of a standard node HTTP server. Supported URL schemes are: - * - * /history@1.12.5/umd/History.min.js (recommended) - * /history@1.12.5 (package.json's main is implied) - * - * Additionally, the following URLs are supported but will return a - * temporary (302) redirect: - * - * /history (redirects to version, latest is implied) - * /history/umd/History.min.js (redirects to version, latest is implied) - * /history@latest/umd/History.min.js (redirects to version) - * /history@^1/umd/History.min.js (redirects to max satisfying version) - */ -function createRequestHandler() { - const app = express.Router() - - app.use( - parseURL, - checkBlacklist, - fetchPackage, - findFile, - serveFile - ) - - return app -} - -module.exports = createRequestHandler diff --git a/server/middleware/parseURL.js b/server/middleware/parseURL.js index 07bebae..a5d054e 100644 --- a/server/middleware/parseURL.js +++ b/server/middleware/parseURL.js @@ -19,12 +19,12 @@ function parseURL(req, res, next) { const url = PackageURL.parse(req.url) if (url == null) - return res.status(403).send(`Invalid URL: ${req.url}`) + return res.status(403).type('text').send(`Invalid URL: ${req.url}`) // Do not allow unrecognized query parameters because // some people use them to bust the cache. if (!queryIsValid(url.query)) - return res.status(403).send(`Invalid query: ${JSON.stringify(url.query)}`) + return res.status(403).type('text').send(`Invalid query: ${JSON.stringify(url.query)}`) req.packageName = url.packageName req.packageVersion = url.packageVersion diff --git a/server/middleware/serveFile.js b/server/middleware/serveFile.js index 7afd7ea..b483c5f 100644 --- a/server/middleware/serveFile.js +++ b/server/middleware/serveFile.js @@ -31,7 +31,7 @@ function sendFile(res, file, stats) { stream.on('error', (error) => { console.error(error) - res.status(500).send('There was an error serving this file') + res.status(500).type('text').send('There was an error serving this file') }) stream.pipe(res) @@ -46,15 +46,21 @@ function serveFile(req, res, next) { Metadata.get(req.packageDir, req.file, req.stats, MaximumDepth, function (error, metadata) { if (error) { console.error(error) - res.status(500).send(`Cannot generate JSON metadata for ${req.packageSpec}${req.filename}`) + res.status(500).type('text').send(`Cannot generate JSON metadata for ${req.packageSpec}${req.filename}`) } else { // Cache metadata for 1 year. - res.set('Cache-Control', 'public, max-age=31536000').send(metadata) + res.set({ + 'Cache-Control': 'public, max-age=31536000', + 'Cache-Tag': 'meta' + }).send(metadata) } }) } else if (req.stats.isFile()) { // Cache files for 1 year. - res.set('Cache-Control', 'public, max-age=31536000') + res.set({ + 'Cache-Control': 'public, max-age=31536000', + 'Cache-Tag': 'file' + }) // TODO: use res.sendFile instead of our own sendFile? sendFile(res, path.join(req.packageDir, req.file), req.stats) @@ -62,14 +68,17 @@ function serveFile(req, res, next) { generateDirectoryIndexHTML(req.packageInfo, req.packageVersion, req.packageDir, req.file, function (error, html) { if (error) { console.error(error) - res.status(500).send(`Cannot generate index page for ${req.packageSpec}${req.filename}`) + res.status(500).type('text').send(`Cannot generate index page for ${req.packageSpec}${req.filename}`) } else { // Cache HTML directory listings for 1 minute. - res.set('Cache-Control', 'public, max-age=60').send(html) + res.set({ + 'Cache-Control': 'public, max-age=60', + 'Cache-Tag': 'index' + }).send(html) } }) } else { - res.status(403).send(`Cannot serve ${req.packageSpec}${req.filename}; it's not a file`) + res.status(403).type('text').send(`Cannot serve ${req.packageSpec}${req.filename}; it's not a file`) } }