UNPKG

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
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();