Copied generated ok-server.py from another repository.
This commit is contained in:
226
ok-server.py
Executable file
226
ok-server.py
Executable 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)
|
||||
Reference in New Issue
Block a user