irc_dicom_service/app/middleware/mystatic.js

258 lines
6.6 KiB
JavaScript

var crypto = require('crypto')
var fs = require('mz/fs')
var zlib = require('mz/zlib')
var path = require('path')
var mime = require('mime-types')
var readDir = require('fs-readdir-recursive')
var debug = require('debug')('koa-static-cache')
class FileManager {
constructor(store) {
if (store && typeof store.set === 'function' && typeof store.get === 'function') {
this.store = store
} else {
this.map = store || Object.create(null)
}
}
mapInit() {
return this.map = Object.create(null);
}
get(key) {
return this.store ? this.store.get(key) : this.map[key]
}
set(key, value) {
if (this.store) return this.store.set(key, value)
this.map[key] = value
}
}
/*
options = {
cacheEnable: true,//缓存是否可用
}
*/
module.exports = function staticCache(dir, options, files) {
if (typeof dir === 'object') {
files = options
options = dir
dir = null
}
options = options || {}
// prefix must be ASCII code
options.prefix = (options.prefix || '').replace(/\/*$/, '/')
files = new FileManager(files || options.files)
dir = dir || options.dir || process.cwd()
dir = path.normalize(dir)
var enableGzip = !!options.gzip
var filePrefix = path.normalize(options.prefix.replace(/^\//, ''))
// option.filter
var fileFilter = function () {
return true
}
if (Array.isArray(options.filter)) fileFilter = function (file) {
return ~options.filter.indexOf(file)
}
if (typeof options.filter === 'function') fileFilter = options.filter
if (options.preload === true) {
readDir(dir).filter(fileFilter).forEach(function (name) {
loadFile(name, dir, options, files)
})
}
if (options.alias) {
Object.keys(options.alias).forEach(function (key) {
var value = options.alias[key]
if (files.get(value)) {
files.set(key, files.get(value))
debug('aliasing ' + value + ' as ' + key)
}
})
}
return async function mystatic(ctx, next) {
// only accept HEAD and GET
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return await next()
// check prefix first to avoid calculate
if (ctx.path.indexOf(options.prefix) !== 0) return await next();
// decode for `/%E4%B8%AD%E6%96%87`
// normalize for `//index`
var filename = path.normalize(safeDecodeURIComponent(ctx.path))
// 缓存是否可用
if (options.cacheEnable !== true) files.mapInit();
var file = files.get(filename)
// try to load file
if (!file) {
if (!options.dynamic) return await next()
if (path.basename(filename)[0] === '.') return await next()
if (filename.charAt(0) === path.sep) filename = filename.slice(1)
// trim prefix
if (options.prefix !== '/') {
if (filename.indexOf(filePrefix) !== 0) return await next()
filename = filename.slice(filePrefix.length)
}
// 如果指定首页 则设置首页
if (options.index && ctx.header.accept.indexOf('html') >= 0) {
filename = options.index
}
var fullpath = path.join(dir, filename)
// files that can be accessd should be under options.dir
if (fullpath.indexOf(dir) !== 0) {
return await next()
}
let stat;
try {
stat = await fs.stat(fullpath);
} catch (err) {
if (options.index) {
// filename
} else {
}
return await next()
}
if (!stat.isFile()) return await next()
file = loadFile(filename, dir, options, files)
}
ctx.status = 200
if (enableGzip) ctx.vary('Accept-Encoding')
if (!file.buffer) {
var stats = await fs.stat(file.path)
if (stats.mtime > file.mtime) {
file.mtime = stats.mtime
file.md5 = null
file.length = stats.size
}
}
ctx.response.lastModified = file.mtime
if (file.md5) ctx.response.etag = file.md5
if (ctx.fresh)
return ctx.status = 304
ctx.type = file.type
ctx.length = file.zipBuffer ? file.zipBuffer.length : file.length
ctx.set('cache-control', file.cacheControl || 'public, max-age=' + file.maxAge)
if (file.md5) ctx.set('content-md5', file.md5)
if (ctx.method === 'HEAD')
return
var acceptGzip = ctx.acceptsEncodings('gzip') === 'gzip'
if (file.zipBuffer) {
if (acceptGzip) {
ctx.set('content-encoding', 'gzip')
ctx.body = file.zipBuffer
} else {
ctx.body = file.buffer
}
return
}
var shouldGzip = enableGzip &&
file.length > 1024 &&
acceptGzip;
// var shouldGzip = enableGzip &&
// file.length > 1024 &&
// acceptGzip &&
// compressible(file.type)
if (file.buffer) {
if (shouldGzip) {
var gzFile = files.get(filename + '.gz')
if (options.usePrecompiledGzip && gzFile && gzFile.buffer) { // if .gz file already read from disk
file.zipBuffer = gzFile.buffer
} else {
file.zipBuffer = await zlib.gzip(file.buffer)
}
ctx.set('content-encoding', 'gzip')
ctx.body = file.zipBuffer
} else {
ctx.body = file.buffer
}
} else {
var stream = fs.createReadStream(file.path);
// update file hash
if (!file.md5) {
var hash = crypto.createHash('md5')
stream.on('data', hash.update.bind(hash))
stream.on('end', file.md5 = hash.digest('base64'))
}
ctx.body = stream
// enable gzip will remove content length
if (shouldGzip) {
ctx.remove('content-length')
ctx.set('content-encoding', 'gzip')
ctx.body = stream.pipe(zlib.createGzip())
// 交给下面执行
}
await next();
}
}
}
function safeDecodeURIComponent(text) {
try {
return decodeURIComponent(text)
} catch (e) {
return text
}
}
/**
* load file and add file content to cache
*
* @param {String} name
* @param {String} dir
* @param {Object} options
* @param {Object} files
* @return {Object}
* @api private
*/
function loadFile(name, dir, options, files) {
var pathname = path.normalize(path.join(options.prefix, name))
if (!files.get(pathname)) files.set(pathname, {})
var obj = files.get(pathname)
var filename = obj.path = path.join(dir, name)
var stats = fs.statSync(filename)
var buffer = fs.readFileSync(filename)
obj.cacheControl = options.cacheControl
obj.maxAge = obj.maxAge ? obj.maxAge : options.maxAge || 0
obj.type = obj.mime = mime.lookup(pathname) || 'application/octet-stream'
obj.mtime = stats.mtime
obj.length = stats.size
obj.md5 = crypto.createHash('md5').update(buffer).digest('base64')
debug('file: ' + JSON.stringify(obj, null, 2))
if (options.buffer)
obj.buffer = buffer
buffer = null
return obj
}