背景
- 静态目录服务很多都是有打包好的。但是写代码就怕不想写,写这个文章就是逼着自己不用人家封装好的写一个。
流程
- 需要第三方模块,mime,chalk,nunjucks,commander。npm安装一下即可。
一、建个bin目录,下面放www文件,编写入口。
#! /usr/bin/env node
let program = require('commander')
let config = {
"-p,--port <val>":"set http-server port",
"-d,--dir <dir>":"set http-server directory"
}
Object.entries(config).forEach(([key,value])=>{
program.option(key,value)
})
program.name('ye-static').usage("<options>")
program.on("--help",()=>{
console.log('Examples:');
console.log('$ye-static --port 3000');
})
let obj =program.parse(process.argv)
let Server=require('../static-server')
let defaultConfig = {
port:3000,
dir:process.cwd(),
...obj
}
let server = new Server(defaultConfig)
server.start()
- 首先把命令行做出来,添加个–help例子,解析用户输入的端口号和目录
- 配置package.json,加入Bin,这样就可以全局调用了。
{
"name": "staticserver",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^3.0.0",
"commander": "^4.0.1",
"mime": "^2.4.4",
"nunjucks": "^3.2.0"
},
"bin": {
"ye-static": "./bin/www"
}
}
- 然后把server类写出个start方法就可以调用了。
class Server{
constructor(config){
this.port = config.port
this.dir = config.dir
}
start(){
console.log('xzcxz');
console.log(this.port,this.dir);
}
}
module.exports =Server
- 在命令行使用npm link 变成全局包,然后使用配置的bin命令进行调试。
- 如果能运行文件就可以下一步。
- 下一步需要写让http服务运行起来,只要在start里加上下面2句即可
let server = http.createServer()
server.listen(this.port)
- 然后需要做静态服务,就要监听访问,可以在http.createServer里传入函数,即可监听访问。
let server = http.createServer(this.handleRequest.bind(this))
- 这个handleRequest函数也是写在Server下,跟start同级,这样也方便拿port和dir。
- 下面就是写静态服务,思路是这样:分为是文件还是是目录,是目录就列出孩子然后拿模板渲染出来,是文件就通过流返回给页面。同时需要注意下文件路径和文件类型,文件类型调用mime写好的来看。
- req.url通过url.parse解码出的pathname是相对于网站根路径的,用来操作子url。
- abspath就是电脑上的绝对路径,fs通过绝对路径查是目录还是文件,或者是查看目录下孩子。
const http= require('http')
const url = require('url')
const path = require('path')
const fs = require('fs').promises
const {createReadStream}=require('fs')
const mime=require('mime')
const chalk = require('chalk')
const nunjucks=require('nunjucks')
class Server{
constructor(config){
this.port = config.port
this.dir = config.dir
}
sendFile(currentpath,res){
res.setHeader('Content-Type',mime.getType(currentpath)+';charset=utf8')
createReadStream(currentpath).pipe(res)//是文件就做成流返回去
}
async handleRequest(req,res){
let {pathname}=url.parse(req.url)//中文名会自动编码
pathname=decodeURIComponent(pathname)//解码中文名
let absPath =path.join(this.dir,pathname)
try{
let statObj= await fs.stat(absPath)//判断路径是目录还是文件
if(statObj.isFile()){//是文件
this.sendFile(absPath,res)
}else{//列出所有文件夹的内容
let children =await fs.readdir(absPath)
children= children.map((item)=>{
return{
current:item,
parent :path.join(pathname,item)
}
})
nunjucks.configure(path.resolve(__dirname))
let template = nunjucks.render('template.html',{
items:children
})
res.setHeader('Content-Type','text/html;charset=utf8')
res.end(template)
}
}catch(e){
console.log(e);
this.sendError(res)
}
}
sendError(res){
res.statusCode=404
res.end('Not Fond')
}
start(){
let server = http.createServer(this.handleRequest.bind(this))
server.listen(this.port,()=>{
console.log(`${chalk.yellow('Starting up http-server, serving') }./${this.dir.split('\\').pop()}
Available on:
http://127.0.0.1:${chalk.green(this.port)}
Hit CTRL-C to stop the server`)
})
}
}
module.exports =Server
- 模板如下,很简单就是个列表循环。这个模板自称继承jinjia2的衣钵,语法和jinjia2是一样的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>
{% for item in items %}
<li><a href="{{item.parent}}">{{ item.current }}</a></li>
{% endfor %}
</ul>
</body>
</html>
- 最后进行发布
- 这个nunjunks如果不设置configure的绝对路径容易找不到模板,虽然文档写默认是当前目录,但实际路径对的但可能内部处理有bug,必须设置下绝对路径。
- 这个包我发到npm上,有需要自取:
npm i ye-staticserver -g
启动直接ye-static
默认3000端口当前目录。