Better async error handling

This commit is contained in:
Michael Jackson 2019-07-30 17:06:27 -07:00
parent 7582c641fb
commit 7f90203a66
9 changed files with 102 additions and 46 deletions

View File

@ -2,9 +2,10 @@ import { renderToString, renderToStaticMarkup } from 'react-dom/server';
import BrowseApp from '../client/browse/App.js'; import BrowseApp from '../client/browse/App.js';
import MainTemplate from '../templates/MainTemplate.js'; import MainTemplate from '../templates/MainTemplate.js';
import { getAvailableVersions } from '../utils/npm.js'; import asyncHandler from '../utils/asyncHandler.js';
import getScripts from '../utils/getScripts.js'; import getScripts from '../utils/getScripts.js';
import { createElement, createHTML } from '../utils/markup.js'; import { createElement, createHTML } from '../utils/markup.js';
import { getAvailableVersions } from '../utils/npm.js';
const doctype = '<!DOCTYPE html>'; const doctype = '<!DOCTYPE html>';
const globalURLs = const globalURLs =
@ -20,7 +21,7 @@ const globalURLs =
'react-dom': '/react-dom@16.8.6/umd/react-dom.development.js' 'react-dom': '/react-dom@16.8.6/umd/react-dom.development.js'
}; };
export default async function serveBrowsePage(req, res) { async function serveBrowsePage(req, res) {
const availableVersions = await getAvailableVersions(req.packageName); const availableVersions = await getAvailableVersions(req.packageName);
const data = { const data = {
packageName: req.packageName, packageName: req.packageName,
@ -51,3 +52,5 @@ export default async function serveBrowsePage(req, res) {
}) })
.send(html); .send(html);
} }
export default asyncHandler(serveBrowsePage);

View File

