feat: add Go server implementation with HTTPS support and update README for usage instructions
This commit is contained in:
+62
-31
@@ -8,8 +8,9 @@
|
||||
* - Displays incoming X-* headers when present
|
||||
* - If User-Agent contains "curl" or "wget" (case-insensitive), the server
|
||||
* responds with Content-Type: text/plain; otherwise Content-Type: text/html.
|
||||
* - --pem specifies a PEM bundle file (cert chain + private key) to enable HTTPS.
|
||||
* Without --pem the server listens on plain HTTP.
|
||||
* - --pem specifies a PEM bundle file (cert chain + private key). When provided,
|
||||
* the server listens on both HTTP (--port) and HTTPS (--tls-port).
|
||||
* Without --pem the server listens on plain HTTP only.
|
||||
* - --look controls which HTML variant is returned for non-CLI agents:
|
||||
* * basic - plain HTML with no external references
|
||||
* * nice - includes Google Font "Noto Sans" (default)
|
||||
@@ -261,6 +262,7 @@ function getClientIp(req) {
|
||||
function parseArgs(argv) {
|
||||
let bind = "0.0.0.0";
|
||||
let port = 8080;
|
||||
let tlsPort = 8443;
|
||||
let look = "nice";
|
||||
let pem = null;
|
||||
|
||||
@@ -298,6 +300,19 @@ function parseArgs(argv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--tls-port") {
|
||||
i += 1;
|
||||
if (i >= argv.length) {
|
||||
throw new Error(`${arg} requires a value`);
|
||||
}
|
||||
const parsedTlsPort = Number.parseInt(argv[i], 10);
|
||||
if (!Number.isInteger(parsedTlsPort) || parsedTlsPort < 1 || parsedTlsPort > 65535) {
|
||||
throw new Error(`Invalid tls-port: ${argv[i]}`);
|
||||
}
|
||||
tlsPort = parsedTlsPort;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--look") {
|
||||
i += 1;
|
||||
if (i >= argv.length) {
|
||||
@@ -314,12 +329,13 @@ function parseArgs(argv) {
|
||||
if (arg === "-h" || arg === "--help") {
|
||||
const help = [
|
||||
"Usage:",
|
||||
" node ok-server.mjs [-b|--bind ADDRESS] [-p|--port PORT] [--pem PEMFILE] [--look basic|nice|bootstrap|tailwind]",
|
||||
" node ok-server.mjs [-b|--bind ADDRESS] [-p|--port PORT] [--tls-port PORT] [--pem PEMFILE] [--look basic|nice|bootstrap|tailwind]",
|
||||
"",
|
||||
"Defaults:",
|
||||
" --bind 0.0.0.0",
|
||||
" --port 8080",
|
||||
" --pem (none, plain HTTP)",
|
||||
" --tls-port 8443",
|
||||
" --pem (none, plain HTTP only)",
|
||||
" --look nice",
|
||||
].join("\n");
|
||||
console.log(help);
|
||||
@@ -329,11 +345,12 @@ function parseArgs(argv) {
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
|
||||
return { bind, port, look, pem };
|
||||
return { bind, port, tlsPort, look, pem };
|
||||
}
|
||||
|
||||
function run(bind, port, look, pem) {
|
||||
function run(bind, port, tlsPort, look, pem) {
|
||||
let isShuttingDown = false;
|
||||
const servers = [];
|
||||
|
||||
function createRequestHandler(req, res) {
|
||||
const method = req.method || "GET";
|
||||
@@ -360,7 +377,32 @@ function run(bind, port, look, pem) {
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
let server;
|
||||
function setupServer(server, scheme, listenPort) {
|
||||
server.on("request", (req) => {
|
||||
const clientIp = getClientIp(req);
|
||||
const now = new Date().toUTCString();
|
||||
const method = req.method || "GET";
|
||||
const path = req.url || "/";
|
||||
process.stderr.write(`${clientIp} - - [${now}] "${method} ${path}"\n`);
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
process.stderr.write(`Server error (${scheme}): ${err.message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
server.listen(listenPort, bind, () => {
|
||||
console.log(`Serving on ${scheme}://${bind}:${listenPort} (default look=${look})`);
|
||||
});
|
||||
|
||||
servers.push(server);
|
||||
}
|
||||
|
||||
// Create HTTP server
|
||||
const httpServer = http.createServer(createRequestHandler);
|
||||
setupServer(httpServer, "http", port);
|
||||
|
||||
// Create HTTPS server if PEM is provided
|
||||
if (pem) {
|
||||
let pemContent;
|
||||
try {
|
||||
@@ -368,36 +410,25 @@ function run(bind, port, look, pem) {
|
||||
} catch (err) {
|
||||
throw new Error(`Cannot read PEM file '${pem}': ${err.message}`);
|
||||
}
|
||||
server = https.createServer({ cert: pemContent, key: pemContent }, createRequestHandler);
|
||||
} else {
|
||||
server = http.createServer(createRequestHandler);
|
||||
const httpsServer = https.createServer({ cert: pemContent, key: pemContent }, createRequestHandler);
|
||||
setupServer(httpsServer, "https", tlsPort);
|
||||
}
|
||||
|
||||
server.on("request", (req) => {
|
||||
const clientIp = getClientIp(req);
|
||||
const now = new Date().toUTCString();
|
||||
const method = req.method || "GET";
|
||||
const path = req.url || "/";
|
||||
process.stderr.write(`${clientIp} - - [${now}] "${method} ${path}"\n`);
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
process.stderr.write(`Server error: ${err.message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
server.listen(port, bind, () => {
|
||||
const scheme = pem ? "https" : "http";
|
||||
console.log(`Serving on ${scheme}://${bind}:${port} (default look=${look}) (Ctrl-C to stop)`);
|
||||
});
|
||||
|
||||
function handleShutdownSignal() {
|
||||
if (isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
isShuttingDown = true;
|
||||
console.log("\nShutting down server");
|
||||
server.close(() => process.exit(0));
|
||||
let closed = 0;
|
||||
servers.forEach((server) => {
|
||||
server.close(() => {
|
||||
closed += 1;
|
||||
if (closed === servers.length) {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
process.on("SIGINT", handleShutdownSignal);
|
||||
@@ -405,8 +436,8 @@ function run(bind, port, look, pem) {
|
||||
}
|
||||
|
||||
try {
|
||||
const { bind, port, look, pem } = parseArgs(process.argv.slice(2));
|
||||
run(bind, port, look, pem);
|
||||
const { bind, port, tlsPort, look, pem } = parseArgs(process.argv.slice(2));
|
||||
run(bind, port, tlsPort, look, pem);
|
||||
} catch (err) {
|
||||
process.stderr.write(`${err.message}\n`);
|
||||
process.exit(2);
|
||||
|
||||
Reference in New Issue
Block a user