这篇文章参考http-server从零实现一个自己的http-server
命令行工具
准备工作
跟控制台的交互,这里用到的是 commander
和 chalk
:
对于http
服务中文件类型的判断这里用到mime
,内容模板使用ejs
:
在本地开发npm模块的时候,可以使用npm link
命令,将npm
模块链接到对应的运行项目中,方便地对模块进行调试和测试。
新建文件夹http-server
,在文件根目录下执行命令npm init -y
,生成package.json
文件,给package.json
新增bin
命令:
// package.json
{
"name": "http-server",
"version": "1.0.0",
"license": "MIT",
"bin": {
"my-http-server": "./bin/www.js"
}
}
在控制台执行npm link
可以将全局的my-http-server
命令指向这个目录了,当然名称可以根据自己的需要更换,路径./bin/www.js
是执行这个命令的文件。
接着就可以到想启动的http服务的文件目录下,打开控制台执行my-http-server
启动我们的命令了,不过在这之前还要先创建www.js
文件,我们执行的命令,实际是执行node www.js
。
创建:
$ mkdir bin
$ touch mkdir/www.js
为了让文件能够以nodejs的环境执行,www.js
文件开头需要输入声明:
#! /usr/bin/env node
配置项
开始之前,还需要新建配置项,在http-server
文件夹下新建config.js
输入配置内容:
let config = {
port: 3000, // 端口号
host: "127.0.0.1", // 启动路径
dir: process.cwd() // 命令行执行目录
}
module.exports = config;
通过commander
可以让用户方便的跟控制台交互,修改bin/www.js
文件下的内容如下:
#! /usr/bin/env node
let config = require("../config");
const commander = require("commander");
const {version} = require("../package.json");
commander
.version(version)
.option("-p --port ", "set http-server port") // 端口号
.option("-0 --host ", "set http-server host") // 主机路径
.option("-d --dir ", "set http-server directory") // 服务启动路径
.on("--help",()=>{
console.log("Example");
console.log(" $ my-http-server --port --host");
})
.parse(process.argv)
// 拿到用户跟控制台交互后的命令,合并到配置文件中
config = {...config,...commander};
保存文件,在控制台下输入my-webpack-server -h
就可以看到对应的交互信息了。
createServer
在http-server
文件夹下新建server.js
,创建Server
类,封装httpcreateServer
方法:
- server.js
const http = require("http")
module.exports = class Server {
constructor(config){
this.dir = config.dir
this.port = config.port
this.host = config.host
}
handleRequest(req,res){}
start(){
const server = http.createServer(this.handleRequest.bind(this))
server.listen(this.port, this.host, () => {
// 成功后回调函数
})
}
}
为了能在启动服务后,在控制台输出多色彩的字符串信息,我们可以利用chalk
这个依赖包,我们添加下面代码到server.js
:
const chalk = require("chalk")
...
server.listen(this.port, this.host, () => {
console.log(chalk.yellow(`Starring up http-server,\r\nserving ${this.dir} \r\n Available on \r\n`))
console.log(chalk.green(`http://${this.host}:${this.port}`))
})
...
handleRequest 处理请求
接下来开始封装请求方法,handleRequest
函数主要做下面的工作:
- 获取
requests
的url路径 - 判断路径是文件还是文件夹
fs.stat
- 若路径不存在对应文件或文件夹,则返回404
- 文件则判断文件mime类型,响应文件信息
- 文件夹判断文件夹里面是否有
index.html
文件,有则渲染index.html
,否则利用ejs
模板渲染文件列表
const url = require("url")
const ejs = require("ejs")
const mime = require("mime")
const fs = require("mz/fs")
// 模板文件
const template = fs.readFileSync(
path.resolve(__dirname, "./template.ejs"),
"utf-8"
)
...
constructor(){...}
async handleRequest(req, res) {
this.req = req
this.res = res
// 通过url解析路径名
const { pathname } = url.parse(req.url, true)
// 拿到文件绝对路径,decodeURIComponent 防止中文符号无法识别
const absPath = path.join(this.dir, decodeURIComponent(pathname))
try {
// 判断是文件还是文件夹
const statObj = await fs.stat(absPath)
if (statObj.isDirectory()) {
let indexPath = absPath+"/index.html";
// 如果文件夹里面有index.html 则进入
fs.access(indexPath)
.then(this.sendFile.bind(this, indexPath))
.catch(async () => {
// 模板文件过滤掉.DS_Store,并加上href链接
let files = await fs.readdir(absPath)
files = files
.filter((f) => f !== ".DS_Store")
.map((file) => ({ file, href: path.join(pathname, file) }))
let str = ejs.render(template, {
name: path.basename(pathname) || "文件列表",
files,
});
res.setHeader("Content-Type", "text/html;charset=utf-8")
res.end(str)
})
} else {
// 文件直接返回
this.sendFile(absPath)
}
} catch (error) {
this.sendError(error)
}
}
sendError(error) {
this.res.statusCode = 404;
// 返回错误信息,生产环境需要屏蔽
this.res.end(error.toString())
}
sendFile(filePath) {
// 判断文件mime类型
this.res.setHeader(
"Content-Type",
mime.getType(filePath) + ";charset=utf-8"
)
// 创建文件可读流并返回
fs.createReadStream(filePath).pipe(this.res)
}
start(){}
模板文件
模板文件渲染文件夹的文件列表
// template.ejs
<% files.forEach(function(item){ %>
- <%=item.file%>
<% }) %>
引入
最后www.js
文件引入Server
类,初始化并渲染:
const Server = require("../server")
let server = new Server(config);
server.start()
找个想启动http服务的文件夹打开控制台,输入my-http-server
就可以启动服务了。
发布npm包
在 http-server
文件夹下打开控制台,输入下面指令:
- 如果你是第一次发包,使用
npm adduser
- 如果不是第一次发,使用
npm login
输入账号密码和注册邮箱
username:yourname
password:
Email:(this is public)[email protected]
确保是在package.json
同级目录,然后执行npm publish
发布:
$ npm publish