/** * This module contains some common helpers shared between middlewares */ 'use strict' const mime = require('mime') const _ = require('lodash') const parseRange = require('range-parser') const Buffer = require('safe-buffer').Buffer const log = require('../logger').create('web-server') function createServeFile (fs, directory, config) { const cache = Object.create(null) return function (filepath, rangeHeader, response, transform, content, doNotCache) { let responseData function convertForRangeRequest () { const range = parseRange(responseData.length, rangeHeader) if (range === -2) { return 200 // malformed header string } else if (range === -1) { responseData = Buffer.alloc(0) // unsatisfiable range return 416 } else if (range.type === 'bytes') { responseData = Buffer.from(responseData) if (range.length === 1) { const { start, end } = range[0] response.setHeader('Content-Range', `bytes ${start}-${end}/${responseData.length}`) response.setHeader('Accept-Ranges', 'bytes') response.setHeader('Content-Length', end - start + 1) responseData = responseData.slice(start, end + 1) return 206 } else { responseData = Buffer.alloc(0) // Multiple ranges are not supported. Maybe future? return 416 } } return 200 // All other states, ignore } if (directory) { filepath = directory + filepath } if (!content && cache[filepath]) { content = cache[filepath] } if (config && config.customHeaders && config.customHeaders.length > 0) { config.customHeaders.forEach((header) => { const regex = new RegExp(header.match) if (regex.test(filepath)) { log.debug(`setting header: ${header.name} for: ${filepath}`) response.setHeader(header.name, header.value) } }) } if (content && !doNotCache) { log.debug(`serving (cached): ${filepath}`) response.setHeader('Content-Type', mime.getType(filepath, 'text/plain')) responseData = (transform && transform(content)) || content response.writeHead(rangeHeader ? convertForRangeRequest() : 200) return response.end(responseData) } return fs.readFile(filepath, function (error, data) { if (error) { return serve404(response, filepath) } if (!doNotCache) { cache[filepath] = data.toString() } log.debug('serving: ' + filepath) response.setHeader('Content-Type', mime.getType(filepath, 'text/plain')) responseData = (transform && transform(data.toString())) || data response.writeHead(rangeHeader ? convertForRangeRequest() : 200) return response.end(responseData) }) } } function serve404 (response, path) { log.warn(`404: ${path}`) response.writeHead(404) return response.end('NOT FOUND') } function setNoCacheHeaders (response) { response.setHeader('Cache-Control', 'no-cache') response.setHeader('Pragma', 'no-cache') response.setHeader('Expires', (new Date(0)).toUTCString()) } function setHeavyCacheHeaders (response) { response.setHeader('Cache-Control', 'public, max-age=31536000') } function initializeMimeTypes (config) { if (config && config.mime) { _.forEach(config.mime, (value, key) => { mime.define({ [key]: value }, true) }) } } class PromiseContainer { constructor () { this.promise = null } then (success, error) { return this.promise.then(success, error) } set (newPromise) { this.promise = newPromise } } // PUBLIC API exports.PromiseContainer = PromiseContainer exports.createServeFile = createServeFile exports.setNoCacheHeaders = setNoCacheHeaders exports.setHeavyCacheHeaders = setHeavyCacheHeaders exports.initializeMimeTypes = initializeMimeTypes exports.serve404 = serve404