在上一篇文章介绍了Node.js基础API 接下来我们做一个案例,用Node.js实现静态资源服务器
目录结构
首先新键如下目录结构
config:存放一些配置文件
helper:辅助文件
template:模板文件(后面会使用到模板引擎)
app.js:入口文件
搭建服务
我们要根据客户端请求的url返回相应的文件/目录信息,所以我们要先搭建服务
使用http
模块搭建
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
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)
...