Add Cache-Tag headers

This commit is contained in:
MICHAEL JACKSON 2017-08-12 17:23:40 -07:00
parent 5f2805c2e9
commit db69375e9c
8 changed files with 66 additions and 95 deletions

View File

@ -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)
})
}
})

View File

@ -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('<p>Internal Server Error</p>')
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

View File

@ -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()
}

View File

@ -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}`)
}
}
})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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`)
}
}