diff --git a/src/index.js b/src/index.js index bb851b4..e101548 100644 --- a/src/index.js +++ b/src/index.js @@ -19,6 +19,11 @@ process.filename = __filename; //process.singleThreaded = false; process.singleThreaded = true; +process.err4xxcounter = 0; +process.err5xxcounter = 0; +process.reqcounter = 0; +process.malformedcounter = 0; + if (process.versions) process.versions.svrjs = version; // Inject SVR.JS into process.versions let forceSecure = false; @@ -202,9 +207,11 @@ let middleware = [ require("./middleware/responseHeaders.js"), require("./middleware/checkForbiddenPaths.js"), require("./middleware/nonStandardCodesAndHttpAuthentication.js"), - require("./middleware/redirectTrailingSlashes.js") + require("./middleware/redirectTrailingSlashes.js"), // TODO: SVR.JS mods go here // TODO: default handler + require("./middleware/defaultHandlerChecks.js"), + require("./middleware/status.js") ]; function addMiddleware(mw) { diff --git a/src/middleware/core.js b/src/middleware/core.js index 3d8afff..4a4df4d 100644 --- a/src/middleware/core.js +++ b/src/middleware/core.js @@ -8,10 +8,6 @@ const fixNodeMojibakeURL = require("../utils/urlMojibakeFixer.js"); const ipMatch = require("../utils/ipMatch.js"); const matchHostname = require("../utils/matchHostname.js"); -if (!process.err4xxcounter) process.err4xxcounter = 0; -if (!process.err5xxcounter) process.err5xxcounter = 0; -if (!process.reqcounter) process.reqcounter = 0; - module.exports = (req, res, logFacilities, config, next) => { config.generateServerString = () => { return generateServerString(config.exposeServerVersion); diff --git a/src/middleware/defaultHandlerChecks.js b/src/middleware/defaultHandlerChecks.js new file mode 100644 index 0000000..c2440d9 --- /dev/null +++ b/src/middleware/defaultHandlerChecks.js @@ -0,0 +1,27 @@ +const http = require("http"); + +module.exports = (req, res, logFacilities, config, next) => { + if (req.isProxy) { + let eheaders = config.getCustomHeaders(); + eheaders["Content-Type"] = "text/html; charset=utf-8"; + res.writeHead(501, http.STATUS_CODES[501], eheaders); + res.write("Proxy not implemented

Proxy not implemented

SVR.JS doesn't support proxy without proxy mod. If you're administator of this server, then install this mod in order to use SVR.JS as a proxy.

" + config.generateServerString().replace(/&/g, "&").replace(//g, ">") + "

"); + res.end(); + logFacilities.errmessage("SVR.JS doesn't support proxy without proxy mod."); + return; + } + + if (req.method == "OPTIONS") { + let hdss = config.getCustomHeaders(); + hdss["Allow"] = "GET, POST, HEAD, OPTIONS"; + res.writeHead(204, http.STATUS_CODES[204], hdss); + res.end(); + return; + } else if (req.method != "GET" && req.method != "POST" && req.method != "HEAD") { + res.error(405); + logFacilities.errmessage("Invaild method: " + req.method); + return; + } + + next(); +} \ No newline at end of file diff --git a/src/middleware/status.js b/src/middleware/status.js new file mode 100644 index 0000000..32653e1 --- /dev/null +++ b/src/middleware/status.js @@ -0,0 +1,37 @@ +const http = require("http"); +const os = require("os"); +const sizify = require("../utils/sizify.js"); +const svrjsInfo = require("../../svrjs.json"); +const {name} = svrjsInfo; + +module.exports = (req, res, logFacilities, config, next) => { + if (config.allowStatus && (req.parsedURL.pathname == "/svrjsstatus.svr" || (os.platform() == "win32" && req.parsedURL.pathname.toLowerCase() == "/svrjsstatus.svr"))) { + const formatRelativeTime = (relativeTime) => { + const days = Math.floor(relativeTime / 60 / (60 * 24)); + const dateDiff = new Date(relativeTime * 1000); + return days + " days, " + dateDiff.getUTCHours() + " hours, " + dateDiff.getUTCMinutes() + " minutes, " + dateDiff.getUTCSeconds() + " seconds"; + } + let statusBody = ""; + statusBody += "Server version: " + config.generateServerString() + "

