@architect/http-proxy
Version:
Architect HTTP proxy distribution, extracted from @architect/functions (arc.http.proxy)
1,946 lines (1,864 loc) • 210 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.proxy = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
/**
* HTTP error response generator and template
*/
module.exports = {
httpError,
proxyConfig: proxyConfig()
}
function proxyConfig () {
let title = 'Index not found'
let message = `No static asset bucket or <code>get /</code> function found in your project. Possible solutions:<br?
<ul>
<li>Add <code>@static</code> to your project manifest</li>
<li>Add <code>get /</code> to the <code>@http</code> pragma of your project manifest</li>
<li>Manually specify an S3 bucket (containing an <code>index.html</code> file) with the <code>ARC_STATIC_BUCKET</code> env var</li>
<li>If using <code>arc.http.proxy</code>, pass in a valid config object</li>
</ul>
<a href="https://arc.codes/primitives/static" target="_blank">Learn more</a>`
return httpError({title, message})
}
function httpError ({statusCode=502, title='Unknown error', message=''}) {
title = title === 'Error'
? `${statusCode} error`
: `${statusCode} error: ${title}`
return {
statusCode,
headers: {
'Content-Type': 'text/html; charset=utf8;',
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0'
},
body: `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${title}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
code {
font-size: 1.25rem;
color: #00c26e;
}
.max-width-320 {
max-width: 20rem;
}
.margin-left-8 {
margin-left: 0.5rem;
}
.margin-bottom-16 {
margin-bottom: 1rem;
}
.margin-bottom-8 {
margin-bottom: 0.5rem;
}
.padding-32 {
padding: 2rem;
}
.padding-top-16 {
padding-top: 1rem;
}
a, a:hover {
color: #333;
}
p, li {
padding-bottom: 0.5rem;
}
</style>
</head>
<body class="padding-32">
<div>
<div class="margin-left-8">
<div class="margin-bottom-16">
<h1 class="margin-bottom-16">
${title}
</h1>
<p>
${message}
</p>
</div>
<div class="padding-top-16">
<p>
View Architect documentation at:
</p>
<a href="https://arc.codes">https://arc.codes</a>
</div>
</div>
</div>
</body>
</html>
`
}
}
},{}],2:[function(require,module,exports){
/**
* Common binary MIME types
*/
module.exports = [
'application/octet-stream',
// Docs
'application/epub+zip',
'application/msword',
'application/pdf',
'application/rtf',
'application/vnd.amazon.ebook',
'application/vnd.ms-excel',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
// Fonts
'font/otf',
'font/woff',
'font/woff2',
// Images
'image/bmp',
'image/gif',
'image/jpeg',
'image/png',
'image/tiff',
'image/vnd.microsoft.icon',
'image/webp',
// Audio
'audio/3gpp',
'audio/aac',
'audio/basic',
'audio/mpeg',
'audio/ogg',
'audio/wavaudio/webm',
'audio/x-aiff',
'audio/x-midi',
'audio/x-wav',
// Video
'video/3gpp',
'video/mp2t',
'video/mpeg',
'video/ogg',
'video/quicktime',
'video/webm',
'video/x-msvideo',
// Archives
'application/java-archive',
'application/vnd.apple.installer+xml',
'application/x-7z-compressed',
'application/x-apple-diskimage',
'application/x-bzip',
'application/x-bzip2',
'application/x-gzip',
'application/x-java-archive',
'application/x-rar-compressed',
'application/x-tar',
'application/x-zip',
'application/zip'
]
},{}],3:[function(require,module,exports){
// Bundler index + defaults
const GetIndexDefaultHandler = require('./index.js')
exports.handler = GetIndexDefaultHandler.proxy({ spa: true })
},{"./index.js":8}],4:[function(require,module,exports){
let {
gzipSync,
gunzipSync,
brotliCompressSync,
brotliDecompressSync,
deflateSync,
inflateSync,
} = require('zlib')
function compressor (direction, type, data) {
let compress = direction === 'compress'
let exec = {
gzip: compress ? gzipSync : gunzipSync,
br: compress ? brotliCompressSync : brotliDecompressSync,
deflate: compress ? deflateSync : inflateSync
}
if (!exec[type]) throw ReferenceError('Invalid compression type specified, must be gzip, br, or deflate')
return exec[type](data)
}
module.exports = {
compress: compressor.bind({}, 'compress'),
decompress: compressor.bind({}, 'decompress')
}
},{"zlib":undefined}],5:[function(require,module,exports){
let mime = require('mime-types')
let path = require('path')
let { compress } = require('./compress')
/**
* Normalizes response shape
*/
module.exports = function normalizeResponse (params) {
let { response, result, Key, isProxy, contentEncoding, config } = params
let noCache = [
'text/html',
'application/json',
'application/vnd.api+json'
]
response.headers = response.headers || {}
// Establish Content-Type
let contentType =
response.headers['Content-Type'] || // Possibly get content-type passed via proxy plugins
response.headers['content-type'] || // ...
result && result.ContentType || // Fall back to what came down from S3's metadata
mime.contentType(path.extname(Key)) // Finally, fall back to the mime type database
// Set Content-Type
response.headers['Content-Type'] = contentType
// Set caching headers
let neverCache = noCache.some(n => contentType.includes(n))
if (config.cacheControl) {
response.headers['Cache-Control'] = config.cacheControl
}
else if (neverCache) {
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0'
}
else if (result && result.CacheControl) {
response.headers['Cache-Control'] = result.CacheControl
}
else {
response.headers['Cache-Control'] = 'public, max-age=0, must-revalidate'
}
// Populate optional userland headers
if (config.headers) {
Object.keys(config.headers).forEach(h => response.headers[h] = config.headers[h])
}
// Normalize important common header casings to prevent dupes
if (response.headers['content-type']) {
response.headers['Content-Type'] = response.headers['content-type']
delete response.headers['content-type']
}
// Probably unable to be set via this code path, but normalize jic
if (response.headers['cache-control']) {
response.headers['Cache-Control'] = response.headers['cache-control']
delete response.headers['cache-control']
}
let notArcSix = !process.env.ARC_CLOUDFORMATION
let notArcProxy = !process.env.ARC_HTTP || process.env.ARC_HTTP === 'aws'
let isArcFive = notArcSix && notArcProxy
let isHTML = response.headers['Content-Type'].includes('text/html')
if (isArcFive && isHTML && !isProxy) {
// This is a deprecated code path that may be removed when Arc 5 exits LTS status
// Only return string bodies for certain types, and ONLY in Arc 5
response.body = Buffer.from(response.body).toString()
response.type = response.headers['Content-Type'] // Re-set type or it will fall back to JSON
}
else {
if (contentEncoding) {
response.body = compress(contentEncoding, response.body)
response.headers['Content-Encoding'] = contentEncoding
}
// Base64 everything else on the way out to enable text + binary support
response.body = Buffer.from(response.body).toString('base64')
response.isBase64Encoded = true
}
// Add ETag
response.headers.ETag = result.ETag
return response
}
},{"./compress":4,"mime-types":15,"path":undefined}],6:[function(require,module,exports){
module.exports = function templatizeResponse (params) {
let { isBinary, assets, response, isLocal=false } = params
// Bail early
if (isBinary) {
return response
}
else {
// Find: ${STATIC('path/filename.ext')}
// or: ${arc.static('path/filename.ext')}
let staticRegex = /\${(STATIC|arc\.static)\(.*\)}/g
// Maybe stringify jic previous steps passed a buffer; perhaps we can remove this step if/when proxy plugins is retired
let body = response.body instanceof Buffer ? response.body.toString() : response.body
response.body = body.replace(staticRegex, function fingerprint(match) {
let start = match.startsWith(`\${STATIC(`) ? 10 : 14
let Key = match.slice(start, match.length-3)
// Normalize around no leading slash for static manifest lookups
let startsWithSlash = Key.startsWith('/')
let lookup = startsWithSlash ? Key.substr(1) : Key
if (assets && assets[lookup] && !isLocal) {
Key = assets[lookup]
Key = startsWithSlash ? `/${Key}` : Key
}
return Key
})
response.body = Buffer.from(response.body) // Re-enbufferize
return response
}
}
},{}],7:[function(require,module,exports){
/**
* transform reduces {headers, body} with given plugins
*
* @param args - arguments obj
* @param args.Key - the origin S3 bucket Key requested
* @param args.config - the entire arc.proxy.public config obj
* @param args.defaults - the default {headers, body} in the transform pipeline
*/
module.exports = function transform({Key, config, isBinary, defaults}) {
let filetype = Key.split('.').pop()
let plugins = config.plugins? config.plugins[filetype] || [] : []
// early return if there's no processing to do
if (plugins.length === 0 || isBinary)
return defaults
else {
defaults.body = defaults.body.toString() // Convert non-binary files to strings for mutation
// otherwise walk the supplied plugins
return plugins.reduce(function run(response, plugin) {
/* eslint global-require: 'off' */
let transformer = typeof plugin === 'function'? plugin: require(plugin)
return transformer(Key, response, config)
}, defaults)
}
}
},{}],8:[function(require,module,exports){
let read = require('./read')
let errors = require('../errors')
/**
* arc.http.proxy
*
* Primary interface for reading static assets out of S3
*
* @param config - object, for configuration
* @param config.alias - object, map of root rel URLs to map to fully qualified root rel URLs
* @param config.assets - object, map of local, unfingerprinted filenames to fingerprinted filenames
* @param config.bucket - object, {staging, production} override the s3 bucket names
* @param config.bucket.staging - object, {staging, production} override the s3 bucket names
* @param config.bucket.production - object, {staging, production} override the s3 bucket names
* @param config.bucket.folder - string, bucket folder
* @param config.cacheControl - string, set a custom Cache-Control max-age header value
* @param config.plugins - object, configure proxy-plugin-* transforms per file extension
* @param config.spa - boolean, forces index.html no matter the folder depth
*
* @returns HTTPLambda - an HTTP Lambda function that proxies calls to S3
*/
function proxy (config={}) {
return async function httpProxy (req) {
let { ARC_STATIC_BUCKET, ARC_STATIC_SPA, NODE_ENV } = process.env
let deprecated = req.version === undefined || req.version === '1.0'
let isProduction = NODE_ENV === 'production'
let path = deprecated ? req.path : req.rawPath
let isFolder = path.split('/').pop().indexOf('.') === -1
let Key // Assigned below
/**
* Bucket config
*/
let configBucket = config.bucket
let bucketSetting = isProduction
? configBucket && configBucket['production']
: configBucket && configBucket['staging']
// Ok, all that out of the way, let's set the actual bucket, eh?
let Bucket = ARC_STATIC_BUCKET || bucketSetting
if (!Bucket) {
return errors.proxyConfig
}
/**
* Configure SPA + set up the file to be requested
*/
let spa = ARC_STATIC_SPA === 'false'
? false
: config && config.spa
if (!spa) config.spa = false
if (spa) {
// If SPA: force index.html
Key = isFolder ? 'index.html' : path.substring(1)
}
else {
// Return index.html for root, otherwise pass the path
let last = path.split('/').filter(Boolean).pop()
let isFile = last ? last.includes('.') : false
let isRoot = path === '/'
Key = isRoot? 'index.html' : path.substring(1) // Always remove leading slash
// Append default index.html to requests to folder paths
if (isRoot === false && isFile === false) {
Key = `${Key.replace(/\/$/, '')}/index.html`
}
}
/**
* Alias
* Allows a Key to be manually overridden
*/
let aliasing = config && config.alias && config.alias.hasOwnProperty(path)
if (aliasing) {
Key = config.alias[path].substring(1) // Always remove leading slash
}
/**
* REST API [very deprecated]: strip `staging/`, `production/` path parts
* - Post Architect 5.5 (2019-02-03) which added /{proxy+} this would be an edge case
* - e.g. you'd see this if someone put up a proxy in not-`get /`
*/
if (Key.startsWith('staging/')) Key = Key.replace('staging/', '')
if (Key.startsWith('production/')) Key = Key.replace('production/', '')
/**
* REST API [deprecated]: flag `staging/`, `production/` requests
*/
let rootPath
let reqPath = req.requestContext && req.requestContext.path
if (deprecated && reqPath) {
if (reqPath && reqPath.startsWith('/staging/')) rootPath = 'staging'
if (reqPath && reqPath.startsWith('/production/')) rootPath = 'production'
}
// Normalize if-none-match header to lower case; it differs between environments
let find = k => k.toLowerCase() === 'if-none-match'
let IfNoneMatch = req.headers && req.headers[Object.keys(req.headers).find(find)]
// Ensure response shape is correct for proxy SPA responses
let isProxy = req.resource === '/{proxy+}' || !!req.rawPath
return await read({ Key, Bucket, IfNoneMatch, isFolder, isProxy, config, rootPath })
}
}
module.exports = {
proxy, // Default
read // Read a specific file
}
},{"../errors":1,"./read":12}],9:[function(require,module,exports){
let { existsSync, readFileSync } = require('fs')
let { extname, join, sep } = require('path')
let mime = require('mime-types')
let crypto = require('crypto')
let binaryTypes = require('../../helpers/binary-types')
let { httpError } = require('../../errors')
let transform = require('../format/transform') // Soon to be deprecated
let templatizeResponse = require('../format/templatize')
let normalizeResponse = require('../format/response')
let pretty = require('./_pretty')
/**
* arc.http.proxy.read
*
* Reads a file from the local filesystem, resolving an HTTP Lambda friendly payload
*
* @param {Object} params
* @param {String} params.Key
* @param {String} params.IfNoneMatch
* @param {String} params.isFolder
* @param {String} params.isProxy
* @param {Object} params.config
* @returns {Object} {statusCode, headers, body}
*/
module.exports = async function readLocal (params) {
let { ARC_SANDBOX_PATH_TO_STATIC, ARC_STATIC_PREFIX, ARC_STATIC_FOLDER } = process.env
let { Key, IfNoneMatch, isFolder, isProxy, config } = params
let headers = {}
let response = {}
// Unlike S3, handle basePath and assets inside the function as Sandbox is long-lived
let staticAssets
// After 6.x we can rely on this env var in sandbox
let basePath = ARC_SANDBOX_PATH_TO_STATIC || join(process.cwd(), '..', '..', '..', 'public')
let staticManifest = join(basePath, 'static.json')
if (existsSync(staticManifest)) {
staticAssets = JSON.parse(readFileSync(staticManifest))
}
let assets = config.assets || staticAssets
// Look up the blob
// Assume we're running from a lambda in src/**/* OR from vendored node_modules/@architect/sandbox
let filePath = join(basePath, Key)
// Denormalize static folder for local paths (not something we'd do in S3)
let staticPrefix = ARC_STATIC_PREFIX || ARC_STATIC_FOLDER
if (filePath.includes(staticPrefix)) {
filePath = filePath.replace(`${staticPrefix}${sep}`, '')
}
try {
// If client sends If-None-Match, use it in S3 getObject params
let matchedETag = false
// If the static asset manifest has the key, use that, otherwise fall back to the original Key
let contentType = mime.contentType(extname(Key))
if (!existsSync(filePath)) {
return await pretty({ Key: filePath, config, isFolder })
}
let body = readFileSync(filePath)
let ETag = crypto.createHash('sha256').update(body).digest('hex')
let result = {
ContentType: contentType,
ETag
}
if (IfNoneMatch === ETag) {
matchedETag = true
headers.ETag = IfNoneMatch
response = {
statusCode: 304,
headers
}
}
// No ETag found, return the blob
if (!matchedETag) {
let isBinary = binaryTypes.some(type => result.ContentType.includes(type) || contentType.includes(type))
// Transform first to allow for any proxy plugin mutations
response = transform({
Key,
config,
isBinary,
defaults: {
headers,
body
}
})
// Handle templating
response = templatizeResponse({
isBinary,
assets,
response,
isLocal: true
})
// Normalize response
response = normalizeResponse({
response,
result,
Key,
isProxy,
config
})
// Add ETag
response.headers.ETag = result.ETag
}
if (!response.statusCode) {
response.statusCode = 200
}
return response
}
catch (err) {
let notFound = err.name === 'NoSuchKey'
if (notFound) {
return await pretty({ Key: filePath, config, isFolder })
}
else {
let title = err.name
let message = `
${err.message}<br>
<pre>${err.stack}</pre>
`
return httpError({ statusCode: 500, title, message })
}
}
}
},{"../../errors":1,"../../helpers/binary-types":2,"../format/response":5,"../format/templatize":6,"../format/transform":7,"./_pretty":10,"crypto":undefined,"fs":undefined,"mime-types":15,"path":undefined}],10:[function(require,module,exports){
let aws = require('aws-sdk')
let { existsSync, readFileSync } = require('fs')
let { join } = require('path')
let { httpError } = require('../../errors')
/**
* Peek into a dir without a trailing slash to see if it's got an index.html file
* If not, look for a custom 404.html
* Finally, return the default 404
*/
module.exports = async function pretty (params) {
let { Bucket, Key, assets, headers, isFolder, prefix } = params
let { ARC_LOCAL, ARC_SANDBOX_PATH_TO_STATIC, NODE_ENV } = process.env
let local = NODE_ENV === 'testing' || ARC_LOCAL
let s3 = new aws.S3
function getKey (Key) {
let lookup = Key.replace(prefix + '/', '')
if (assets && assets[lookup]) {
Key = assets[lookup]
Key = prefix ? `${prefix}/${Key}` : Key
}
return Key
}
async function getLocal (file) {
let basepath = ARC_SANDBOX_PATH_TO_STATIC
if (!file.startsWith(basepath)) {
file = join(basepath, file)
}
if (!existsSync(file)) {
let err = ReferenceError(`NoSuchKey: ${Key} not found`)
err.name = 'NoSuchKey'
throw err
}
else return {
Body: readFileSync(file)
}
}
async function getS3 (Key) {
return await s3.getObject({ Bucket, Key }).promise()
}
async function get (file) {
let getter = local ? getLocal : getS3
try {
return await getter(file)
}
catch (err) {
if (err.name === 'NoSuchKey') {
err.statusCode = 404
return err
}
else {
err.statusCode = 500
return err
}
}
}
/**
* Enable pretty urls
* Peek into a dir without trailing slash to see if it contains index.html
*/
if (isFolder && !Key.endsWith('/')) {
let peek = getKey(`${Key}/index.html`)
let result = await get(peek)
if (result.Body) {
let body = result.Body.toString()
return { headers, statusCode: 200, body }
}
}
/**
* Enable custom 404s
* Check to see if user defined a custom 404 page
*/
let notFound = getKey(`404.html`)
let result = await get(notFound)
if (result.Body) {
let body = result.Body.toString()
return {
headers: {
'Content-Type': 'text/html; charset=utf8;',
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0'
},
statusCode: 404,
body
}
}
else {
let err = result
let { statusCode } = err
let title = err.name
let message = `
${err.message } <pre><b>${ Key }</b></pre><br>
<pre>${err.stack}</pre>
`
return httpError({ statusCode, title, message })
}
}
},{"../../errors":1,"aws-sdk":"aws-sdk","fs":undefined,"path":undefined}],11:[function(require,module,exports){
let { existsSync, readFileSync } = require('fs')
let { extname, join } = require('path')
let mime = require('mime-types')
let aws = require('aws-sdk')
let binaryTypes = require('../../helpers/binary-types')
let { httpError } = require('../../errors')
let transform = require('../format/transform') // Soon to be deprecated
let templatizeResponse = require('../format/templatize')
let normalizeResponse = require('../format/response')
let pretty = require('./_pretty')
let { decompress } = require('../format/compress')
/**
* arc.http.proxy.read
*
* Reads a file from S3, resolving an HTTP Lambda friendly payload
*
* @param {Object} params
* @param {String} params.Key
* @param {String} params.Bucket
* @param {String} params.IfNoneMatch
* @param {String} params.isFolder
* @param {String} params.isProxy
* @param {Object} params.config
* @returns {Object} {statusCode, headers, body}
*/
module.exports = async function readS3 (params) {
let { Bucket, Key, IfNoneMatch, isFolder, isProxy, config, rootPath } = params
let { ARC_STATIC_PREFIX, ARC_STATIC_FOLDER } = process.env
let prefix = ARC_STATIC_PREFIX || ARC_STATIC_FOLDER || config.bucket && config.bucket.folder
let assets = config.assets || staticAssets
let headers = {}
let response = {}
try {
// If client sends If-None-Match, use it in S3 getObject params
let matchedETag = false
let s3 = new aws.S3
// Try to interpolate HTML/JSON requests to fingerprinted filenames
let contentType = mime.contentType(extname(Key))
let capture = [ 'text/html', 'application/json' ]
let isCaptured = capture.some(type => contentType.includes(type))
if (assets && assets[Key] && isCaptured) {
// Not necessary to flag response formatter for anti-caching
// Those headers are already set in S3 file metadata
Key = assets[Key]
}
/**
* Check for possible fingerprint upgrades and forward valid requests
*/
if (assets && assets[Key] && !isCaptured) {
let location = rootPath
? `/${rootPath}/_static/${assets[Key]}`
: `/_static/${assets[Key]}`
return {
statusCode: 302,
headers: {
location,
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0'
}
}
}
/**
* Folder prefix
* Enables a bucket folder at root to be specified
*/
if (prefix) {
Key = `${prefix}/${Key}`
}
let options = { Bucket, Key }
if (IfNoneMatch) {
options.IfNoneMatch = IfNoneMatch
}
let result = await s3.getObject(options).promise().catch(err => {
// ETag matches (getObject error code of NotModified), so don't transit the whole file
if (err.code === 'NotModified') {
matchedETag = true
headers.ETag = IfNoneMatch
response = {
statusCode: 304,
headers
}
}
else {
// Important: do not swallow this error otherwise!
throw err
}
})
// No ETag found, return the blob
if (!matchedETag) {
let contentEncoding = result.ContentEncoding
if (contentEncoding) {
result.Body = decompress(contentEncoding, result.Body)
}
let isBinary = binaryTypes.some(type => result.ContentType.includes(type) || contentType.includes(type))
// Transform first to allow for any proxy plugin mutations
response = transform({
Key,
config,
isBinary,
defaults: {
headers,
body: result.Body
}
})
// Handle templating
response = templatizeResponse({
isBinary,
assets,
response
})
// Normalize response
response = normalizeResponse({
response,
result,
Key,
isProxy,
contentEncoding,
config
})
}
if (!response.statusCode) {
response.statusCode = 200
}
return response
}
catch (err) {
let notFound = err.name === 'NoSuchKey'
if (notFound) {
return await pretty({ Bucket, Key, assets, headers, isFolder, prefix })
}
else {
let title = err.name
let message = `
${err.message}<br>
<pre>${err.stack}</pre>
`
return httpError({ statusCode: 500, title, message })
}
}
}
/**
* Fingerprinting manifest
* Load the manifest, try to hit the disk as infrequently as possible across invocations
*/
let staticAssets
let staticManifest = join(process.cwd(), 'node_modules', '@architect', 'shared', 'static.json')
if (staticAssets === false) {
null /*noop*/
}
else if (existsSync(staticManifest) && !staticAssets) {
staticAssets = JSON.parse(readFileSync(staticManifest))
}
else {
staticAssets = false
}
},{"../../errors":1,"../../helpers/binary-types":2,"../format/compress":4,"../format/response":5,"../format/templatize":6,"../format/transform":7,"./_pretty":10,"aws-sdk":"aws-sdk","fs":undefined,"mime-types":15,"path":undefined}],12:[function(require,module,exports){
let readLocal = require('./_local')
let readS3 = require('./_s3')
function read () {
let { ARC_LOCAL, NODE_ENV } = process.env
let local = NODE_ENV === 'testing' || ARC_LOCAL
return local ? readLocal : readS3
}
module.exports = read()
},{"./_local":9,"./_s3":11}],13:[function(require,module,exports){
module.exports={
"application/1d-interleaved-parityfec": {
"source": "iana"
},
"application/3gpdash-qoe-report+xml": {
"source": "iana",
"charset": "UTF-8",
"compressible": true
},
"application/3gpp-ims+xml": {
"source": "iana",
"compressible": true
},
"application/a2l": {
"source": "iana"
},
"application/activemessage": {
"source": "iana"
},
"application/activity+json": {
"source": "iana",
"compressible": true
},
"application/alto-costmap+json": {
"source": "iana",
"compressible": true
},
"application/alto-costmapfilter+json": {
"source": "iana",
"compressible": true
},
"application/alto-directory+json": {
"source": "iana",
"compressible": true
},
"application/alto-endpointcost+json": {
"source": "iana",
"compressible": true
},
"application/alto-endpointcostparams+json": {
"source": "iana",
"compressible": true
},
"application/alto-endpointprop+json": {
"source": "iana",
"compressible": true
},
"application/alto-endpointpropparams+json": {
"source": "iana",
"compressible": true
},
"application/alto-error+json": {
"source": "iana",
"compressible": true
},
"application/alto-networkmap+json": {
"source": "iana",
"compressible": true
},
"application/alto-networkmapfilter+json": {
"source": "iana",
"compressible": true
},
"application/alto-updatestreamcontrol+json": {
"source": "iana",
"compressible": true
},
"application/alto-updatestreamparams+json": {
"source": "iana",
"compressible": true
},
"application/aml": {
"source": "iana"
},
"application/andrew-inset": {
"source": "iana",
"extensions": ["ez"]
},
"application/applefile": {
"source": "iana"
},
"application/applixware": {
"source": "apache",
"extensions": ["aw"]
},
"application/atf": {
"source": "iana"
},
"application/atfx": {
"source": "iana"
},
"application/atom+xml": {
"source": "iana",
"compressible": true,
"extensions": ["atom"]
},
"application/atomcat+xml": {
"source": "iana",
"compressible": true,
"extensions": ["atomcat"]
},
"application/atomdeleted+xml": {
"source": "iana",
"compressible": true,
"extensions": ["atomdeleted"]
},
"application/atomicmail": {
"source": "iana"
},
"application/atomsvc+xml": {
"source": "iana",
"compressible": true,
"extensions": ["atomsvc"]
},
"application/atsc-dwd+xml": {
"source": "iana",
"compressible": true,
"extensions": ["dwd"]
},
"application/atsc-dynamic-event-message": {
"source": "iana"
},
"application/atsc-held+xml": {
"source": "iana",
"compressible": true,
"extensions": ["held"]
},
"application/atsc-rdt+json": {
"source": "iana",
"compressible": true
},
"application/atsc-rsat+xml": {
"source": "iana",
"compressible": true,
"extensions": ["rsat"]
},
"application/atxml": {
"source": "iana"
},
"application/auth-policy+xml": {
"source": "iana",
"compressible": true
},
"application/bacnet-xdd+zip": {
"source": "iana",
"compressible": false
},
"application/batch-smtp": {
"source": "iana"
},
"application/bdoc": {
"compressible": false,
"extensions": ["bdoc"]
},
"application/beep+xml": {
"source": "iana",
"charset": "UTF-8",
"compressible": true
},
"application/calendar+json": {
"source": "iana",
"compressible": true
},
"application/calendar+xml": {
"source": "iana",
"compressible": true,
"extensions": ["xcs"]
},
"application/call-completion": {
"source": "iana"
},
"application/cals-1840": {
"source": "iana"
},
"application/cap+xml": {
"source": "iana",
"charset": "UTF-8",
"compressible": true
},
"application/cbor": {
"source": "iana"
},
"application/cbor-seq": {
"source": "iana"
},
"application/cccex": {
"source": "iana"
},
"application/ccmp+xml": {
"source": "iana",
"compressible": true
},
"application/ccxml+xml": {
"source": "iana",
"compressible": true,
"extensions": ["ccxml"]
},
"application/cdfx+xml": {
"source": "iana",
"compressible": true,
"extensions": ["cdfx"]
},
"application/cdmi-capability": {
"source": "iana",
"extensions": ["cdmia"]
},
"application/cdmi-container": {
"source": "iana",
"extensions": ["cdmic"]
},
"application/cdmi-domain": {
"source": "iana",
"extensions": ["cdmid"]
},
"application/cdmi-object": {
"source": "iana",
"extensions": ["cdmio"]
},
"application/cdmi-queue": {
"source": "iana",
"extensions": ["cdmiq"]
},
"application/cdni": {
"source": "iana"
},
"application/cea": {
"source": "iana"
},
"application/cea-2018+xml": {
"source": "iana",
"compressible": true
},
"application/cellml+xml": {
"source": "iana",
"compressible": true
},
"application/cfw": {
"source": "iana"
},
"application/clue+xml": {
"source": "iana",
"compressible": true
},
"application/clue_info+xml": {
"source": "iana",
"compressible": true
},
"application/cms": {
"source": "iana"
},
"application/cnrp+xml": {
"source": "iana",
"compressible": true
},
"application/coap-group+json": {
"source": "iana",
"compressible": true
},
"application/coap-payload": {
"source": "iana"
},
"application/commonground": {
"source": "iana"
},
"application/conference-info+xml": {
"source": "iana",
"compressible": true
},
"application/cose": {
"source": "iana"
},
"application/cose-key": {
"source": "iana"
},
"application/cose-key-set": {
"source": "iana"
},
"application/cpl+xml": {
"source": "iana",
"compressible": true
},
"application/csrattrs": {
"source": "iana"
},
"application/csta+xml": {
"source": "iana",
"compressible": true
},
"application/cstadata+xml": {
"source": "iana",
"compressible": true
},
"application/csvm+json": {
"source": "iana",
"compressible": true
},
"application/cu-seeme": {
"source": "apache",
"extensions": ["cu"]
},
"application/cwt": {
"source": "iana"
},
"application/cybercash": {
"source": "iana"
},
"application/dart": {
"compressible": true
},
"application/dash+xml": {
"source": "iana",
"compressible": true,
"extensions": ["mpd"]
},
"application/dashdelta": {
"source": "iana"
},
"application/davmount+xml": {
"source": "iana",
"compressible": true,
"extensions": ["davmount"]
},
"application/dca-rft": {
"source": "iana"
},
"application/dcd": {
"source": "iana"
},
"application/dec-dx": {
"source": "iana"
},
"application/dialog-info+xml": {
"source": "iana",
"compressible": true
},
"application/dicom": {
"source": "iana"
},
"application/dicom+json": {
"source": "iana",
"compressible": true
},
"application/dicom+xml": {
"source": "iana",
"compressible": true
},
"application/dii": {
"source": "iana"
},
"application/dit": {
"source": "iana"
},
"application/dns": {
"source": "iana"
},
"application/dns+json": {
"source": "iana",
"compressible": true
},
"application/dns-message": {
"source": "iana"
},
"application/docbook+xml": {
"source": "apache",
"compressible": true,
"extensions": ["dbk"]
},
"application/dots+cbor": {
"source": "iana"
},
"application/dskpp+xml": {
"source": "iana",
"compressible": true
},
"application/dssc+der": {
"source": "iana",
"extensions": ["dssc"]
},
"application/dssc+xml": {
"source": "iana",
"compressible": true,
"extensions": ["xdssc"]
},
"application/dvcs": {
"source": "iana"
},
"application/ecmascript": {
"source": "iana",
"compressible": true,
"extensions": ["ecma","es"]
},
"application/edi-consent": {
"source": "iana"
},
"application/edi-x12": {
"source": "iana",
"compressible": false
},
"application/edifact": {
"source": "iana",
"compressible": false
},
"application/efi": {
"source": "iana"
},
"application/emergencycalldata.comment+xml": {
"source": "iana",
"compressible": true
},
"application/emergencycalldata.control+xml": {
"source": "iana",
"compressible": true
},
"application/emergencycalldata.deviceinfo+xml": {
"source": "iana",
"compressible": true
},
"application/emergencycalldata.ecall.msd": {
"source": "iana"
},
"application/emergencycalldata.providerinfo+xml": {
"source": "iana",
"compressible": true
},
"application/emergencycalldata.serviceinfo+xml": {
"source": "iana",
"compressible": true
},
"application/emergencycalldata.subscriberinfo+xml": {
"source": "iana",
"compressible": true
},
"application/emergencycalldata.veds+xml": {
"source": "iana",
"compressible": true
},
"application/emma+xml": {
"source": "iana",
"compressible": true,
"extensions": ["emma"]
},
"application/emotionml+xml": {
"source": "iana",
"compressible": true,
"extensions": ["emotionml"]
},
"application/encaprtp": {
"source": "iana"
},
"application/epp+xml": {
"source": "iana",
"compressible": true
},
"application/epub+zip": {
"source": "iana",
"compressible": false,
"extensions": ["epub"]
},
"application/eshop": {
"source": "iana"
},
"application/exi": {
"source": "iana",
"extensions": ["exi"]
},
"application/expect-ct-report+json": {
"source": "iana",
"compressible": true
},
"application/fastinfoset": {
"source": "iana"
},
"application/fastsoap": {
"source": "iana"
},
"application/fdt+xml": {
"source": "iana",
"compressible": true,
"extensions": ["fdt"]
},
"application/fhir+json": {
"source": "iana",
"charset": "UTF-8",
"compressible": true
},
"application/fhir+xml": {
"source": "iana",
"charset": "UTF-8",
"compressible": true
},
"application/fido.trusted-apps+json": {
"compressible": true
},
"application/fits": {
"source": "iana"
},
"application/flexfec": {
"source": "iana"
},
"application/font-sfnt": {
"source": "iana"
},
"application/font-tdpfr": {
"source": "iana",
"extensions": ["pfr"]
},
"application/font-woff": {
"source": "iana",
"compressible": false
},
"application/framework-attributes+xml": {
"source": "iana",
"compressible": true
},
"application/geo+json": {
"source": "iana",
"compressible": true,
"extensions": ["geojson"]
},
"application/geo+json-seq": {
"source": "iana"
},
"application/geopackage+sqlite3": {
"source": "iana"
},
"application/geoxacml+xml": {
"source": "iana",
"compressible": true
},
"application/gltf-buffer": {
"source": "iana"
},
"application/gml+xml": {
"source": "iana",
"compressible": true,
"extensions": ["gml"]
},
"application/gpx+xml": {
"source": "apache",
"compressible": true,
"extensions": ["gpx"]
},
"application/gxf": {
"source": "apache",
"extensions": ["gxf"]
},
"application/gzip": {
"source": "iana",
"compressible": false,
"extensions": ["gz"]
},
"application/h224": {
"source": "iana"
},
"application/held+xml": {
"source": "iana",
"compressible": true
},
"application/hjson": {
"extensions": ["hjson"]
},
"application/http": {
"source": "iana"
},
"application/hyperstudio": {
"source": "iana",
"extensions": ["stk"]
},
"application/ibe-key-request+xml": {
"source": "iana",
"compressible": true
},
"application/ibe-pkg-reply+xml": {
"source": "iana",
"compressible": true
},
"application/ibe-pp-data": {
"source": "iana"
},
"application/iges": {
"source": "iana"
},
"application/im-iscomposing+xml": {
"source": "iana",
"charset": "UTF-8",
"compressible": true
},
"application/index": {
"source": "iana"
},
"application/index.cmd": {
"source": "iana"
},
"application/index.obj": {
"source": "iana"
},
"application/index.response": {
"source": "iana"
},
"application/index.vnd": {
"source": "iana"
},
"application/inkml+xml": {
"source": "iana",
"compressible": true,
"extensions": ["ink","inkml"]
},
"application/iotp": {
"source": "iana"
},
"application/ipfix": {
"source": "iana",
"extensions": ["ipfix"]
},
"application/ipp": {
"source": "iana"
},
"application/isup": {
"source": "iana"
},
"application/its+xml": {
"source": "iana",
"compressible": true,
"extensions": ["its"]
},
"application/java-archive": {
"source": "apache",
"compressible": false,
"extensions": ["jar","war","ear"]
},
"application/java-serialized-object": {
"source": "apache",
"compressible": false,
"extensions": ["ser"]
},
"application/java-vm": {
"source": "apache",
"compressible": false,
"extensions": ["class"]
},
"application/javascript": {
"source": "iana",
"charset": "UTF-8",
"compressible": true,
"extensions": ["js","mjs"]
},
"application/jf2feed+json": {
"source": "iana",
"compressible": true
},
"application/jose": {
"source": "iana"
},
"application/jose+json": {
"source": "iana",
"compressible": true
},
"application/jrd+json": {
"source": "iana",
"compressible": true
},
"application/json": {
"source": "iana",
"charset": "UTF-8",
"compressible": true,
"extensions": ["json","map"]
},
"application/json-patch+json": {
"source": "iana",
"compressible": true
},
"application/json-seq": {
"source": "iana"
},
"application/json5": {
"extensions": ["json5"]
},
"application/jsonml+json": {
"source": "apache",
"compressible": true,
"extensions": ["jsonml"]
},
"application/jwk+json": {
"source": "iana",
"compressible": true
},
"application/jwk-set+json": {
"source": "iana",
"compressible": true
},
"application/jwt": {
"source": "iana"
},
"application/kpml-request+xml": {
"source": "iana",
"compressible": true
},
"application/kpml-response+xml": {
"source": "iana",
"compressible": true
},
"application/ld+json": {
"source": "iana",
"compressible": true,
"extensions": ["jsonld"]
},
"application/lgr+xml": {
"source": "iana",
"compressible": true,
"extensions": ["lgr"]
},
"application/link-format": {
"source": "iana"
},
"application/load-control+xml": {
"source": "iana",
"compressible": true
},
"application/lost+xml": {
"source": "iana",
"compressible": true,
"extensions": ["lostxml"]
},
"application/lostsync+xml": {
"source": "iana",
"compressible": true
},
"application/lpf+zip": {
"source": "iana",
"compressible": false
},
"application/lxf": {
"source": "iana"
},
"application/mac-binhex40": {
"source": "iana",
"extensions": ["hqx"]
},
"application/mac-compactpro": {
"source": "apache",
"extensions": ["cpt"]
},
"application/macwriteii": {
"source": "iana"
},
"application/mads+xml": {
"source": "iana",
"compressible": true,
"extensions": ["mads"]
},
"application/manifest+json": {
"charset": "UTF-8",
"compressible": true,
"extensions": ["webmanifest"]
},
"application/marc": {
"source": "iana",
"extensions": ["mrc"]
},
"application/marcxml+xml": {
"source": "iana",
"compressible": true,
"extensions": ["mrcx"]
},
"application/mathematica": {
"source": "iana",
"extensions": ["ma","nb","mb"]
},
"application/mathml+xml": {
"source": "iana",
"compressible": true,
"extensions": ["mathml"]
},
"application/mathml-content+xml": {
"source": "iana",
"compressible": true
},
"application/mathml-presentation+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-associated-procedure-description+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-deregister+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-envelope+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-msk+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-msk-response+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-protection-description+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-reception-report+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-register+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-register-response+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-schedule+xml": {
"source": "iana",
"compressible": true
},
"application/mbms-user-service-description+xml": {
"source": "iana",
"compressible": true
},
"application/mbox": {
"source": "iana",
"extensions": ["mbox"]
},
"application/media-policy-dataset+xml": {
"source": "iana",
"compressible": true
},
"application/media_control+xml": {
"source": "iana",
"compressible": true
},
"application/mediaservercontrol+xml": {
"source": "iana",
"compressible": true,
"extensions": ["mscml"]
},
"application/merge-patch+json": {
"source": "iana",
"compressible": true
},
"application/metalink+xml": {
"source": "apache",
"compressible": true,
"extensions": ["metalink"]
},
"application/metalink4+xml": {
"source": "iana",
"compressible": true,
"extensions": ["meta4"]
},
"application/mets+xml": {
"source": "iana",
"compressible": true,
"extensions": ["mets"]
},
"application/mf4": {
"source": "iana"
},
"application/mikey": {
"source": "iana"
},
"application/mipc": {
"source": "iana"
},
"application/mmt-aei+xml": {
"source": "iana",
"compressible": true,
"extensions": ["maei"]
},
"application/mmt-usd+xml": {
"source": "iana",
"compressible": true,
"extensions": ["musd"]
},
"application/mods+xml": {
"source": "iana",
"compressible": true,
"extensions": ["mods"]
},
"application/moss-keys": {
"source": "iana"
},
"application/moss-signature": {
"source": "iana"
},
"application/mosskey-data": {
"source": "iana"
},
"application/mosskey-request": {
"source": "iana"
},
"application/mp21": {
"source": "iana",
"extensions": ["m21","mp21"]
},
"application/mp4": {
"source": "iana",
"extensions": ["mp4s","m4p"]
},
"application/mpeg4-generic": {
"source": "iana"
},
"application/mpeg4-iod": {
"source": "iana"
},
"application/mpeg4-iod-xmt": {
"source": "iana"
},
"application/mrb-consumer+xml": {
"source": "iana",
"compressible": true,
"extensions": ["xdf"]
},
"application/mrb-publish+xml": {
"source": "iana",
"compressible": true,
"extensions": ["xdf"]
},
"application/msc-ivr+xml": {
"source": "iana",
"charset": "UTF-8",
"compressible": true
},
"application/msc-mixer+xml": {
"source": "iana",
"charset": "UTF-8",
"compressible": true
},
"application/msword": {
"source": "iana",
"compressible": false,
"extensions": ["doc","dot"]
},
"application/mud+json": {
"source": "iana",
"compressible": true
},
"application/multipart-core": {
"source": "iana"
},
"application/mxf": {
"source": "iana",
"extensions": ["mxf"]
},
"application/n-quads": {
"source": "iana",
"extensions": ["nq"]
},
"application/n-triples": {
"source": "iana",
"extensions": ["nt"]
},
"application/nasdata": {
"source": "iana"
},
"application/news-checkgroups": {
"source": "iana",
"charset": "US-ASCII"
},
"application/news-groupinfo": {
"source": "iana",
"charset": "US-ASCII"
},
"application/news-transmission": {
"source": "iana"
},
"application/nlsml+xml": {
"source": "iana",
"compressible": true
},
"application/node": {
"source": "iana",
"extensions": ["cjs"]
},
"application/nss": {
"source": "iana"
},
"application/ocsp-request": {
"source": "iana"
},
"application/ocsp-response": {
"source": "iana"
},
"application/octet-stream": {
"source": "iana",
"compressible": false,
"extensions": ["bin","dms","lrf","mar","so","dist","distz","pkg","bpk","dump","elc","deploy","exe","dll","deb","dmg","iso","img","msi","msp","msm","buffer"]
},
"application/oda": {
"source": "iana",
"extensions": ["oda"]
},
"application/odm+xml": {
"source": "iana",
"compressible": true
},
"application/odx": {
"source": "iana"
},
"application/oebps-package+xml": {
"source": "iana",
"compressible": true,
"extensions": ["opf"]
},
"application/ogg": {
"source": "iana",
"compressible": false,
"extensions": ["ogx"]
},
"application/omdoc+xml": {
"source": "apache",
"compressible": true,
"extensions": ["omdoc"]
},
"application/onenote": {
"source": "apache",
"extensions": ["onetoc","onetoc2","onetmp","onepkg"]
},
"application/oscore": {
"source": "iana"
},
"application/oxps": {
"source": "iana",
"extensions": ["oxps"]
},
"application/p2p-overlay+xml": {
"source": "iana",
"compressible": true,
"extensions": ["relo"]
},
"application/parityfec": {
"source": "iana"
},
"application/passport": {
"source": "iana"
},
"application/patch-ops-error+xml": {
"source": "iana",
"compressible": true,
"extensions": ["xer"]
},
"application/pdf": {
"source": "iana",
"compressible": false,
"extensions": ["pdf"]
},
"application/pdx": {
"source": "iana"
},
"application/pem-certificate-chain": {
"source": "iana"
},
"application/pgp-encrypted": {
"source": "iana",
"compressible": false,
"extensions": ["pgp"]
},
"application/pgp-keys": {
"source": "iana"
},
"application/pgp-signature": {
"source": "iana",
"extensions": ["asc","s