koa使用koa-staitc来处理静态文件
其核心使用koa-send组件来完成,对齐文件进行解析,它对文件进行了几项处理
1、对组件root目录,和请求路径进行合并,并对返回文件头进行处理,主要针对.br 和.gzapp.use(static(你要制定的静态文件根目录)) 。
所以你要请求的路径中不能带入跟目录相关字段,直接请求文件即可,请求早于其他使用router包请求处理,所以尽量不要和后面的非静态路径请求冲突。
以下最主要就一句ctx.body = fs.createReadStream(path),加上文件头就是核心。
/**
* Module dependencies.
*/
const debug = require('debug')('koa-send')
const resolvePath = require('resolve-path')
const createError = require('http-errors')
const assert = require('assert')
const fs = require('mz/fs')
const {
normalize,
basename,
extname,
resolve,
parse,
sep
} = require('path')
/**
* Expose `send()`.
*/
module.exports = send
/**
* Send file at `path` with the
* given `options` to the koa `ctx`.
*
* @param {Context} ctx
* @param {String} path
* @param {Object} [opts]
* @return {Function}
* @api public
*/
async function send (ctx, path, opts = {}) {
assert(ctx, 'koa context required')
assert(path, 'pathname required')
// options
debug('send "%s" %j', path, opts)
const root = opts.root ? normalize(resolve(opts.root)) : ''
const trailingSlash = path[path.length - 1] === '/'
path = path.substr(parse(path).root.length)
const index = opts.index
const maxage = opts.maxage || opts.maxAge || 0
const immutable = opts.immutable || false
const hidden = opts.hidden || false
const format = opts.format !== false
const extensions = Array.isArray(opts.extensions) ? opts.extensions : false
const brotli = opts.brotli !== false
const gzip = opts.gzip !== false
const setHeaders = opts.setHeaders
if (setHeaders && typeof setHeaders !== 'function') {
throw new TypeError('option setHeaders must be function')
}
// normalize path
path = decode(path)
if (path === -1) return ctx.throw(400, 'failed to decode')
// index file support
if (index && trailingSlash) path += index
path = resolvePath(root, path)
// hidden file support, ignore
if (!hidden && isHidden(root, path)) return
let encodingExt = ''
// serve brotli file when possible otherwise gzipped file when possible
if (ctx.acceptsEncodings('br', 'identity') === 'br' && brotli && (await fs.exists(path + '.br'))) {
path = path + '.br'
ctx.set('Content-Encoding', 'br')
ctx.res.removeHeader('Content-Length')
encodingExt = '.br'
} else if (ctx.acceptsEncodings('gzip', 'identity') === 'gzip' && gzip && (await fs.exists(path + '.gz'))) {
path = path + '.gz'
ctx.set('Content-Encoding', 'gzip')
ctx.res.removeHeader('Content-Length')
encodingExt = '.gz'
}
if (extensions && !/\.[^/]*$/.exec(path)) {
const list = [].concat(extensions)
for (let i = 0; i < list.length; i++) {
let ext = list[i]
if (typeof ext !== 'string') {
throw new TypeError('option extensions must be array of strings or false')
}
if (!/^\./.exec(ext)) ext = '.' + ext
if (await fs.exists(path + ext)) {
path = path + ext
break
}
}
}
// stat
let stats
try {
stats = await fs.stat(path)
// Format the path to serve static file servers
// and not require a trailing slash for directories,
// so that you can do both `/directory` and `/directory/`
if (stats.isDirectory()) {
if (format && index) {
path += '/' + index
stats = await fs.stat(path)
} else {
return
}
}
} catch (err) {
const notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR']
if (notfound.includes(err.code)) {
throw createError(404, err)
}
err.status = 500
throw err
}
if (setHeaders) setHeaders(ctx.res, path, stats)
// stream
ctx.set('Content-Length', stats.size)
if (!ctx.response.get('Last-Modified')) ctx.set('Last-Modified', stats.mtime.toUTCString())
if (!ctx.response.get('Cache-Control')) {
const directives = ['max-age=' + (maxage / 1000 | 0)]
if (immutable) {
directives.push('immutable')
}
ctx.set('Cache-Control', directives.join(','))
}
if (!ctx.type) ctx.type = type(path, encodingExt)
ctx.body = fs.createReadStream(path)
return path
}
/**
* Check if it's hidden.
*/
function isHidden (root, path) {
path = path.substr(root.length).split(sep)
for (let i = 0; i < path.length; i++) {
if (path[i][0] === '.') return true
}
return false
}
/**
* File type.
*/
function type (file, ext) {
return ext !== '' ? extname(basename(file, ext)) : extname(file)
}
/**
* Decode `path`.
*/
function decode (path) {
try {
return decodeURIComponent(path)
} catch (err) {
return -1
}
}
网友评论