feat: add support for HTTPS with --pem option in both Python and Node.js servers
This commit is contained in:
@@ -20,16 +20,21 @@ To run the server from the command line:
|
|||||||
python3 ok-server.py
|
python3 ok-server.py
|
||||||
python3 ok-server.py --look basic
|
python3 ok-server.py --look basic
|
||||||
python3 ok-server.py --bind 127.0.0.1 --port 8080 --look tailwind
|
python3 ok-server.py --bind 127.0.0.1 --port 8080 --look tailwind
|
||||||
|
python3 ok-server.py --pem /path/to/bundle.pem --port 8443
|
||||||
```
|
```
|
||||||
|
|
||||||
`--look` accepts `basic`, `nice`, `bootstrap`, or `tailwind`.
|
`--look` accepts `basic`, `nice`, `bootstrap`, or `tailwind`.
|
||||||
You can override the look per request with the `look` query parameter, for example:
|
You can override the look per request with the `look` query parameter, for example:
|
||||||
`http://localhost:8080/?look=tailwind`
|
`http://localhost:8080/?look=tailwind`
|
||||||
|
|
||||||
|
`--pem` accepts a path to a PEM file containing the certificate chain and private key.
|
||||||
|
When provided, the server listens on HTTPS instead of plain HTTP.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node ok-server.mjs
|
node ok-server.mjs
|
||||||
node ok-server.mjs --look basic
|
node ok-server.mjs --look basic
|
||||||
node ok-server.mjs --bind 127.0.0.1 --port 8080 --look tailwind
|
node ok-server.mjs --bind 127.0.0.1 --port 8080 --look tailwind
|
||||||
|
node ok-server.mjs --pem /path/to/bundle.pem --port 8443
|
||||||
```
|
```
|
||||||
|
|
||||||
Connect to the server using a web browser or a tool like `curl`:
|
Connect to the server using a web browser or a tool like `curl`:
|
||||||
|
|||||||
+38
-8
@@ -8,6 +8,8 @@
|
|||||||
* - Displays incoming X-* headers when present
|
* - Displays incoming X-* headers when present
|
||||||
* - If User-Agent contains "curl" or "wget" (case-insensitive), the server
|
* - If User-Agent contains "curl" or "wget" (case-insensitive), the server
|
||||||
* responds with Content-Type: text/plain; otherwise Content-Type: text/html.
|
* 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.
|
||||||
* - --look controls which HTML variant is returned for non-CLI agents:
|
* - --look controls which HTML variant is returned for non-CLI agents:
|
||||||
* * basic - plain HTML with no external references
|
* * basic - plain HTML with no external references
|
||||||
* * nice - includes Google Font "Noto Sans" (default)
|
* * nice - includes Google Font "Noto Sans" (default)
|
||||||
@@ -18,6 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import http from "node:http";
|
import http from "node:http";
|
||||||
|
import https from "node:https";
|
||||||
|
import fs from "node:fs";
|
||||||
import { URL } from "node:url";
|
import { URL } from "node:url";
|
||||||
|
|
||||||
const VALID_LOOKS = new Set(["basic", "nice", "bootstrap", "tailwind"]);
|
const VALID_LOOKS = new Set(["basic", "nice", "bootstrap", "tailwind"]);
|
||||||
@@ -258,6 +262,7 @@ function parseArgs(argv) {
|
|||||||
let bind = "0.0.0.0";
|
let bind = "0.0.0.0";
|
||||||
let port = 8080;
|
let port = 8080;
|
||||||
let look = "nice";
|
let look = "nice";
|
||||||
|
let pem = null;
|
||||||
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
for (let i = 0; i < argv.length; i += 1) {
|
||||||
const arg = argv[i];
|
const arg = argv[i];
|
||||||
@@ -284,6 +289,15 @@ function parseArgs(argv) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg === "--pem") {
|
||||||
|
i += 1;
|
||||||
|
if (i >= argv.length) {
|
||||||
|
throw new Error(`${arg} requires a value`);
|
||||||
|
}
|
||||||
|
pem = argv[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg === "--look") {
|
if (arg === "--look") {
|
||||||
i += 1;
|
i += 1;
|
||||||
if (i >= argv.length) {
|
if (i >= argv.length) {
|
||||||
@@ -300,11 +314,12 @@ function parseArgs(argv) {
|
|||||||
if (arg === "-h" || arg === "--help") {
|
if (arg === "-h" || arg === "--help") {
|
||||||
const help = [
|
const help = [
|
||||||
"Usage:",
|
"Usage:",
|
||||||
" node ok-server.mjs [-b|--bind ADDRESS] [-p|--port PORT] [--look basic|nice|bootstrap|tailwind]",
|
" node ok-server.mjs [-b|--bind ADDRESS] [-p|--port PORT] [--pem PEMFILE] [--look basic|nice|bootstrap|tailwind]",
|
||||||
"",
|
"",
|
||||||
"Defaults:",
|
"Defaults:",
|
||||||
" --bind 0.0.0.0",
|
" --bind 0.0.0.0",
|
||||||
" --port 8080",
|
" --port 8080",
|
||||||
|
" --pem (none, plain HTTP)",
|
||||||
" --look nice",
|
" --look nice",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
console.log(help);
|
console.log(help);
|
||||||
@@ -314,12 +329,13 @@ function parseArgs(argv) {
|
|||||||
throw new Error(`Unknown argument: ${arg}`);
|
throw new Error(`Unknown argument: ${arg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { bind, port, look };
|
return { bind, port, look, pem };
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(bind, port, look) {
|
function run(bind, port, look, pem) {
|
||||||
let isShuttingDown = false;
|
let isShuttingDown = false;
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
|
function createRequestHandler(req, res) {
|
||||||
const method = req.method || "GET";
|
const method = req.method || "GET";
|
||||||
|
|
||||||
if (method !== "GET" && method !== "HEAD") {
|
if (method !== "GET" && method !== "HEAD") {
|
||||||
@@ -342,7 +358,20 @@ function run(bind, port, look) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.end(body);
|
res.end(body);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
let server;
|
||||||
|
if (pem) {
|
||||||
|
let pemContent;
|
||||||
|
try {
|
||||||
|
pemContent = fs.readFileSync(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);
|
||||||
|
}
|
||||||
|
|
||||||
server.on("request", (req) => {
|
server.on("request", (req) => {
|
||||||
const clientIp = getClientIp(req);
|
const clientIp = getClientIp(req);
|
||||||
@@ -358,7 +387,8 @@ function run(bind, port, look) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.listen(port, bind, () => {
|
server.listen(port, bind, () => {
|
||||||
console.log(`Serving on ${bind}:${port} (default look=${look}) (Ctrl-C to stop)`);
|
const scheme = pem ? "https" : "http";
|
||||||
|
console.log(`Serving on ${scheme}://${bind}:${port} (default look=${look}) (Ctrl-C to stop)`);
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleShutdownSignal() {
|
function handleShutdownSignal() {
|
||||||
@@ -375,8 +405,8 @@ function run(bind, port, look) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { bind, port, look } = parseArgs(process.argv.slice(2));
|
const { bind, port, look, pem } = parseArgs(process.argv.slice(2));
|
||||||
run(bind, port, look);
|
run(bind, port, look, pem);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
process.stderr.write(`${err.message}\n`);
|
process.stderr.write(`${err.message}\n`);
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
|
|||||||
+17
-3
@@ -7,6 +7,8 @@ Behavior:
|
|||||||
- Displays whether any incoming X-* headers were detected and lists them
|
- Displays whether any incoming X-* headers were detected and lists them
|
||||||
- If User-Agent contains "curl" or "wget" (case-insensitive) the server
|
- If User-Agent contains "curl" or "wget" (case-insensitive) the server
|
||||||
responds with Content-Type: text/plain; otherwise Content-Type: text/html.
|
responds with Content-Type: text/plain; otherwise Content-Type: text/html.
|
||||||
|
- --pem path to a PEM bundle (cert chain + private key) to enable HTTPS;
|
||||||
|
without --pem the server uses plain HTTP.
|
||||||
- --look controls which HTML variant is returned for non-CLI agents:
|
- --look controls which HTML variant is returned for non-CLI agents:
|
||||||
* basic - plain HTML with no external references
|
* basic - plain HTML with no external references
|
||||||
* nice - includes Google Font "Noto Sans" (default)
|
* nice - includes Google Font "Noto Sans" (default)
|
||||||
@@ -30,6 +32,7 @@ Test:
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import signal
|
import signal
|
||||||
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
from html import escape
|
from html import escape
|
||||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
@@ -303,7 +306,7 @@ class OkHandler(BaseHTTPRequestHandler):
|
|||||||
(self.client_address[0], self.log_date_time_string(), format % args))
|
(self.client_address[0], self.log_date_time_string(), format % args))
|
||||||
|
|
||||||
|
|
||||||
def run(bind: str, port: int, look: str):
|
def run(bind: str, port: int, look: str, pem: str | None = None):
|
||||||
addr = (bind, port)
|
addr = (bind, port)
|
||||||
try:
|
try:
|
||||||
with ThreadingHTTPServer(addr, OkHandler) as httpd:
|
with ThreadingHTTPServer(addr, OkHandler) as httpd:
|
||||||
@@ -316,7 +319,16 @@ def run(bind: str, port: int, look: str):
|
|||||||
|
|
||||||
# attach chosen look to server so handlers can use it as default
|
# attach chosen look to server so handlers can use it as default
|
||||||
httpd.look = look
|
httpd.look = look
|
||||||
print(f"Serving on {bind}:{port} (default look={look}) (Ctrl-C to stop)")
|
if pem:
|
||||||
|
try:
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
|
ctx.load_cert_chain(pem)
|
||||||
|
except (ssl.SSLError, OSError) as e:
|
||||||
|
print(f"Cannot load PEM file '{pem}': {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True)
|
||||||
|
scheme = "https" if pem else "http"
|
||||||
|
print(f"Serving on {scheme}://{bind}:{port} (default look={look}) (Ctrl-C to stop)")
|
||||||
httpd.serve_forever()
|
httpd.serve_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nShutting down server")
|
print("\nShutting down server")
|
||||||
@@ -333,5 +345,7 @@ if __name__ == "__main__":
|
|||||||
help="Port to listen on (default: 8080)")
|
help="Port to listen on (default: 8080)")
|
||||||
parser.add_argument("--look", choices=VALID_LOOKS, default=VALID_LOOKS[1],
|
parser.add_argument("--look", choices=VALID_LOOKS, default=VALID_LOOKS[1],
|
||||||
help="Default HTML look for non-cli agents (basic, nice, bootstrap, tailwind). Default: nice")
|
help="Default HTML look for non-cli agents (basic, nice, bootstrap, tailwind). Default: nice")
|
||||||
|
parser.add_argument("--pem", default=None, metavar="PEMFILE",
|
||||||
|
help="PEM bundle file (cert chain + private key) to enable HTTPS; plain HTTP if omitted")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
run(args.bind, args.port, args.look)
|
run(args.bind, args.port, args.look, args.pem)
|
||||||
|
|||||||
Reference in New Issue
Block a user