Node.js做静态资源服务器

2022-09-14 15:11:40 浏览数 (1)

在上一篇文章介绍了Node.js基础API 接下来我们做一个案例,用Node.js实现静态资源服务器

目录结构

首先新键如下目录结构

config:存放一些配置文件

helper:辅助文件

template:模板文件(后面会使用到模板引擎)

app.js:入口文件

搭建服务

我们要根据客户端请求的url返回相应的文件/目录信息,所以我们要先搭建服务

使用http模块搭建

代码语言:javascript复制
const http = require('http');
const conf = require('./config/defaultConf')
const server = http.createServer((req,res)=>{
    res.statusCode = 200
    res.setHeader('Content-Type','text/plain')
    res.end('hello word')

})

server.listen(conf.port,conf.hostname,()=>{
    console.info('server start')
})

配置文件

代码语言:javascript复制
module.exports = {
    root:process.cwd(),
    hostname :'127.0.0.1',
    port:'3000'
}

要做一个资源服务器首先我们得获取到用户请求的url,得到url后将当前node执行的目录与url进行拼接

代码语言:javascript复制
const path = require('path')
...
const server = http.createServer((req,res)=>{
    res.statusCode = 200
    res.setHeader('Content-Type','text/plain')
    const filePath = path.join(conf.root,req.url)

})
...

得到url后有三种情况

1.url指向某个文件

2.url指向某个目录

3.不存在的路径

当url指向某个文件时我们直接返回,当指向某个目录时,我们将该目录的文件全部列出,并且实现超链接,当没有该目录或文件时返回提示信息“没有该文件”

接下来通过代码实现 我们将这部分逻辑写进helper/route.js

代码语言:javascript复制
const fs = require('fs')
const path = require('path')
const {promisify} = require('util')
const stat = promisify(fs.stat)
const readdir = promisify(fs.readdir)
const conf = require('../config/defaultConf');
const mime = require('../helper/mime')
module.exports = async function(req,res,filepath){
    try{
        const stats = await stat(filepath)//异步
        if(stats.isFile()){
            //文件
            //根据文件类型返回相应mime
            const contentType = mime(filepath)
            res.statusCode = 200
            res.setHeader('Content-type',contentType)
            fs.createReadStream(filepath).pipe(res)//读取文件流并返回
        }else if(stats.isDirectory()){
            //目录
            //1.读取该目录所有内容
            const files = await readdir(filepath)
            res.statusCode = 200
            res.setHeader('Content-Type','text/html')
            res.end(files.join(','))

        }
    }catch(e){
        console.log(e)

    }
}

在route.js中我们引入promisify模块引入,将相关文件操作封装成promise对象,这样可以使我们在读取文件时不用进行各种回调,通过async与await时同步的方式去做异步的事情。 我们还引入了自定义模块mime这个模块放置了文件类型对应的contentType,以确保我们发送给客户端正确的contentType helper/mime.js

代码语言:javascript复制
const path = require('path')
const mimeTypes = {
    'css':'text/css',
    'gif':'image/gif',
    'html':'text/html',
    'ico':'image/x-ico',
    'jpeg':'image/jpeg',
    'jpg':'image/jpeg',
    'js':'text/javascript',
    'json':'application/json',
    'pdf':'application/pdf',
    'png':'image/png',
    'svg':'image/svg xml',
    'txt':'text/plain',
}

module.exports = (filePath)=>{
    let ext = path.extname(filePath)
    .split('.')
    .pop()
    .toLocaleLowerCase();
    if(!ext){
        ext = filePath;
    }
    return mimeTypes[ext] || mimeTypes['txt']
}

到此我们已经可以根据url返回文件,目录了

但是不够美观关,且没有超链接,如点击目录跳转该目录的内容

针对这个问题我们可以通过模板引擎实现 1.引入模板引擎 这里我们使用handlebars

代码语言:javascript复制
cnpm i handlebars

2.在route.js引入

代码语言:javascript复制
const fs = require('fs')
const path = require('path')
const {promisify} = require('util')
const stat = promisify(fs.stat)
const readdir = promisify(fs.readdir)
const conf = require('../config/defaultConf');
const mime = require('../helper/mime')
const Handlebars = require('handlebars')//引入模板引擎
const tplPath = path.join(__dirname,'../template/dir.tpl')
const source = fs.readFileSync(tplPath,'utf-8')//引入模板文件
const template = Handlebars.compile(source)

module.exports = async function(req,res,filepath){
    try{
        const stats = await stat(filepath)//异步
        if(stats.isFile()){
            //文件
            //根据文件类型返回相应mime
            const contentType = mime(filepath)
            res.statusCode = 200
            res.setHeader('Content-type',contentType)
            fs.createReadStream(filepath).pipe(res)//读取文件流并返回
        }else if(stats.isDirectory()){
            //目录
            //1.读取该目录所有内容
            const files = await readdir(filepath)//
            res.statusCode = 200
            res.setHeader('Content-Type','text/html')
            const dir = path.relative(conf.root,filepath)//该目录的相对路径
            const data = {
                // files,
                // title:path.basename(filePath),
                // dir:dir?`/${dir}`:''
                files:files.map(file=>{
                    return {
                        file,
                        icon:mime(file)
                    }
                }),
                title:path.basename(filepath),
                dir:dir?`/${dir}`:''

            }
            res.end(template(data))

        }
    }catch(e){
        console.log(e)

    }
}

浏览器访问如下

此外我们还可以对静态资源进行压缩,提高访问速度 在配置文件设置可以被压缩的文件

代码语言:javascript复制
module.exports = {
    root:process.cwd(),
    hostname :'127.0.0.1',
    port:'3000',
    compress:/.(html|js|css|md)/
}

新键helper/compress.js

代码语言:javascript复制
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(/bgzipb/)){
        res.setHeader('Content-Encoding','gzip')
        return rs.pipe(createGzip())
    }else if(acceptEncoding.match(/bdeflateb/)){
        res.setHeader('Content-Encoding','deflate')
        return rs.pipe(createDeflate())
    }
     
}

修改route.js

代码语言:javascript复制
const compress = require('../helper/compress')
...
 const contentType = mime(filePath)
            res.statusCode = 200
            res.setHeader('Content-type',contentType)
            let rs = fs.createReadStream(filePath)
            //压缩文件
            if(filePath.match(conf.compress)){
                rs =compress(rs,req,res)
            }
            rs.pipe(res)
...

0 人点赞