Copied generated ok-server.py from another repository.

This commit is contained in:
2026-03-27 08:29:25 +01:00
parent 58e31c2525
commit 22fbb896ea

226
ok-server.py Executable file
View File

@@ -0,0 +1,226 @@
#!/usr/bin/env python3
"""Simple HTTP server that responds 200 with a test message including client IP.
Behavior:
- GET returns HTTP/1.1 200 with a message including the requester IP
- HEAD returns the same headers as GET but no body
- If User-Agent contains "curl" or "wget" (case-insensitive) the server
responds with Content-Type: text/plain; otherwise Content-Type: text/html.
- --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)
* bootstrap - Bootstrap 5 layout
- The URL query parameter "look" (e.g. /?look=nice) overrides the command-line --look
for that request only. Values are case-insensitive and must be one of basic,nice,bootstrap.
Usage:
python3 ok_server.py # binds 0.0.0.0:8000, nice look
python3 ok_server.py --look basic
python3 ok_server.py -b 127.0.0.1 -p 8080 --look bootstrap
Test:
curl -i http://localhost:8000/ # text/plain for curl
curl -i "http://localhost:8000/?look=basic" # request-level override
curl -I http://localhost:8000/ # HEAD (headers only)
wget -S -O - http://localhost:8000/ # text/plain for wget
open http://localhost:8000/ # browser gets HTML variant per look
"""
import argparse
import sys
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from typing import Tuple
from urllib.parse import urlparse, parse_qs
PLAIN_TEMPLATE = (
"Hello, This is a test HTTP server.\n\n"
"Your request came from {ip}.\n\n"
"Have a nice day!\n"
)
HTML_BASIC = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test HTTP server</title>
</head>
<body>
<h1>Hello,</h1>
<p>This is a test HTTP server.</p>
<p>Your request came from <strong>{ip}</strong>.</p>
<p>Have a nice day!</p>
</body>
</html>
"""
HTML_NICE = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Test HTTP server</title>
<!-- Google Font: Noto Sans -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&display=swap" rel="stylesheet">
<style>
body {{ font-family: "Noto Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; padding: 2rem; background: #f6f7fb; }}
.card {{ background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); max-width: 800px; margin: 2rem auto; }}
h1 {{ margin-top: 0; }}
</style>
</head>
<body>
<div class="card">
<h1>Hello,</h1>
<p>This is a test HTTP server.</p>
<p>Your request came from <strong>{ip}</strong>.</p>
<p>Have a nice day!</p>
</div>
</body>
</html>
"""
HTML_BOOTSTRAP = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Test HTTP server</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4">
<div class="container">
<a class="navbar-brand" href="#">Test HTTP server</a>
</div>
</nav>
<main class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-sm">
<div class="card-body">
<h1 class="card-title">Hello,</h1>
<p class="card-text">This is a test HTTP server.</p>
<p class="card-text">Your request came from <strong>{ip}</strong>.</p>
<hr>
<p class="mb-0">Have a nice day!</p>
</div>
</div>
</div>
</div>
</main>
<footer class="text-center mt-4 mb-4">
<small class="text-muted">Simple status page for firewall/testing</small>
</footer>
</body>
</html>
"""
VALID_LOOKS = ("basic", "nice", "bootstrap")
class OkHandler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def _is_cli_agent(self, ua: str) -> bool:
if not ua:
return False
ua_l = ua.lower()
return ("curl" in ua_l) or ("wget" in ua_l)
def _get_request_look(self) -> str:
"""Return look from URL query (if valid) or None."""
# parse query string from the request path
parsed = urlparse(self.path)
qs = parse_qs(parsed.query)
look_vals = qs.get("look")
if not look_vals:
return None
# use first value, case-insensitive
look = look_vals[0].strip().lower()
if look in VALID_LOOKS:
return look
return None
def _html_for_look(self, look: str, ip: str) -> str:
if look == "basic":
return HTML_BASIC.format(ip=ip)
if look == "nice":
return HTML_NICE.format(ip=ip)
# fallback to bootstrap
return HTML_BOOTSTRAP.format(ip=ip)
def _make_body_and_type(self, client_ip: str, user_agent: str) -> Tuple[bytes, str]:
if self._is_cli_agent(user_agent):
body = PLAIN_TEMPLATE.format(ip=client_ip).encode("utf-8")
ctype = "text/plain; charset=utf-8"
return body, ctype
# Check request-level override first
request_look = self._get_request_look()
if request_look:
look = request_look
else:
# fallback to server-level default (now: nice)
look = getattr(self.server, "look", "nice")
html = self._html_for_look(look, client_ip)
body = html.encode("utf-8")
ctype = "text/html; charset=utf-8"
return body, ctype
def _send_ok_headers(self, content_type: str, body_len: int):
self.send_response(200)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(body_len))
self.end_headers()
def do_GET(self):
client_ip = self.client_address[0]
user_agent = self.headers.get("User-Agent", "")
body, ctype = self._make_body_and_type(client_ip, user_agent)
try:
self._send_ok_headers(ctype, len(body))
self.wfile.write(body)
except BrokenPipeError:
# client closed connection early; ignore
pass
def do_HEAD(self):
client_ip = self.client_address[0]
user_agent = self.headers.get("User-Agent", "")
body, ctype = self._make_body_and_type(client_ip, user_agent)
# send same headers as GET but no body
self._send_ok_headers(ctype, len(body))
# minimal single-line log to stderr
def log_message(self, format, *args):
sys.stderr.write("%s - - [%s] %s\n" %
(self.client_address[0], self.log_date_time_string(), format % args))
def run(bind: str, port: int, look: str):
addr = (bind, port)
try:
with ThreadingHTTPServer(addr, OkHandler) as httpd:
# attach chosen look to server so handlers can use it as default
httpd.look = look
print(f"Serving on {bind}:{port} (default look={look}) (Ctrl-C to stop)")
httpd.serve_forever()
except KeyboardInterrupt:
print("\nShutting down server")
except Exception as e:
print("Server error:", e, file=sys.stderr)
raise
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="HTTP OK test server")
parser.add_argument("-b", "--bind", default="0.0.0.0",
help="Address to bind to (default: 0.0.0.0)")
parser.add_argument("-p", "--port", type=int, default=8000,
help="Port to listen on (default: 8000)")
parser.add_argument("--look", choices=VALID_LOOKS, default=VALID_LOOKS[1],
help="Default HTML look for non-cli agents (basic, nice, bootstrap). Default: nice")
args = parser.parse_args()
run(args.bind, args.port, args.look)