nodejs实现一静态资源服务器

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 &&  同构

源码


你可能感兴趣的:(nodejs实现一静态资源服务器)