实现静态服务器
本节内容实现一个简单的静态文件服务组件 serveStatic,实现对静态资源的请求处理。代码位于文件 serve/static.js
中。
module.exports = (config) => {
let serveStatic = (stream, headers, next) => _serveStatic(stream, headers, next, config)
return serveStatic
}
该模块参数为 config 配置组件对象,返回 serveStatic 组件,该组件实现基于 _serveStatic 方法。
function _serveStatic(stream, headers, next, config) {
// 请求静态文件路径
const reqPath = headers[HTTP2_HEADER_PATH]
// 请求以 /static 开头(可配置)代表静态资源请求
if(reqPath.split(path.sep)[1] === config.staticDir) {
console.debug(
'server.onStream-static',
stream.id, stream._context.sid,
stream._context.reqid)
// 获取静态文件相对 serverRoot 的绝对路径
const fullPath = path.join(config.serverRoot, reqPath)
// 获取请求文件类型,得出 content-type
const responseMimeType = mime.lookup(fullPath)
// 读取文件进行响应
stream.safe_respondWithFile(fullPath, { 'content-type': responseMimeType }, {
waitForTrailers: true,
statCheck: (stat, resHeaders) => {
resHeaders['last-modified'] = stat.mtime.toUTCString()
resHeaders['cache-control'] = 'public, max-age=0'
if(Date.parse(headers['if-modified-since']) === Date.parse(stat.mtime)) {
stream.safe_respond({ ':status': HTTP_STATUS_NOT_MODIFIED })
stream.end()
next(stream, headers)
return false; // Cancel the send operation
}
},
onError: (err) => {
console.debug(
'server.onStream-static.respondToStreamError',
stream.id,
stream._context.sid,
stream._context.reqid,
err)
if (err.code === 'ENOENT') {
stream.safe_respond({ ':status': HTTP_STATUS_NOT_FOUND })
} else {
stream.safe_respond({ ':status': HTTP_STATUS_NOT_FOUND })
}
stream.end()
next(stream, headers, err)
}
})
stream.on('wantTrailers', () => {
console.debug(
'server.onStream-static.wantTrailers',
stream.id,
stream._context.sid,
stream._context.reqid)
next(stream, headers)
stream.close()
})
return true
}
return false
}
代码上加了注释,整体上比较简单,值得一提的地方是 statCheck 回调。会在读取文件发送回浏览器之前做检查,如果从浏览器上次访问之后没有修改,则返回 304 给浏览器,并且不需返回文件内容。
statCheck: (stat, resHeaders) => {
resHeaders['last-modified'] = stat.mtime.toUTCString()
resHeaders['cache-control'] = 'public, max-age=0'
if(Date.parse(headers['if-modified-since']) === Date.parse(stat.mtime)) {
stream.safe_respond({ ':status': HTTP_STATUS_NOT_MODIFIED })
stream.end()
next(stream, headers)
return false; // Cancel the send operation
}
}
上述代码会返回该文件 last-modified 时间给浏览器,并告诉浏览器缓存策略 cache-control 为,在确定使用缓存前来服务器请求确认下。浏览器在请求资源时,请求头中携带 if-modified-since,时间是服务器返回的 last-modified,如果服务器返回 304,则可以直接使用缓存文件。否则服务器会返回新的文件内容。