nodejs里面用的比较多的即使fs和http。写一个静态资源的demo记录。
文件目录
app.js 入口
const http = require('http') //http网络请求
const chalk = require('chalk') //打印时的字体颜色
const path = require('path')
const conf = require('./config/defaultConfig') //配置文件
const router = require('./helper/router') //处理事件的路由
const openURL = require('./helper/openUrl') //监听请求后,自动打开网页
class Server {
constructor(config) {
this.conf = Object.assign({}, conf, config) //传入参数,合并配置的文件默认参数
}
start() {
const server = http.createServer((req, res) => { //创建请求服务step1
const filePath = path.join(this.conf.root, req.url)
router(req, res, filePath, this.conf)
})
server.listen(this.conf.port, this.conf.hostname, () => { //监听请求服务step2
const addr = `http://${this.conf.hostname}:${this.conf.port}`
console.log(`Server started at ${chalk.green(addr)}`);
openURL(addr); //自动打开网页,后写
})
}
}
module.exports = Server复制代码
router.js 做具体处理
const fs = require('fs'); //文件
const path = require('path')
const util = require('util'); //
require('util.promisify').shim(); //获得util.promisify
const stat = util.promisify(fs.stat); //制造异步调用函数
const readdir = util.promisify(fs.readdir); //封装异步调用函数
const mime = require('./mime') //数据类型mime文件
const Handlebars = require('handlebars'); //文件模板
const compress = require('./compress') //代码压缩
const range = require('./range') //http中的范围请求
const isFresh = require('./cache') //缓存处理
const tplPath = path.join(__dirname, '../template/dir.tpl'); //模板路径
const source = fs.readFileSync(tplPath); //读取原始模板文件
const template = Handlebars.compile(source.toString()); //原始模板填充数据
module.exports = async function (req, res, filePath,config) {
try {
const stats = await stat(filePath) //执行异步调用,获取文件状态
if (stats.isFile()) {
const contentType = mime(filePath); //判断文件类型
//console.log('contenttype===' + contentType)
res.setHeader('Content-Type', contentType); //设置返回头,文本类型
if (isFresh(stats, req, res)) { //判断有没有缓存,缓存是否有用
res.statusCode = 304;
res.end();
return;
}
fs.readFile(filePath, (err, data) => { //读取具体文件
res.end(data) //将文件返回
})
const {code, start, end} = range(stats.size, req, res);
let rs = fs.createReadStream(filePath); //创建读 文件流
if (code === 200) {
res.statusCode = 200;
rs = fs.createReadStream(filePath); //创建读文件流
} else {
res.statusCode = 206;
rs = fs.createReadStream(filePath, {start, end})//创建range读文件流&&important
}
if (filePath.match(config.comporess)) { //判断 compress压缩格式
rs = compress(rs, req, res); //进行文件压缩
}
rs.pipe(res); //文件输出
} else if (stats.isDirectory()) {
const files = await readdir(filePath)
res.statusCode = 200;
res.setHeader('Content-Type', "text/html");
const dir = path.relative(config.root, filePath);
const data = {
title: path.basename(filePath),
dir: dir ? `/${dir}` : "",
files: files.map(file => {
return {
file,
icon: mime(file)
}
})
};
res.end(template(data))
}
} catch (ex) {
res.statusCode = 404;
res.setHeader('ContentType', 'text/plain');
console.log(ex)
res.end(`${filePath} is not a directory or file`);
}
}复制代码
上面两个文件是核心,创建server服务端,监听请求,router负责做具体处理。
-------------------------------------------------------------------------------------------
- 模板文件tpl
- 代码压缩compress
- 缓存处理cache
- 设置http请求范围 range
配置工具文件
dir.tpl模板文件
"en">
"UTF-8">
"viewport" content="width=device-width">
"X-UA-Compatible" content="ie=edge">
{{title}}
{{#each files}}
"{{../dir}}/{{file}}">[{{icon}}]---{{file}}
{{/each}}
复制代码
defaultConfig.js默认配置文件
module.exports = {
root: process.cwd(),
hostname: '127.0.0.1',
port: 9527,
comporess: /\.(html|js|css|md)/
}复制代码
compress.js--代码压缩文件
const {createGzip, createDeflate} = require('zlib')
module.exports = (rs, req, res) => {
const acceptEncoding = req.headers['accept-encoding'];
if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
return rs;
} else if (acceptEncoding.match(/\bgzip\b/)) {
res.setHeader('Content-Encoding', 'gzip');
return rs.pipe(createGzip());
} else if (acceptEncoding.match(/\bgzip\b/)) {
res.setHeader('Content-Encoding', 'deflate');
return rs.pipe(createDeflate());
}
};
复制代码
cache.js缓存处理文件
const {cache} = require('../config/defaultConfig')
function refreshRes(stats, res) {
const {maxAge, expires, cacheControl, lastModified, etag} = cache;
if (expires) { //强缓存标签,http相关
res.setHeader('Expires', (new Date(Date.now() + maxAge * 1000)).toUTCString());
}
if (cacheControl) { //强缓存标签,http相关
res.setHeader('Cache-Control', `public ,max-age=${maxAge}`);
}
if (lastModified) { //协商缓存
res.setHeader('last-Modified', stats.mtime.toUTCString());
}
if (etag) { //协商缓存
res.setHeader('ETag', `${stats.size}-${stats.mtime}`);
}
}
module.exports = function ifFresh(stats, req, res) {
refreshRes(stats, res);
const lastModified = req.headers['if-modified-since'];
const etag = req.headers['if-none-match'];
if (!lastModified && lastModified !== res.getHeader('LastModified')) {
return false;
}
if (etag && etag !== res.getHeader('ETag')) {
return false;
}
return true;
}复制代码
rang.js 范围请求
module.exports = (totalSize, req, res) => {
const range = req.headers['range'];
if (!range) {
return {code: 200}
}
const sizes = range.match(/bytes=(\d*)-(\d*)/);
const end = sizes[2] || totalSize - 1;
const start = sizes[1] || totalSize - end;
if (start > end || start < 0 || end > totalSize) {
return {code: 200}
}
res.setHeader('Accept-Ranges', 'bytes');
res.setHeader('Content-Range', `byte ${start}-${end}/${totalSize}`); //http中请求中配置
res.setHeader('Content-Length', end - start);
return {
code: 206,
start: parseInt(start),
end: parseInt(end)
}
}复制代码
mime.js 数据类型判断文件
const path = require('path')
const mimeTypes = {
"3gp": "video/3gpp",
"gif": "image/gif",
"htm": "text/html",
"html": "text/html",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "application/x-javascript",
"log": "text/plain",
"mp3": "audio/x-mpeg",
"xml": "text/plain",
"css": "text/css",
"ico": "image/x-icon",
"json": "application/json",
"pdf": "application/pdf",
"png": "image/png",
"svg": "image/svg+xml",
"swf": "application/x-shockwave-flash",
"tiff": "image/tiff",
"txt": "text/plain",
"wav": "audio/x-wav",
"wma": "audio/x-ms-wma",
"wmv": "video/x-ms-wmv",
}
module.exports = (filePath) => {
let ext = path.extname(filePath)
.split('.')
.pop()
.toLowerCase();
if (!ext) {
ext = filePath
}
return mimeTypes[ext] || mimeTypes['txt']
}复制代码
openUrl.js
const {exec} = require('child_process'); //引child_process包
module.exports = url => {
switch (process.platform) {
case 'win32':
exec(`start ${url}`);
break;
case 'win64':
exec(`start ${url}`); //调一下start方法,打开url
break;
}
};复制代码
不重要技能---命令框参数配置 ,用开源包yargs
const yargs = require('yargs')
const Server = require('./app')
const argv = yargs
.usage('anywhere [options]')
.options('p', {
alias: 'port',
describe: '',
default: 9527
})
.options('h', {
alias: 'hostname',
describe: '',
default: "127.0.0.1",
})
.options('d', {
alias: 'root',
describe: 'root path',
default: process.cwd()
})
.version()
.alias('v', 'version')
.help()
.argv
const server = new Server(argv);
server.start();复制代码
package.json中,加入
"main": "src/app.js",
"bin": {
"anydoor": "bin/index.js"
},复制代码
bin/anydoor -p 9999
----------------------------------我就是大名鼎鼎的分割线------------------------------------
工具
npm install --save cherrio //node端调用代码,如前端jquery, 缺点:无法绕过反爬虫
npm install supervisor //监听代码变化
核心
stream buffer http
动态web framework
child_process & cluster
深入学习
throught2
express、koa、egg
ssr && 同构
源码