今天跟往常一样逛技术社区,发现了一篇《Express 简单实现》(github) ,写的还是比较简单易懂的。
刚好平时在测试比如局域网共享文件,临时网页共享或测试等等情况下可能会需要一个临时的http服务。
当然一般情况下我都是使用Python命令python -m http.server 8080
来做临时服务,但是如果需要有动态请求时可能就不能用这种办法了(当然啦你也可以使用Python的Bottle库或Node.js的Express来编写临时服务) 。
刚好那就自己也尝试着写写一个简单的web框架呗。
1.一个很简单的示例
simple_http.js
:
const http = require('http');
const url = require('url');
const fs = require('fs');
var routes = {//路由
'/'(req, res) {
res.end('关爱单身狗成长协会');
},
'/demo'(req, res) { //读取html示例
res.write(fs.readFileSync('./demo.html', 'utf-8'));
res.end();
},
/** 自定义路由.... **/
err(req, res, errCode) {//自定义异常
res.writeHead(errCode, { 'Content-Type': 'text/html' });
res.write(errCode.toString());
res.end();
}
};
http.createServer((req, res) => {//监听
let { pathname } = url.parse(req.url, true);//获取访问路径
let route = routes[pathname];//匹配路由
try {
return route ? route(req, res) : routes.err(req, res, 400);
}
catch (err) {
console.error(err);
routes.err(req, res, 500);
}
}).listen(8080);
2.将其包装成库
simple_http.js
'use strict';
const http = require('http');
const url = require('url');
function createApplication() {
let app = function (req, res) {//监听
let { pathname } = url.parse(req.url, true);//获取访问路径
let route = app.routes[pathname];
let r = { "req": req, "res": res, "path": pathname, "app": app, method: req.method.toLocaleUpperCase() };
try {
return route ? route.apply(r) : app.error404(r);
}
catch (err) {
app.error(Object.assign(r, { err: err.toString(), errCode: 500 }));
}
};
app.routes = {};//路由
app.error = (r) => {//异常处理
console.error(r);
r.res.writeHead(r.errCode, { 'Content-Type': 'text/html' });
r.res.write(r.errCode.toString());
r.res.end();
};
app.error404 = (r) => app.error(Object.assign(r, { err: "path not find", errCode: 404 }));//404异常处理
app.listen = function () {
let server = http.createServer(app);
server.listen(...arguments);
return server;
};
return app;
}
module.exports = createApplication;
app.js
调用:
const app = require("./simple_http")();
const fs = require('fs');
app.routes["/"] = function () {
this.res.end('关爱单身狗成长协会');
};
app.routes["/demo"] = function () {
this.res.write(fs.readFileSync('./demo.html', 'utf-8'));
this.res.end();
};
let server = app.listen(8080, '0.0.0.0', function () {
console.log(`listening at http://${server.address().address}:${server.address().port}`)
});
3.增加静态文件夹访问,控制台异常信息输出
simple_http.js
'use strict';
const http = require('http')
const url = require('url')
const utils = require("./utils");
function createApplication() {
let app = function (req, res) {
// 解析请求,包括文件名
let { pathname } = url.parse(req.url, true);
let route = app.routes[pathname];
let r = { "req": req, "res": res, "path": pathname, "app": app, method: req.method.toLocaleUpperCase() };
try {
return route ? route.apply(r) :app.serve(r, (e, res) => {if (e && e.status) app.error404(r);});
}
catch (err) {
app.error(Object.assign(r, { err: err.toString(), errCode: 500 }));
}
};
app.routes = {};
app.error = (r) => r.res.end();
app.error404 = (r) => app.error(Object.assign(r, { err: "path not find", errCode: 404 }));
app.listen = function () {
let server = http.createServer(app);
server.listen(...arguments);
return server;
};
return utils(app);
}
module.exports = createApplication;
utils.js
扩展:
const mime = require("./mime");
const fs = require("fs");
module.exports = function (app) {
app.getClientIP = (req) => req.headers['x-real-ip'] || req.headers['x-forwarded-for']|| req.connection.remoteAddress;
app.errorLog = (r) => console.error({ "path": r.path, "ip": app.getClientIP(r.req), err: r.err, errCode: r.errCode });
app.error = (r) => {
app.errorLog(r);
r.res.writeHead(r.errCode, { 'Content-Type': 'text/html' });
r.res.write(r.errCode.toString());
r.res.end();
};
app.static = [];
app.serve = (r) => {
let path = r.path.replace(/^\/+/, "");
if (app.static.length == 0) return app.error404(r);
let s = app.static.find(_ => path.indexOf(_) == 0);
if (s) {
let t = mime.getType(path);
return fs.readFile(path, function (err, fr) {
if (err) {
app.error404(r);
} else {
r.res.writeHead(200, { 'Content-Type': t });
r.res.write(fr);
r.res.end();
}
});
}
};
app.moveFile = (op, np) => fs.renameSync(op, np);
app.copyFile = (op, np) => fs.writeFileSync(np, fs.readFileSync(op));
app.readJSON = (p) => JSON.parse(fs.readFileSync(p));
app.writeJSON = (p, d) => fs.writeFileSync(p, JSON.stringify(d));
app.readText = (p) => fs.readFileSync(p, 'utf-8');
return app;
};
mime.js
文件扩展名识别
const M = {
getType(p) {
let e = p.toLocaleLowerCase().split(".");
return M[e[e.length - 1]] || M["*"];
},
"*": "application/octet-stream",
"tif": "image/tiff",
//.........
"css": "text/css",
//.........
"htm": "text/html",
"html": "text/html",
//.........
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "application/javascript",
"json":"application/json",
//.........
};
module.exports = M;
app.js
调用:
const app = require("./simple_http")();
const fs = require('fs');
app.static.push("static");//加入静态目录
app.routes["/"] = function () {//加入路由
this.res.end('关爱单身狗成长协会');
};
let server = app.listen(8080, '0.0.0.0', function () {
console.log(`listening at http://${server.address().address}:${server.address().port}`)
});
好啦一个简单的web框架,就写的差不多了。大家可以参考着写写看,或比如加入:正则路由解析、模板引擎、post请求处理、文件上传处理等等的功能。
文章源码放在:https://gitee.com/baojuhua/node_simple_http/tree/master