From bc609ca825d984c2c7c0e4ebf410dada4efb8e41 Mon Sep 17 00:00:00 2001 From: MICHAEL JACKSON Date: Wed, 16 Aug 2017 15:07:34 -0700 Subject: [PATCH] Combine fetchPackage and findFile into same middleware --- server/createServer.js | 6 +- server/middleware/fetchFile.js | 157 ++++++++++++++++++++++++++++++ server/middleware/fetchPackage.js | 59 ----------- server/middleware/findFile.js | 106 -------------------- 4 files changed, 159 insertions(+), 169 deletions(-) create mode 100644 server/middleware/fetchFile.js delete mode 100644 server/middleware/fetchPackage.js delete mode 100644 server/middleware/findFile.js diff --git a/server/createServer.js b/server/createServer.js index 4612f13..c2b2d5b 100644 --- a/server/createServer.js +++ b/server/createServer.js @@ -7,8 +7,7 @@ const morgan = require('morgan') const { fetchStats } = require('./cloudflare') const parsePackageURL = require('./middleware/parsePackageURL') -const fetchPackage = require('./middleware/fetchPackage') -const findFile = require('./middleware/findFile') +const fetchFile = require('./middleware/fetchFile') const serveFile = require('./middleware/serveFile') morgan.token('fwd', function (req) { @@ -68,8 +67,7 @@ function createServer() { })) app.use(parsePackageURL) - app.use(fetchPackage) - app.use(findFile) + app.use(fetchFile) app.use(serveFile) const server = http.createServer(app) diff --git a/server/middleware/fetchFile.js b/server/middleware/fetchFile.js new file mode 100644 index 0000000..97adf4f --- /dev/null +++ b/server/middleware/fetchFile.js @@ -0,0 +1,157 @@ +const fs = require('fs') +const path = require('path') +const semver = require('semver') +const PackageCache = require('../PackageCache') +const PackageInfo = require('../PackageInfo') +const PackageURL = require('../PackageURL') + +const FindExtensions = [ '', '.js', '.json' ] + +/** + * Resolves a path like "lib/file" into "lib/file.js" or "lib/file.json" + * depending on which one is available, similar to require('lib/file'). + */ +function findFile(base, useIndex, callback) { + FindExtensions.reduceRight(function (next, ext) { + const file = base + ext + + return function () { + fs.stat(file, function (error, stats) { + if (error) { + if (error.code === 'ENOENT' || error.code === 'ENOTDIR') { + next() + } else { + callback(error) + } + } else if (useIndex && stats.isDirectory()) { + findFile(path.join(file, 'index'), false, function (error, indexFile, indexStats) { + if (error) { + callback(error) + } else if (indexFile) { + callback(null, indexFile, indexStats) + } else { + next() + } + }) + } else { + callback(null, file, stats) + } + }) + } + }, callback)() +} + +/** + * Fetch the file from the registry and get its stats. Redirect if the URL + * does not specify an exact version number or targets a directory with no + * trailing slash. + */ +function fetchFile(req, res, next) { + PackageInfo.get(req.packageName, function (error, packageInfo) { + if (error) { + console.error(error) + return res.status(500).type('text').send(`Cannot get info for package "${req.packageName}"`) + } + + if (packageInfo == null || packageInfo.versions == null) + return res.status(404).type('text').send(`Cannot find package "${req.packageName}"`) + + req.packageInfo = packageInfo + + const { versions, 'dist-tags': tags } = req.packageInfo + + if (req.packageVersion in versions) { + // A valid request for a package we haven't downloaded yet. + req.packageConfig = versions[req.packageVersion] + + PackageCache.get(req.packageConfig, function (error, outputDir) { + if (error) { + console.error(error) + res.status(500).type('text').send(`Cannot fetch package ${req.packageSpec}`) + } else { + req.packageDir = outputDir + + if (req.filename) { + // Based on the URL, figure out which file they want. + const base = path.join(req.packageDir, req.filename) + + findFile(base, false, function (error, file, stats) { + if (error) + console.error(error) + + if (file == null) { + 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.status(301).redirect(`${req.pathname}/${req.search}`) + } else { + req.file = file.replace(req.packageDir, '') + req.stats = stats + next() + } + }) + } else { + // No filename in the URL. Try to figure out which file they want by + // checking package.json's "unpkg", "browser", and "main" fields. + let mainFilename + + const packageConfig = req.packageConfig + const queryMain = req.query.main + + if (queryMain) { + if (!(queryMain in packageConfig)) + return res.status(404).type('text').send(`Cannot find field "${queryMain}" in ${req.packageSpec} package config`) + + mainFilename = packageConfig[queryMain] + } else { + if (typeof packageConfig.unpkg === 'string') { + // The "unpkg" field allows packages to explicitly declare the + // file to serve at the bare URL (see #59). + mainFilename = packageConfig.unpkg + } else if (typeof packageConfig.browser === 'string') { + // Fall back to the "browser" field if declared (only support strings). + mainFilename = packageConfig.browser + } else { + // If there is no main, use "index" (same as npm). + mainFilename = packageConfig.main || 'index' + } + } + + findFile(path.join(req.packageDir, mainFilename), true, function (error, file, stats) { + if (error) + console.error(error) + + if (file == null) { + 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 + next() + } + }) + } + } + }) + } else if (req.packageVersion in tags) { + // Cache tag redirects for 1 minute. + res.set({ + 'Cache-Control': 'public, max-age=60', + 'Cache-Tag': 'redirect' + }).redirect(PackageURL.create(req.packageName, tags[req.packageVersion], req.filename, req.search)) + } else { + const maxVersion = semver.maxSatisfying(Object.keys(versions), req.packageVersion) + + if (maxVersion) { + // Cache semver redirects for 1 minute. + res.set({ + 'Cache-Control': 'public, max-age=60', + 'Cache-Tag': 'redirect' + }).redirect(PackageURL.create(req.packageName, maxVersion, req.filename, req.search)) + } else { + res.status(404).type('text').send(`Cannot find package ${req.packageSpec}`) + } + } + }) +} + +module.exports = fetchFile diff --git a/server/middleware/fetchPackage.js b/server/middleware/fetchPackage.js deleted file mode 100644 index 85999d8..0000000 --- a/server/middleware/fetchPackage.js +++ /dev/null @@ -1,59 +0,0 @@ -const semver = require('semver') -const PackageCache = require('../PackageCache') -const PackageInfo = require('../PackageInfo') -const PackageURL = require('../PackageURL') - -/** - * Fetch the package from the registry and store a local copy on disk. - * Redirect if the URL does not specify an exact version number. - */ -function fetchPackage(req, res, next) { - PackageInfo.get(req.packageName, function (error, packageInfo) { - if (error) { - console.error(error) - return res.status(500).type('text').send(`Cannot get info for package "${req.packageName}"`) - } - - if (packageInfo == null || packageInfo.versions == null) - return res.status(404).type('text').send(`Cannot find package "${req.packageName}"`) - - req.packageInfo = packageInfo - - const { versions, 'dist-tags': tags } = req.packageInfo - - if (req.packageVersion in versions) { - // A valid request for a package we haven't downloaded yet. - req.packageConfig = versions[req.packageVersion] - - PackageCache.get(req.packageConfig, function (error, outputDir) { - if (error) { - console.error(error) - res.status(500).type('text').send(`Cannot fetch package ${req.packageSpec}`) - } else { - req.packageDir = outputDir - next() - } - }) - } else if (req.packageVersion in tags) { - // Cache tag redirects for 1 minute. - res.set({ - 'Cache-Control': 'public, max-age=60', - 'Cache-Tag': 'redirect' - }).redirect(PackageURL.create(req.packageName, tags[req.packageVersion], req.filename, req.search)) - } else { - const maxVersion = semver.maxSatisfying(Object.keys(versions), req.packageVersion) - - if (maxVersion) { - // Cache semver redirects for 1 minute. - res.set({ - 'Cache-Control': 'public, max-age=60', - 'Cache-Tag': 'redirect' - }).redirect(PackageURL.create(req.packageName, maxVersion, req.filename, req.search)) - } else { - res.status(404).type('text').send(`Cannot find package ${req.packageSpec}`) - } - } - }) -} - -module.exports = fetchPackage diff --git a/server/middleware/findFile.js b/server/middleware/findFile.js deleted file mode 100644 index a281311..0000000 --- a/server/middleware/findFile.js +++ /dev/null @@ -1,106 +0,0 @@ -const fs = require('fs') -const path = require('path') - -const ResolveExtensions = [ '', '.js', '.json' ] - -/** - * Resolves a path like "lib/file" into "lib/file.js" or "lib/file.json" - * depending on which one is available, similar to require('lib/file'). - */ -function resolveFile(base, useIndex, callback) { - ResolveExtensions.reduceRight(function (next, ext) { - const file = base + ext - - return function () { - fs.stat(file, function (error, stats) { - if (error) { - if (error.code === 'ENOENT' || error.code === 'ENOTDIR') { - next() - } else { - callback(error) - } - } else if (useIndex && stats.isDirectory()) { - resolveFile(path.join(file, 'index'), false, function (error, indexFile, indexStats) { - if (error) { - callback(error) - } else if (indexFile) { - callback(null, indexFile, indexStats) - } else { - next() - } - }) - } else { - callback(null, file, stats) - } - }) - } - }, callback)() -} - -/** - * Determine which file we're going to serve and get its stats. - * Redirect if the request targets a directory with no trailing slash. - */ -function findFile(req, res, next) { - if (req.filename) { - const base = path.join(req.packageDir, req.filename) - - // Based on the URL, figure out which file they want. - resolveFile(base, false, function (error, file, stats) { - if (error) - console.error(error) - - if (file == null) { - 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}`) - } else { - req.file = file.replace(req.packageDir, '') - req.stats = stats - next() - } - }) - } else { - // No filename in the URL. Try to figure out which file they want by - // checking package.json's "unpkg", "browser", and "main" fields. - let mainFilename - - const packageConfig = req.packageConfig - const queryMain = req.query.main - - if (queryMain) { - if (!(queryMain in packageConfig)) - return res.status(404).type('text').send(`Cannot find field "${queryMain}" in ${req.packageSpec} package config`) - - mainFilename = packageConfig[queryMain] - } else { - if (typeof packageConfig.unpkg === 'string') { - // The "unpkg" field allows packages to explicitly declare the - // file to serve at the bare URL (see #59). - mainFilename = packageConfig.unpkg - } else if (typeof packageConfig.browser === 'string') { - // Fall back to the "browser" field if declared (only support strings). - mainFilename = packageConfig.browser - } else { - // If there is no main, use "index" (same as npm). - mainFilename = packageConfig.main || 'index' - } - } - - resolveFile(path.join(req.packageDir, mainFilename), true, function (error, file, stats) { - if (error) - console.error(error) - - if (file == null) { - 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 - next() - } - }) - } -} - -module.exports = findFile