手写node的http-server插件
1.http-server介绍
有时候网页地址栏需要用到http协议,这个时候就可以通过http-server
这个插件来达到效果。终端工具输入npm i -g http-server就可以全局安装http-server
了。
npm i -g http-server
复制代码
安装之后在需要打开的文件的目录下输入http-server
就可以开启http
协议啦。
比如我在一个项目目录下打开命令行
输入http-server启动服务
然后打开这个http://127.0.0.1:8081/就能看到这样的效果,当前文件夹目录解构及文件。
这是一个最简单的用法场景,他还能配置许多选项。例如 http-server --port 3000可以设置服务的端口号,还可以用 --directory来指定目录等等,详细用法请参考GitHub:github.com/http-party/…
2.代码实现
1.需求分析和实现
- 在
package.json
里面配置bin
字段,然后用npm link
连接到全局npm
包,这样就可以在全局用自己定义的命令了,可以用粉笔工具chalk
设置提示的文件颜色 - 引入
commander
包接受用户在命令行输入的参数来覆盖默认参数,设置命令行提示、颜色等内容。 - 用
http-server
创建一个服务,根据请求路径读取目录,判断是文件还是文件夹,如果是文件直接返回对应的文件,否则返回一个当前目录结构的html
。返回的html
可以用ejs
这个包根据模板生成,不同类型的文件需要设置不同的响应头Content-Type
类型,可以用mime
这个包解析出来。
2.代码实现
1.初始化项目
npm init -y
复制代码
然后把项目名字改成my-http-server
,在package.json
里面配置bin
字段,增加一个bin
文件夹,里面新建www
和config.js
文件,bin
目录是工具的启动文件目录,后面执行my-hs
命令是直接执行www
里面的代码。然后新建一个src
目录并新增server.js
文件,用来处理客户端的请求和响应相关逻辑,新建template.html
用于处理路径下是目录要返回该目录的html
情况。然后安装chalk,commander,ejs,mime
这个4个需要用到的包。
目录结构如下:
|-bin
| |-config.js ------------- 服务默认配置
| |-www -------------服务启动文件
|
|-src
| |-server.js --------- 服务逻辑处理
| |-template.html ---------- html模板,用于渲染文件夹目录结构
|
|-package.json
复制代码
package.json
{
"name": "my-hs",
"version": "1.0.0",
"description": "",
"bin": {
"my-hs": "./bin/www"
},
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^4.1.0",
"commander": "^6.2.1",
"ejs": "^3.1.5",
"mime": "^2.4.7"
}
}
复制代码
config.js 默认配置文件
// 自定义需要显示到命令行中的命令
const config = { // 给自己来维护参数的
'port':{
option:'-p,--port <n>', // <v> 表示时一个值
descriptor:'set your server port',
default: 8080,
usage:'my-hs --port <n>'
},
'directory':{
option:'-d,--directory <n>',
descriptor:'set your server start directory',
default: process.cwd(),
usage: 'my-hs --directory <n>'
},
'cache':{
option:'-c,--cache <n>',
descriptor:'set your server cache',
default:'no-cache',
usage: 'my-hs --cache <n>'
}
}
module.exports = config;
复制代码
www文件
注意:文件头部的#! /usr/bin/env node
是固定写法,告诉系统这个文件要用node来执行,
#! /usr/bin/env node
const program = require('commander');
const chalk = require('chalk');
const config = require('./config');
const Server = require('../src/server')
// 配置使用的名字
program.name('my-hs');
// 默认的配置对象
const defaultConfig = {};
// 使用样例集合
const usageList = [];
// 遍历配置到当前的程序里面
Object.entries(config).forEach(([key, value]) => {
defaultConfig[key] = value.default;
usageList.push(value.usage)
program.option(value.option, value.descriptor);
});
// 监听--help事件,在命令行显示样例
program.on('--help',function () {
console.log('Examples:');
usageList.forEach(line=>{
console.log(` ${chalk.green(line)} \r`);
})
})
// 解析用户执行时的参数
program.parse(process.argv);
// 根据用户的参数 和 默认值 做出一个配置来
function mergeOtions(defaultConfig,newConfig){
const options = {}
for(let key in defaultConfig){
if(!(key in newConfig)){
options[key] = defaultConfig[key]
}else{
// 校验newConfig 是否符合我的预期
options[key] = newConfig[key]
}
}
return options
}
let options = mergeOtions(defaultConfig,program);
// 获取用户的参数来创建一个服务并且启动
let server = new Server(options);
server.start();
复制代码
server.js文件
const http = require('http');
const url = require('url'); // 解析url参数
const path = require('path');
const fs = require('fs').promises; // 获取fs模块的promise方法
const { createReadStream, createWriteStream } = require('fs'); // 获取读写流方法
const chalk = require('chalk'); // 粉笔工具
const mime = require('mime'); // 解析文件mime类型的包
const ejs = require('ejs'); // 生成html的包
// 服务类
class Server {
constructor(options) {
this.port = options.port;
this.directory = options.directory;
this.cache = options.cache;
}
async handleRequest(req, res) {
let { pathname } = url.parse(req.url);
pathname = decodeURIComponent(pathname); // pathname有可能是中文,把base64解析成中文
// 列出所有的文件夹
let requestUrl = path.join(this.directory, pathname); // 路径带/的不要用resolve会回到根路径
try {
const statObj = await fs.stat(requestUrl); // 读取路径对应的类型,是目录还是文件
// 如果是目录则返回一个该目录的html
if (statObj.isDirectory()) {
// 获取该目录下的所有文件及文件夹
let dirs = await fs.readdir(requestUrl);
let content = await ejs.renderFile(path.resolve(__dirname, 'template.html'), {
dirs: dirs.map(dir => ({
name: dir,
pathname: path.join(pathname, dir) // 加上前缀来获取深层的文件夹结构
}))
});
res.setHeader('Content-Type', 'text/html;charset=utf-8');
res.end(content);
} else {
// 文件 读取文件
this.sendFile(requestUrl, req, res, statObj)
}
} catch (e) {
console.log(e)
this.sendError(e, req, res);
}
}
// 错误响应
sendError(err, req, res) {
res.statusCode = 404;
res.end('Not Found')
}
// 根据文件类型设置响应头并返回文件
sendFile(filePath, req, res, stat) {
res.setHeader('Content-Type', `${mime.getType(filePath)};charset=utf-8`)
createReadStream(filePath).pipe(res);
}
// 启动服务方法
start() {
const server = http.createServer(this.handleRequest.bind(this));
server.listen(this.port, () => {
console.log(`${chalk.yellow('Starting up http-server, serving')}`);
console.log(` http://127.0.0.1:${chalk.green(this.port)}`)
});
}
}
module.exports = Server;
复制代码
文件目录的html的模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<%dirs.forEach(dir=>{%>
<li><a href="<%=dir.pathname%>"><%=dir.name%></a></li>
<%})%>
</body>
</html>
复制代码
3.使用
在命令工具里面输入 my-hs --help
,我们在代码里监听了--help
事件,显示了使用样例。
在命令工具里面输入my-hs -d /Users/xujian/workPlace/vue-better-drawer
,就在/Users/xujian/workPlace/vue-better-drawer目录下启动了一个http服务
浏览器打开http://127.0.0.1:8080/这个地址就能访问到这个目录下的文件结构
能显示具体的文件文件类容:
并且能访问更深层的目录:
一个简易版的http-server
就已经实现了,github地址:github.com/Itherma/my-…
作者:凌晨3点
链接:https://juejin.cn/post/6907530903841423373
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。