"; + + //Those entries are just dates and numbers converted/formatted to strings, so no escaping is needed. + statusBody += "Current time: " + new Date().toString() + "
Thread start time: " + new Date(new Date() - (process.uptime() * 1000)).toString() + "
Thread uptime: " + formatRelativeTime(Math.floor(process.uptime())) + "
"; + statusBody += "OS uptime: " + formatRelativeTime(os.uptime()) + "
"; + statusBody += "Total request count: " + process.reqcounter + "
"; + statusBody += "Average request rate: " + (Math.round((process.reqcounter / process.uptime()) * 100) / 100) + " requests/s
"; + statusBody += "Client errors (4xx): " + process.err4xxcounter + "
"; + statusBody += "Server errors (5xx): " + process.err5xxcounter + "
"; + statusBody += "Average error rate: " + (Math.round(((process.err4xxcounter + process.err5xxcounter) / process.reqcounter) * 10000) / 100) + "%
"; + statusBody += "Malformed HTTP requests: " + process.malformedcounter; + if (process.memoryUsage) statusBody += "
Memory usage of thread: " + sizify(process.memoryUsage().rss, true) + "B"; + if (process.cpuUsage) statusBody += "
Total CPU usage by thread: u" + (process.cpuUsage().user / 1000) + "ms s" + (process.cpuUsage().system / 1000) + "ms - " + (Math.round((((process.cpuUsage().user + process.cpuUsage().system) / 1000000) / process.uptime()) * 1000) / 1000) + "%"; + statusBody += "
Thread PID: " + process.pid + "
"; + + res.writeHead(200, http.STATUS_CODES[200], { + "Content-Type": "text/html; charset=utf-8" + }); + res.end((res.head == "" ? "SVR.JS status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")) + "" : res.head.replace(//i, "" + name.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + " status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")) + "")) + "

" + name.replace(/&/g, "&").replace(//g, ">") + " status" + (req.headers.host == undefined ? "" : " for " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "

" + statusBody + (res.foot == "" ? "" : res.foot)); + return; + } + next(); +} \ No newline at end of file diff --git a/src/utils/sizify.js b/src/utils/sizify.js new file mode 100644 index 0000000..37fe2de --- /dev/null +++ b/src/utils/sizify.js @@ -0,0 +1,13 @@ +function sizify(bytes, addI) { + if (bytes == 0) return "0"; + if (bytes < 0) bytes = -bytes; + const prefixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"]; + let prefixIndex = Math.floor(Math.log2 ? Math.log2(bytes) / 10 : (Math.log(bytes) / (Math.log(2) * 10))); + if (prefixIndex >= prefixes.length - 1) prefixIndex = prefixes.length - 1; + let prefixIndexTranslated = Math.pow(2, 10 * prefixIndex); + let decimalPoints = 2 - Math.floor(Math.log10 ? Math.log10(bytes / prefixIndexTranslated) : (Math.log(bytes / prefixIndexTranslated) / Math.log(10))); + if (decimalPoints < 0) decimalPoints = 0; + return (Math.ceil((bytes / prefixIndexTranslated) * Math.pow(10, decimalPoints)) / Math.pow(10, decimalPoints)) + prefixes[prefixIndex] + ((prefixIndex > 0 && addI) ? "i" : ""); +} + +module.exports = sizify; \ No newline at end of file diff --git a/tests/utils/sizify.test.js b/tests/utils/sizify.test.js new file mode 100644 index 0000000..7a4408c --- /dev/null +++ b/tests/utils/sizify.test.js @@ -0,0 +1,57 @@ +const sizify = require('../../src/utils/sizify'); + +describe('"sizify" function', () => { + test('should return "0" for 0 bytes', () => { + expect(sizify(0)).toBe('0'); + }); + + test('should handle negative bytes', () => { + expect(sizify(-1024)).toBe('1K'); + }); + + test('should return correct size for small values', () => { + expect(sizify(1000)).toBe('1000'); + expect(sizify(1024)).toBe('1K'); + }); + + test('should return correct size for larger values', () => { + expect(sizify(1048576)).toBe('1M'); + expect(sizify(1073741824)).toBe('1G'); + expect(sizify(1099511627776)).toBe('1T'); + expect(sizify(1125899906842624)).toBe('1P'); + expect(sizify(1152921504606846976)).toBe('1E'); + expect(sizify(1180591620717411303424)).toBe('1Z'); + expect(sizify(1208925819614629174706176)).toBe('1Y'); + expect(sizify(1237940039285380274899124224)).toBe('1R'); + expect(sizify(1267650600228229401496703205376)).toBe('1Q'); + }); + + test('should handle very large values', () => { + const largeValue = 2 ** 100; // A very large number + expect(sizify(largeValue)).toBe('1Q'); + }); + + test('should add "i" suffix when addI is true', () => { + expect(sizify(1024, true)).toBe('1Ki'); + expect(sizify(1048576, true)).toBe('1Mi'); + expect(sizify(1073741824, true)).toBe('1Gi'); + }); + + test('should not add "i" suffix when addI is false', () => { + expect(sizify(1024, false)).toBe('1K'); + expect(sizify(1048576, false)).toBe('1M'); + expect(sizify(1073741824, false)).toBe('1G'); + }); + + test('should handle decimal points correctly', () => { + expect(sizify(1500)).toBe('1.47K'); + expect(sizify(1500000)).toBe('1.44M'); + expect(sizify(1500000000)).toBe('1.4G'); + }); + + test('should handle edge cases', () => { + expect(sizify(1)).toBe('1'); + expect(sizify(1023)).toBe('1023'); + expect(sizify(1025)).toBe('1.01K'); + }); +});