@ -2,6 +2,7 @@ import path from 'path';
import gunzip from 'gunzip-maybe'; import gunzip from 'gunzip-maybe';
import tar from 'tar-stream'; import tar from 'tar-stream';
import asyncHandler from '../utils/asyncHandler.js';
import bufferStream from '../utils/bufferStream.js'; import bufferStream from '../utils/bufferStream.js';
import getContentType from '../utils/getContentType.js'; import getContentType from '../utils/getContentType.js';
import getIntegrity from '../utils/getIntegrity.js'; import getIntegrity from '../utils/getIntegrity.js';
@ -45,15 +46,19 @@ async function findMatchingEntries(stream, filename) {
return; return;
} }
const content = await bufferStream(stream); try {
const content = await bufferStream(stream);
entry.contentType = getContentType(entry.path); entry.contentType = getContentType(entry.path);
entry.integrity = getIntegrity(content); entry.integrity = getIntegrity(content);
entry.size = content.length; entry.size = content.length;
entries[entry.path] = entry; entries[entry.path] = entry;
next(); next();
} catch (error) {
next(error);
}
}) })
.on('finish', () => { .on('finish', () => {
accept(entries); accept(entries);
@ -61,7 +66,7 @@ async function findMatchingEntries(stream, filename) {
}); });
} }
export default async function serveDirectoryBrowser(req, res) { async function serveDirectoryBrowser(req, res) {
const stream = await getPackage(req.packageName, req.packageVersion); const stream = await getPackage(req.packageName, req.packageVersion);
const filename = req.filename.slice(0, -1) || '/'; const filename = req.filename.slice(0, -1) || '/';
@ -79,3 +84,5 @@ export default async function serveDirectoryBrowser(req, res) {
serveBrowsePage(req, res); serveBrowsePage(req, res);
} }
export default asyncHandler(serveDirectoryBrowser);

View File

@ -2,6 +2,7 @@ import path from 'path';
import gunzip from 'gunzip-maybe'; import gunzip from 'gunzip-maybe';
import tar from 'tar-stream'; import tar from 'tar-stream';
import asyncHandler from '../utils/asyncHandler.js';
import bufferStream from '../utils/bufferStream.js'; import bufferStream from '../utils/bufferStream.js';
import getContentType from '../utils/getContentType.js'; import getContentType from '../utils/getContentType.js';
import getIntegrity from '../utils/getIntegrity.js'; import getIntegrity from '../utils/getIntegrity.js';
@ -46,16 +47,20 @@ async function findMatchingEntries(stream, filename) {
return; return;
} }
const content = await bufferStream(stream); try {
const content = await bufferStream(stream);
entry.contentType = getContentType(entry.path); entry.contentType = getContentType(entry.path);
entry.integrity = getIntegrity(content); entry.integrity = getIntegrity(content);
entry.lastModified = header.mtime.toUTCString(); entry.lastModified = header.mtime.toUTCString();
entry.size = content.length; entry.size = content.length;
entries[entry.path] = entry; entries[entry.path] = entry;
next(); next();
} catch (error) {
next(error);
}
}) })
.on('finish', () => { .on('finish', () => {
accept(entries); accept(entries);
@ -86,7 +91,7 @@ function getMetadata(entry, entries) {
return metadata; return metadata;
} }
export default async function serveDirectoryMetadata(req, res) { async function serveDirectoryMetadata(req, res) {
const stream = await getPackage(req.packageName, req.packageVersion); const stream = await getPackage(req.packageName, req.packageVersion);
const filename = req.filename.slice(0, -1) || '/'; const filename = req.filename.slice(0, -1) || '/';
@ -95,3 +100,5 @@ export default async function serveDirectoryMetadata(req, res) {
res.send(metadata); res.send(metadata);
} }
export default asyncHandler(serveDirectoryMetadata);

View File

@ -1,6 +1,7 @@
import gunzip from 'gunzip-maybe'; import gunzip from 'gunzip-maybe';
import tar from 'tar-stream'; import tar from 'tar-stream';
import asyncHandler from '../utils/asyncHandler.js';
import bufferStream from '../utils/bufferStream.js'; import bufferStream from '../utils/bufferStream.js';
import createDataURI from '../utils/createDataURI.js'; import createDataURI from '../utils/createDataURI.js';
import getContentType from '../utils/getContentType.js'; import getContentType from '../utils/getContentType.js';
@ -37,10 +38,15 @@ async function findEntry(stream, filename) {
return; return;
} }
entry.content = await bufferStream(stream); try {
foundEntry = entry; entry.content = await bufferStream(stream);
next(); foundEntry = entry;
next();
} catch (error) {
next(error);
}
}) })
.on('finish', () => { .on('finish', () => {
accept(foundEntry); accept(foundEntry);
@ -48,7 +54,7 @@ async function findEntry(stream, filename) {
}); });
} }
export default async function serveFileBrowser(req, res) { async function serveFileBrowser(req, res) {
const stream = await getPackage(req.packageName, req.packageVersion); const stream = await getPackage(req.packageName, req.packageVersion);
const entry = await findEntry(stream, req.filename); const entry = await findEntry(stream, req.filename);
@ -82,3 +88,5 @@ export default async function serveFileBrowser(req, res) {
serveBrowsePage(req, res); serveBrowsePage(req, res);
} }
export default asyncHandler(serveFileBrowser);

View File

@ -1,6 +1,7 @@
import gunzip from 'gunzip-maybe'; import gunzip from 'gunzip-maybe';
import tar from 'tar-stream'; import tar from 'tar-stream';
import asyncHandler from '../utils/asyncHandler.js';
import bufferStream from '../utils/bufferStream.js'; import bufferStream from '../utils/bufferStream.js';
import getContentType from '../utils/getContentType.js'; import getContentType from '../utils/getContentType.js';
import getIntegrity from '../utils/getIntegrity.js'; import getIntegrity from '../utils/getIntegrity.js';
@ -32,16 +33,20 @@ async function findEntry(stream, filename) {
return; return;
} }
const content = await bufferStream(stream); try {
const content = await bufferStream(stream);
entry.contentType = getContentType(entry.path); entry.contentType = getContentType(entry.path);
entry.integrity = getIntegrity(content); entry.integrity = getIntegrity(content);
entry.lastModified = header.mtime.toUTCString(); entry.lastModified = header.mtime.toUTCString();
entry.size = content.length; entry.size = content.length;
foundEntry = entry; foundEntry = entry;
next(); next();
} catch (error) {
next(error);
}
}) })
.on('finish', () => { .on('finish', () => {
accept(foundEntry); accept(foundEntry);
@ -49,7 +54,7 @@ async function findEntry(stream, filename) {
}); });
} }
export default async function serveFileMetadata(req, res) { async function serveFileMetadata(req, res) {
const stream = await getPackage(req.packageName, req.packageVersion); const stream = await getPackage(req.packageName, req.packageVersion);
const entry = await findEntry(stream, req.filename); const entry = await findEntry(stream, req.filename);
@ -59,3 +64,5 @@ export default async function serveFileMetadata(req, res) {
res.send(entry); res.send(entry);
} }
export default asyncHandler(serveFileMetadata);

View File

@ -1,7 +1,7 @@
import serveHTMLModule from './serveHTMLModule.js'; import serveHTMLModule from './serveHTMLModule.js';
import serveJavaScriptModule from './serveJavaScriptModule.js'; import serveJavaScriptModule from './serveJavaScriptModule.js';
export default async function serveModule(req, res) { export default function serveModule(req, res) {
if (req.entry.contentType === 'application/javascript') { if (req.entry.contentType === 'application/javascript') {
return serveJavaScriptModule(req, res); return serveJavaScriptModule(req, res);
} }

View File

@ -2,12 +2,13 @@ import path from 'path';
import gunzip from 'gunzip-maybe'; import gunzip from 'gunzip-maybe';
import tar from 'tar-stream'; import tar from 'tar-stream';
import asyncHandler from '../utils/asyncHandler.js';
import bufferStream from '../utils/bufferStream.js';
import createPackageURL from '../utils/createPackageURL.js'; import createPackageURL from '../utils/createPackageURL.js';
import createSearch from '../utils/createSearch.js'; import createSearch from '../utils/createSearch.js';
import { getPackage } from '../utils/npm.js';
import getIntegrity from '../utils/getIntegrity.js';
import getContentType from '../utils/getContentType.js'; import getContentType from '../utils/getContentType.js';
import bufferStream from '../utils/bufferStream.js'; import getIntegrity from '../utils/getIntegrity.js';
import { getPackage } from '../utils/npm.js';
function fileRedirect(req, res, entry) { function fileRedirect(req, res, entry) {
// Redirect to the file with the extension so it's // Redirect to the file with the extension so it's
@ -123,20 +124,24 @@ function searchEntries(stream, filename) {
} }
} }
const content = await bufferStream(stream); try {
const content = await bufferStream(stream);
entry.contentType = getContentType(entry.path); entry.contentType = getContentType(entry.path);
entry.integrity = getIntegrity(content); entry.integrity = getIntegrity(content);
entry.lastModified = header.mtime.toUTCString(); entry.lastModified = header.mtime.toUTCString();
entry.size = content.length; entry.size = content.length;
// Set the content only for the foundEntry and // Set the content only for the foundEntry and
// discard the buffer for all others. // discard the buffer for all others.
if (entry === foundEntry) { if (entry === foundEntry) {
entry.content = content; entry.content = content;
}
next();
} catch (error) {
next(error);
} }
next();
}) })
.on('finish', () => { .on('finish', () => {
accept({ accept({
@ -153,7 +158,7 @@ function searchEntries(stream, filename) {
* Fetch and search the archive to try and find the requested file. * Fetch and search the archive to try and find the requested file.
* Redirect to the "index" file if a directory was requested. * Redirect to the "index" file if a directory was requested.
*/ */
export default async function findEntry(req, res, next) { async function findEntry(req, res, next) {
const stream = await getPackage(req.packageName, req.packageVersion); const stream = await getPackage(req.packageName, req.packageVersion);
const { foundEntry: entry, matchingEntries: entries } = await searchEntries( const { foundEntry: entry, matchingEntries: entries } = await searchEntries(
stream, stream,
@ -201,3 +206,5 @@ export default async function findEntry(req, res, next) {
next(); next();
} }
export default asyncHandler(findEntry);

View File

@ -1,3 +1,4 @@
import asyncHandler from '../utils/asyncHandler.js';
import createPackageURL from '../utils/createPackageURL.js'; import createPackageURL from '../utils/createPackageURL.js';
import { getPackageConfig, resolveVersion } from '../utils/npm.js'; import { getPackageConfig, resolveVersion } from '../utils/npm.js';
@ -18,7 +19,7 @@ function semverRedirect(req, res, newVersion) {
* fetch the package config and add it to req.packageConfig. Redirect to * fetch the package config and add it to req.packageConfig. Redirect to
* the resolved version number if necessary. * the resolved version number if necessary.
*/ */
export default async function validateVersion(req, res, next) { async function validateVersion(req, res, next) {
const version = await resolveVersion(req.packageName, req.packageVersion); const version = await resolveVersion(req.packageName, req.packageVersion);
if (!version) { if (!version) {
@ -47,3 +48,5 @@ export default async function validateVersion(req, res, next) {
next(); next();
} }
export default asyncHandler(validateVersion);

View File

@ -0,0 +1,14 @@
/**
* Useful for wrapping `async` request handlers so they
* automatically propagate errors.
*/
export default function asyncHandler(handler) {
return (req, res, next) => {
Promise.resolve(handler(req, res, next)).catch(error => {
console.error(`Unexpected error in ${handler.name}!`);
console.error(error);
next(error);
});
};
}