sw2express
Version:
A lite & simple cross-platform Express-like web application framework
502 lines (479 loc) • 13.3 kB
JavaScript
class nodeRequest {
constructor(req, body) {
this.req = req;
this.headers = req.headers;
this.method = req.method;
this.host = this.headers["x-forward-for"] || req.host;
this.protocol = this.headers["x-forward-proto"] || req.protocol;
this.path = req.url;
if (this.method === "POST") {
this.body = body;
}
}
}
class swRequest {
constructor(req, body) {
this.req = req;
this.headers = Object.fromEntries(req.headers.entries());
this.method = req.method;
this.url = new URL(req.url);
this.host = this.headers["x-forward-for"] || this.url.host;
this.path = this.url.pathname;
this.protocol = this.headers["x-forward-proto"] || this.url.protocol;
if (this.method === "POST") {
this.body = body;
}
}
}
const Headers = {
"Content-Type": "text/plain; charset=utf-8",
server: "Sw=>Express",
"X-Powered-By": (() => {
switch (checkPlatform()) {
case "NODE":
if (process.env.NODE_ENV !== "production") {
return `Sw2Express on Node.js/${process.version} on ${process.platform}`;
} else {
return `Sw2Express`;
}
case "SW":
return `Sw2Express on Service Worker`;
default:
return `Sw2Express on JavaScript`;
}
})(),
"X-Served-By": "Sw=>Express",
};
class nodeReply {
constructor(response, request, options) {
this.request = request;
this.response = response;
this.statusCode = 200;
this.headers = Object.assign({}, Headers);
this.isSendHeader = false;
this.isEnd = false;
this.sendMsg = [];
this.options = options;
}
setHeader(name, value) {
this.headers[name] = value;
return this;
}
getHeader(name) {
return this.headers[name];
}
Nativesend(text) {
this.response.statusCode = this.statusCode;
for (let i in this.headers) {
this.response.setHeader(i, this.headers[i]);
}
this.isSendHeader = true;
this.response.write(text);
return this;
}
send(text) {
this.sendMsg.push(text);
this.MSG = this.sendMsg.join("");
}
async end(text) {
this.sendMsg.push(text);
this.MSG = this.sendMsg.join("");
if (this.options.ETag) {
const ETag = `W/"${await globalThis.md5(this.MSG)}"`;
this.setHeader("ETag", ETag);
if (ETag === this.request.headers["if-none-match"]) {
this.statusCode = 304;
this.response.statusCode = this.statusCode;
this.response.end("");
return true;
} else {
this.Nativeend(this.MSG);
}
return true;
}
this.Nativeend(this.MSG);
return true;
}
json(text) {
if (this.getHeader("Content-Type") === "text/plain; charset=utf-8") {
this.setHeader("Content-Type", "application/json; charset=utf-8");
}
this.end(JSON.stringify(text));
return this;
}
Nativeend(text) {
if (!this.isSendHeader && text) this.Nativesend(text).Nativeend();
else this.response.end(text);
this.isEnd = true;
return true;
}
set(a, b) {
this[a] = b;
}
}
class swReply {
constructor(request, options) {
//this.response = response;
this.request = request;
this.statusCode = 200;
this.headers = Object.assign({}, Headers);
this.isSendHeader = false;
this.isEnd = false;
this.sendMsg = [];
this.options = options;
}
setHeader(name, value) {
this.headers[name] = value;
return this;
}
getHeader(name) {
return this.headers[name];
}
async GenResponse() {
if (this.options.ETag) {
const ETag = `W/"${await globalThis.md5(this.MSG)}"`;
this.headers.ETag = ETag;
this.requestHeaders = Object.fromEntries(this.request.headers.entries());
if (ETag === this.requestHeaders["if-none-match"]) {
return new Response(null, {
headers: this.headers,
status: 304,
});
}
}
this.isSendHeader = true;
return new Response(this.MSG, {
headers: this.headers,
status: this.statusCode,
});
}
send(text) {
this.sendMsg.push(text);
this.MSG = this.sendMsg.join("");
return this;
}
json(text) {
if (this.getHeader("Content-Type") === "text/plain; charset=utf-8") {
this.setHeader("Content-Type", "application/json; charset=utf-8");
}
this.end(JSON.stringify(text));
return this;
}
end(text) {
this.sendMsg.push(text);
this.MSG = this.sendMsg.join("");
this.isEnd = true;
return this;
}
set(a, b) {
this[a] = b;
}
}
async function startHTTPServer(PORT) {
const http = await import('http').then((e) => e.default);
const app = http.createServer();
if (PORT) {
app.listen(PORT);
}
return app;
}
async function doRequest$1(func, NodeRequest, NodeReply) {
const rep = await func(NodeRequest, NodeReply).catch(async (e) => {
await doRequest$1(page.Code(500).GenAsync, NodeRequest, NodeReply);
console.log(e.stack);
});
if (!NodeReply.isEnd) {
if (typeof rep !== "object") NodeReply.end(rep);
else NodeReply.json(rep);
}
}
const newHTTPServer = async (port) => {
return async (that) => {
const options = that.options;
if (options.cluster) {
const cluster = await import('cluster').then((e) => e.default);
if (cluster.isMaster) {
console.log("Master Process is running...");
for (let i = 0; i < options.cluster; i++) {
cluster.fork();
}
cluster.on("exit", (worker, code, signal) => {
console.log(`Worker ${worker.id} is exited with code ${code}`);
cluster.fork();
});
}
if (cluster.isWorker) {
console.log(`Worker Process ${cluster.worker.id} is running...`);
const server = await startHTTPServer(port);
server.on("request", await HTTPHandler(that));
}
} else {
const server = await startHTTPServer(port);
server.on("request", await HTTPHandler(that));
return server;
}
};
};
const HTTPHandler = async (that) => {
const page = that.DefaultPage;
return async (request, response) => {
let body = await new Promise((resolve) => {
if (request.method === "POST" && !request.body) {
let body = [];
request.on("data", (chunk) => body.push(chunk));
request.on("end", () => {
body = Buffer.concat(body).toString();
resolve(body);
});
} else if (request.body) {
resolve(request.body);
} else {
resolve();
}
});
let NodeRequest = new nodeRequest(request, body),
NodeReply = new nodeReply(response, request, that.options),
path = request.url;
new Promise((resolve) => {
if (that.options.logger) {
console.log(
`[${new Date().toLocaleString()}] ${request.socket.remoteAddress} - ${
request.method
} ${path}`
);
resolve();
}
});
await Promise.all(
that._middleWare.map((middleware) => middleware(NodeRequest, NodeReply))
);
if (!NodeReply.isEnd) {
if (that._route[path]) {
if (that._route[path][request.method]) {
await doRequest$1(
that._route[path][request.method],
NodeRequest,
NodeReply
);
} else if (that._route[path].all) {
await doRequest$1(that._route[path].all, NodeRequest, NodeReply);
} else {
await doRequest$1(page.Code(500).GenAsync, NodeRequest, NodeReply);
}
} else {
await doRequest$1(page.Code(404).GenAsync, NodeRequest, NodeReply);
}
}
};
};
const makeHandler = (that) => {
const page = that.DefaultPage;
return async (request) => {
let body = "";
if (request.method === "POST") {
body = await request.text();
}
let req = new swRequest(request, body),
rep = new swReply(request, that.options),
answer;
const path = req.path;
await Promise.all(
that._middleWare.map((middleware) => middleware(req, rep))
);
if (!rep.isEnd) {
if (that._route[path]) {
if (that._route[path][request.method]) {
answer = await doRequest(that._route[path][request.method], req, rep);
} else if (that._route[path].all) {
answer = await doRequest(that._route[path].all, req, rep);
} else {
answer = await doRequest(page.Code(500).GenAsync, req, rep);
}
} else {
answer = await doRequest(page.Code(404).GenAsync, req, rep);
}
} else {
answer = await doRequest(
async (req, rep) => {
"NotThingToDoHere.";
},
req,
rep
);
}
return answer;
};
};
async function doRequest(func, req, rep) {
const funcAnswer = await func(req, rep).catch(async (e) => {
await doRequest(page.Code(500).GenAsync, req, rep);
console.log(e.stack);
});
if (!rep.isEnd) {
if (typeof funcAnswer !== "object") rep.end(funcAnswer);
rep.json(funcAnswer);
}
return await rep.GenResponse();
}
var toMD5 = () => {
if (globalThis.platform === "NODE") {
const crypto = import('crypto').then((e) => e.default);
return async (text) => {
const md5 = (await crypto).createHash("md5");
const answer = md5.update(text).digest("hex");
return answer;
};
} else if (globalThis.platform === "SW") {
return async (text) => {
const textUint8 = new TextEncoder().encode(text);
const hashBuffer = await crypto.subtle.digest("MD5", textUint8);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return hashHex;
};
} else {
return async (text) => {
throw new Error("Can't Found Platform!");
};
}
};
const getReallyPrefix = (relativePrefix, globalPrefix) => {
const prefix = new URL("http://sw2express.localhost");
prefix.pathname = globalPrefix;
const reallyPrefix = new URL(relativePrefix, prefix.href);
return reallyPrefix.pathname;
};
var registerPlugins = {
name: "register",
func: (that) => (func, globalPrefix = "/") => {
const app = {
route: (prefix) => {
const reallyRoute = that.route(getReallyPrefix(prefix, globalPrefix));
return reallyRoute;
},
use: (Handler) => {
that._registerMiddlewares.push({
prefix: globalPrefix,
Handler: Handler,
});
},
};
func(app);
return that;
},
bootstrap: (app) => {
app._register = true;
app._registerMiddlewares = [];
app.use(async (req, rep) => {
app._registerMiddlewares.forEach(async (handler) => {
if (req.path.indexOf(handler.prefix) === 0) {
await handler.Handler(req, rep);
}
});
});
},
};
function Code(statusCode) {
return {
GenAsync: async function (res, rep) {
rep.statusCode = statusCode;
rep.setHeader("Content-Type", "text/plain");
rep.send(`Error: ${statusCode}`);
},
};
}
var page$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Code: Code
});
function checkPlatform() {
if (!(typeof process === "undefined")) {
return "NODE";
} else if (!(typeof self === "undefined")) {
return "SW";
} else {
return "UNKNOWN";
}
}
class sw2express {
constructor(options = { logger: false, ETag: true, cluster: false }) {
this.options = {
logger: options.logger,
ETag: options.ETag,
cluster: options.cluster,
};
this._route = {};
this._middleWare = [];
this.DefaultPage = page$1;
globalThis.platform = checkPlatform();
globalThis.md5 = toMD5();
}
use(MiddleWare) {
this._middleWare.push(MiddleWare);
return this;
}
route(url) {
this._route[url] = {};
const that = {
POST: (func) => {
this._route[url].POST = func;
return that;
},
GET: (func) => {
this._route[url].GET = func;
return that;
},
all: (func) => {
this._route[url].all = func;
return that;
},
};
return that;
}
makeNewHandler(platform) {
switch (platform) {
case "NODE": {
return {
listen: (port) => newHTTPServer(port).then((e) => e(this)),
Server: () => newHTTPServer().then((e) => e(this)),
Handler: HTTPHandler(this),
};
}
case "SW": {
return makeHandler(this);
}
case "VERCEL": {
return HTTPHandler(this);
}
default:
return new Error("Invalid Platform");
}
}
listen(port) {
switch (checkPlatform()) {
case "NODE": {
let answer = "NODE",
Handler = this.makeNewHandler(answer);
Handler.listen(port);
return Handler;
}
case "SW": {
let answer = "SW",
Handler = this.makeNewHandler(answer);
self.addEventListener("fetch", (event) =>
event.respondWith(Handler(event.request))
);
return Handler;
}
}
}
extend({ name, func, bootstrap }) {
this[name] = func(this);
return bootstrap(this);
}
}
const plugins = { register: registerPlugins };
export default sw2express;
export { checkPlatform, plugins };