vesh
Version:
码农nodejs版本VESH框架,使用函数责任链模式 实现了 默认文件跳转,自定义错误页,空文件处理,URL解析,Querystring与Form参数解析,PostFiles解析,MVC自动映射,SQL自动服务化,可继承页面,静态文件,json,tjson,jsonp,tjsonp,string,void,xjson,xjosnp等等6种JSON格式,http与https等等操作
877 lines (856 loc) • 26.8 kB
JavaScript
import V from "gcl/com/coooders/common/tool";
import N from "gcl/com/coooders/db/ni";
import { AModuler } from "./app";
import Q from "querystring";
import createError from "http-errors";
import { ArrayStream } from "gcl/com/coooders/collection/arraystream";
import iconv from "iconv-lite";
import U from "url";
import Z from "zlib";
import C from "gcl/com/coooders/net/cookie";
import { MIME } from "gcl/com/coooders/net/mime";
import I from "gcl/com/coooders/io/tool";
import F from "fs";
import Busboy from "busboy";
/**
* 使用判断如果未根目录就自动设置默认页面地址
*/
export const DefaultModuler = class extends AModuler {
constructor(path = "", isRedirect = true) {
super("DefaultModuler");
pri(this, { path: path.split("?")[0], isRedirect });
}
onrequest(req, rep, session) {
const { _, __ } = pri(this);
const query = req.url.split("?");
if (query[0].length <= 1) {
req.url = __.path + (query[1] || "");
if (__.isRedirect) session.redirect(req.url);
}
return false;
}
};
/**
* StatusCode进行判断自动转向指定的错误页
*/
export const StatusModuler = class extends AModuler {
constructor(paths = {}, valid = "htm;html") {
super("StatusModuler");
const __ = {};
valid
.split(";")
.filter((v) => V.isValid(v))
.forEach((k) => (__[k] = true));
pri(this, { paths, valid: __ });
}
onresponse(req, rep, session) {
const { _, __ } = pri(this);
if (__.paths[rep.statusCode + ""] && __.valid[session.extend])
session.redirect(__.paths[rep.statusCode + ""]);
return false;
}
};
/**
* 用于添加reponse的静态头部
*/
export const HeaderModuler = class extends AModuler {
constructor(data = {}) {
super("HeaderModuler");
pri(this, { data });
}
onrequest(req, rep, session) {
const { _, __ } = pri(this);
if (
req.headers["host"] &&
req.headers["origin"] &&
req.headers["host"] !== req.headers["origin"].split("//")[1]
) {
if (!__.data["Access-Control-Allow-Origin"])
rep.setHeader("Access-Control-Allow-Origin", req.headers["origin"]);
if (!__.data["Access-Control-Allow-Credentials"])
rep.setHeader("Access-Control-Allow-Credentials", "true");
}
for (var k in __.data) rep.setHeader(k, __.data[k]);
return false;
}
};
/**
* 使用querystring和raw-body完成SessionDataManager.QueryString,Form的管理和设置
*/
export const FormModuler = class extends AModuler {
constructor(size = Math.pow(2, 20) * 4) {
super("FormModuler");
pri(this, {
size,
fregex: /[/\\][^/\\]+$/g,
dregex: /\.[^/\\\.]+$/g,
});
}
parse(req, rep, session, func) {
const { _, __ } = pri(this);
let callback = func;
req.headers["content-type"] = req.headers["content-type"] || "text/plain";
switch (req.headers["content-type"].split(";")[0].toLowerCase()) {
case "ws":
case "mqtt":
session.form = req.form || {};
func(null, false);
return;
case "application/x-www-form-urlencoded":
callback = (err, data) => {
try {
if (err) func(createError(415, err));
else {
session.orgform = data;
session.form = Q.parse(data); // 判断size长度,判断content-type 分别继续进行处理
func(null, false);
}
} catch (e) {
func(createError(500, e));
}
};
break;
case "application/json":
callback = (err, data) => {
try {
if (err) func(createError(415, err));
else {
session.orgform = data;
session.form = V.json(data); // 判断size长度,判断content-type 分别继续进行处理
func(null, false);
}
} catch (e) {
func(createError(500, e));
}
};
break;
case "text/xml":
case "application/xml":
callback = (err, data) => {
try {
if (err) func(createError(415, err));
else {
session.orgform = data;
session.form = V.xml().toJson(data); // 判断size长度,判断content-type 分别继续进行处理
func(null, false);
}
} catch (e) {
func(createError(500, e));
}
};
break;
case "text/plain":
callback = (err, data) => {
try {
if (err) func(createError(415, err));
else {
session.orgform = data;
session.form = data; // 判断size长度,判断content-type 分别继续进行处理
func(null, false);
}
} catch (e) {
func(createError(500, e));
}
};
break;
case "application/octet-stream":
//未测试
callback = (err, data, encoding) => {
try {
if (err) func(createError(415, err));
else {
session.orgform = data;
session.form = iconv.decode(data, encoding || "utf-8"); // 判断GB2312进行处理
func(null, false);
}
} catch (e) {
func(createError(500, e));
}
};
break;
case "multipart/form-data":
func(null, false);
return;
default:
func(
createError(
415,
new Error("Invalid Content-Type:" + req.headers["content-type"]),
),
);
return;
}
//需要Form处理
const encoding =
(req.headers["content-encoding"] || "identity").toLowerCase() || "";
let cencoding = "utf-8";
const length = req.headers["content-length"] || __.size;
const stream = new ArrayStream(length);
stream.on("error", callback);
stream.on("finish", (err) =>
callback(err, Buffer.from(stream.toArray()).toString("utf-8"), cencoding),
);
switch (encoding.toLowerCase()) {
case "gzip":
req.pipe(Z.createGunzip()).pipe(stream);
break;
case "deflate":
req.pipe(Z.createInflate()).pipe(stream);
break;
default:
case "identity":
req.pipe(stream);
break;
}
}
async onrequest(req, rep, session) {
const { _, __ } = pri(this);
session.url = U.parse(
(req.protocol || "http://") +
req.headers.host +
req.url.replace(/\/+/g, "/"),
);
session.querystring = Q.parse(session.url.query, null, null, {
decodeURIComponent: decodeURIComponent,
});
//兼容无后缀和多点的情况
var query = session.url.pathname.match(__.fregex);
query = query && query[0].substr(1).replace(__.dregex, ""); // || session.url.pathname;
session.status = query;
session.filename = query;
if (req.method !== "") {
await V.callback2((call) => _.parse(req, rep, session, call));
} else return false;
}
onresponse(req, rep, session) {
delete session.querystring;
delete session.form;
delete session.filename;
return false;
}
};
/**
* 文件处理
*/
export const _FilesModuler = class extends AModuler {
constructor(path, limit = Math.pow(2, 20) * 10) {
super("FilesModuler");
pri(this, {
limit,
postfile: class {
constructor(path) {
pri(this, { path: path });
const that = this;
this.stream = F.createWriteStream(path);
this.stream.on("error", (err) => V.showException("", err));
this.stream.on("finish", () => delete that.stream);
}
async save(path) {
const { __ } = pri(this);
return await I.copyFile(__.path, path);
}
remove() {
const { __ } = pri(this);
F.unlink(__.path, () => {});
return false;
}
end() {
if (this.stream) this.stream.end();
delete this.stream;
}
},
check: (session, req) => {
if (req.isEnd) {
let isF = true;
(session.Files ? session.Files : []).forEach((v) => {
isF = v.isEnd && isF;
});
return isF;
}
return false;
},
});
this.path = I.formatPath(path);
}
parse(datas, spliter, rest) {
//将二进制数据根据常用字符串\r\n进行匹配和分割
const strs = Buffer.from(spliter).toJSON().data;
let l = 0,
r = rest ? rest : 0;
let buffer = [],
ret = [];
while (l < datas.length) {
if (datas[l] == strs[r]) {
//开始进入第二步测试
if (r < strs.length - 1) r++;
else {
//匹配成功 同时处理越次匹配这种事
if (buffer.length > 0) ret.push(buffer);
ret.push({ isspliter: true });
buffer = [];
r = 0;
}
} else if (r > 0) {
//说明匹配失败 l回退
l = l - r;
if (l < 0) {
//说明数据回退到上次获取的结尾之前,需要进行补充 补充内容是 strs[0-abs(l)]
for (var i = 0; i <= Math.abs(l); i++) buffer.push(strs[i]);
l = 0;
} else buffer.push(datas[l]);
r = 0;
} else buffer.push(datas[l]);
l++;
}
ret.push(buffer);
return { datas: ret, rest: r };
}
onrequest(req, rep, session, call) {
const { _, __ } = pri(this);
session.Files = [];
req.headers["content-type"] = req.headers["content-type"] || "";
if (
req.method === "POST" &&
req.headers["content-type"]
.toLowerCase()
.trim()
.startWith("multipart/form-data")
) {
let length = req.headers["content-length"],
received = 0;
if (length > __.limit) {
session.end(
400,
V.format("request size is too large:{length}>{limit}", {
limit: __.limit,
length: length,
}),
);
return false;
}
const key = V.GUID();
const boundaryKey = req.headers["content-type"]
.split(";")[1]
.split("=")[1];
//.trim('-').trim('"').trim("'");//兼容IOS
const boundaryKey2 = boundaryKey.trim("-").trim('"').trim("'");
// console.log(291,boundaryKey);
session.checker = new I.checker(() => {
//使用多流截止检查
delete session.checker;
call(null, false);
});
req.on("error", call);
req.on("end", () => {
if (received != length)
session.end(
400,
`request size did not match content length:${length}!=${receive}`,
);
});
session.checker.push(req);
let fileStream = null,
file = null,
rest = 0;
//首先有\r\n进行处理
req.on("data", (data) => {
received += data.length;
let datas = _.parse(data, "\r\n", rest);
let strs = datas.datas;
rest = datas.rest;
//确定文件头部位置
if (strs.length > 1) {
//说明找到了头部和头部相关的位置
let isHead = false,
hasWrite = false,
buffer = 0;
strs.forEach((v) => {
if (v.isspliter) {
buffer++;
} else {
var v2 = Buffer.from(v).toString("utf-8");
// switch (v2) {
// case '--' + boundaryKey:
// case '----' + boundaryKey:
// if (file) {
// console.log('关闭文件并重新开启文件');
// }
// console.log('关闭文件并重新开启文件2',v2);
// break;
// case 'Content-Transfer-Encoding: binary':
// case 'Content-Type: application/octet-stream':
// console.log(v2);
// break;
// case '':
// if (isHead) {
// isHead = false;
// buffer = -1;
// return;
// }
// console.log('空');
// break;
// case '--' + boundaryKey + '--':
// case '----' + boundaryKey + '--':
// //传送结束
// console.log('结束',v2);
// break;
// default:
// //修正上传文件类型类型不为流时 不写入
// if (v2.toLowerCase().startWith('content-type:'))
// console.log('content-type:');
// if (v2.indexOf('Content-Disposition:') >= 0) {
// console.log('Content-Disposition:');
// } else {
// //主要数据入口
// console.log('数据',v.length);
// }
// break;
// }
switch (v2) {
case "--" + boundaryKey2:
case "----" + boundaryKey2:
case "--" + boundaryKey:
case "----" + boundaryKey:
if (file) {
//关闭文件并重新开启文件
file.end();
// if (!file.filename) {
// file.remove();
// session.Files.pop();
// }
file = null;
}
var path =
_.path +
V.environment.splitChar +
key +
session.Files.length +
".tmp";
file = new __.postfile(path);
file.length = length;
session.Files.push(file);
session.checker.push(file.stream);
buffer = 0;
isHead = true;
hasWrite = false;
break;
case "Content-Transfer-Encoding: binary":
case "Content-Type: application/octet-stream":
break;
case "":
if (isHead) {
isHead = false;
buffer = -1;
return;
}
break;
case "--" + boundaryKey2 + "--":
case "----" + boundaryKey2 + "--":
case "--" + boundaryKey + "--":
case "----" + boundaryKey + "--":
//传送结束
if (file) {
//关闭文件并重新开启文件
file.end();
file = null;
}
break;
default:
//修正上传文件类型类型不为流时 不写入
if (v2.toLowerCase().startWith("content-type:")) break;
if (v2.indexOf("Content-Disposition:") >= 0) {
var desc = v2.split('"');
if (file) {
file.name = desc[1];
file.filename = desc[3];
}
} else {
//主要数据入口
while (buffer > 0) {
buffer--;
if (hasWrite && file) {
//
file.stream.write(Buffer.from("\r\n"));
}
}
if (v.length > 0) {
hasWrite = true;
file && file.stream.write(Buffer.from(v));
}
}
break;
}
}
});
} else if (strs[0].length > 0 && file)
file.stream.write(Buffer.from(strs[0]));
});
} else call(null, false);
}
init() {
I.createDir(this.path);
}
onresponse(req, rep, session) {
if (session.Files.length > 0) session.Files.forEach((v) => v.remove());
return false;
}
};
/**
* Cookie处理
*/
export const CookieModuler = class extends AModuler {
constructor() {
super("CookieModuler");
}
onrequest(req, rep, session) {
session.Cookies = req.headers["cookie"]
? C.serverParse(req.headers["cookie"])
: {};
session.SetCookies = {};
return false;
}
onresponse(req, rep, session) {
if (!session.isStatic)
for (let i in session.SetCookies)
if (i) {
!session.isInterrupt() &&
rep.setHeader(
"Set-Cookie",
C.toHeaderArray(V.getValue(session.SetCookies, {})),
);
//delete session.Cookies;
return false;
}
return false;
}
};
/**
* 会话初始化Moduler
*/
export const InitSessionModuler = class extends AModuler {
constructor() {
super("InitSessionModuler");
}
async onrequest(req, rep, session) {
await session.initSession(
session.param("sessionID") ||
session.param("SessionID") ||
(session.WebSocket && session.WebSocket.tsessionID),
);
return false;
}
onresponse(req, rep, session) {
//这时如果刚刚登录或者已经登录应该进行session的Reset操作 要求如果是WebSocket请求,那么把WebSocket的tsessionID和SessionID进行绑定交换
// 放弃绑定各管各的
// session.WebSocket &&
// session.resetWebSocketSessionID(
// session.param("sessionID") ||
// session.param("SessionID") ||
// (session.WebSocket && session.WebSocket.tsessionID)
// );
// console.log(494, Object.keys(session.WSSessionMap).length);
return false;
}
};
/**
* 空返回值处理
*/
export const EmptyModuler = class extends AModuler {
constructor() {
super("EmptyModuler");
}
onresponse(req, rep, session) {
switch (rep.getHeader("content-type") + "") {
case "undefined":
if (rep.statusCode == 200) {
rep.setHeader("Content-Type", MIME.txt);
session.end(404, "no such file or action:" + session.url.pathname);
} else {
rep.setHeader("Content-Type", MIME.html + ";charset=utf-8");
}
return false;
default:
return false;
}
}
};
/**
* 限速Module
*/
/**
*
* @param {限制单位时间数量} count
* @param {定义单位时间} time
* @param {提示错误信息} time
*/
export const LimitModuler = class extends AModuler {
constructor(
error = "访问人数过多,请稍后重试!",
count = 200,
time = 1,
limit = 10000000,
) {
super("LimitModuler");
pri(this, { count, time, error, limit });
}
onrequest(req, rep, session) {
const { __ } = pri(this);
session.testLimit("limitModule", __.error, __.count, __.time, __.limit);
return false;
}
};
/**
* 缓存Moduler处理
*/
export const CacheModuler = class extends AModuler {
constructor() {
super("CacheModuler");
pri(this, { idic: {} });
}
onrequest(req, rep, session) {
const { _, __ } = pri(this);
if (__.idic[V.hash(req.url)]) {
var data = __.idic[V.hash(req.url)];
if (data.date.getTime() > new Date().getTime()) {
//缓存可用
for (let k in data.headers) {
rep.setHeader(k, data.headers[k]);
}
var stream = new ArrayStream();
stream.write(data.data);
session.writeStream(stream);
return true;
} else return false;
} else return false;
}
onresponse(req, rep, session, call) {
const { _, __ } = pri(this);
if (V.isValid(rep.getHeader("content-type"))) {
var stream = new ArrayStream();
var sb = session.clearWrite();
if (V.isValid(sb)) stream.write(sb, session.encoding);
var ss = session.clearStreams();
session.writeStream(stream);
if (ss && ss.length > 0) {
V.each(ss, (v, next) => {
v.on("error", (err) => session.Log.error("VESH:" + err.stack));
v.on("end", () => {
v.removeAllListeners("err");
v.removeAllListeners("end");
if (next) next();
});
//写入文件
v.pipe(stream, { end: false });
}).then(() => {
if (stream.buf.length > 0)
__.idic[V.hash(req.url)] = {
data: Buffer.from(Array.prototype.slice.call(stream.buf, 0)),
date: new Date().add("n", 2),
headers: { "content-type": rep.getHeader("content-type") },
};
call(null, false);
});
} else return false;
} else return false;
}
};
/**
* 文件处理
*/
export const FilesModuler = class extends AModuler {
constructor(path, limit = Math.pow(2, 20) * 10) {
super("FilesModuler");
pri(this, {
limit,
postfile: class {
constructor(path) {
pri(this, { path: path });
const that = this;
this.stream = F.createWriteStream(path);
this.stream.on("error", (err) => V.showException("", err));
this.stream.on("finish", () => delete that.stream);
}
async save(path) {
const { __ } = pri(this);
return await I.copyFile(__.path, path);
}
remove() {
const { __ } = pri(this);
F.unlink(__.path, () => {});
return false;
}
end() {
if (this.stream) this.stream.end();
delete this.stream;
}
},
check: (session, req) => {
if (req.isEnd) {
let isF = true;
(session.Files ? session.Files : []).forEach((v) => {
isF = v.isEnd && isF;
});
return isF;
}
return false;
},
});
this.path = I.formatPath(path);
}
onrequest(req, rep, session, call) {
const { _, __ } = pri(this);
session.Files = [];
req.headers["content-type"] = req.headers["content-type"] || "";
if (
req.method === "POST" &&
req.headers["content-type"]
.toLowerCase()
.trim()
.startWith("multipart/form-data")
) {
let length = req.headers["content-length"],
received = 0;
if (length > __.limit) {
session.end(
400,
V.format("request size is too large:{length}>{limit}", {
limit: __.limit,
length: length,
}),
);
return false;
}
const key = V.GUID();
const boundaryKey = req.headers["content-type"]
.split(";")[1]
.split("=")[1];
//.trim('-').trim('"').trim("'");//兼容IOS
const boundaryKey2 = boundaryKey.trim("-").trim('"').trim("'");
// console.log(291,boundaryKey);
session.checker = new I.checker(() => {
//使用多流截止检查
delete session.checker;
call(null, false);
});
req.on("error", call);
req.on("end", () => {
// console.log(600,'request end!')
});
session.checker.push(req);
let busboy = null;
let file = null;
try {
busboy = Busboy({ headers: req.headers, limits: __.limit });
busboy.on(
"file",
(fieldname, fileStream, { filename, encoding, mimeType }) => {
if (!filename) return fileStream.resume();
console.log(
`File [${fieldname}]: filename: %j, encoding: %j, mimeType: %j`,
filename,
encoding,
mimeType,
);
filename = Buffer.from(filename, "latin1").toString("utf-8");
// if (file) {
// //关闭文件并重新开启文件
// file.end();
// // if (!file.filename) {
// // file.remove();
// // session.Files.pop();
// // }
// file = null;
// }
var path =
_.path +
V.environment.splitChar +
key +
session.Files.length +
".tmp";
var file = new __.postfile(path);
file.name = filename.split(".");
file.name.pop();
file.name = file.name.join(".");
file.filename = filename;
file.encoding = encoding;
file.mimeType = mimeType;
file.length = 1;
session.Files.push(file);
session.checker.push(file.stream);
fileStream.on("close", () => {
// console.log('File Received!');
received = length;
if (file) file.end();
// if (!file.filename) {
// file.remove();
// session.Files.pop();
// }
file = null;
});
fileStream.on("error", (e) => {
console.log("File error!", e.message);
if (file) file.end();
// if (!file.filename) {
// file.remove();
// session.Files.pop();
// }
file = null;
call(e);
});
fileStream.pipe(file.stream);
// file.on('data', (data) => {
// console.log(`File [${name}] got ${data.length} bytes`);
// }).on('close', () => {
// console.log(`File [${name}] done`);
// });
},
);
busboy.on("field", (name, val, info) => {
console.log(`Field [${name}]: value: %j`, val);
});
busboy.on("close", () => {
// console.log('Done parsing form!',received,length);
if (file) {
file.end();
req.unpipe(busboy);
busboy.removeAllListeners();
received = length;
}
if (received != length)
session.end(
400,
`request size did not match content length:${length}!=${received}`,
);
file = null;
});
req.pipe(busboy);
} catch (err) {
if (file) {
req.unpipe(busboy);
busboy.removeAllListeners();
}
call(err);
}
} else call(null, false);
}
init() {
I.createDir(this.path);
}
onresponse(req, rep, session) {
if (session.Files.length > 0) session.Files.forEach((v) => v.remove());
return false;
}
};
export default {
DefaultModuler,
StatusModuler,
HeaderModuler,
FormModuler,
FilesModuler,
CookieModuler,
EmptyModuler,
CacheModuler,
InitSessionModuler,
LimitModuler,
};
const pri = V.pris();