From 22fbb896eaab073b55dfc5258b03c66ab5458272 Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Fri, 27 Mar 2026 08:29:25 +0100 Subject: [PATCH] Copied generated ok-server.py from another repository. --- ok-server.py | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100755 ok-server.py diff --git a/ok-server.py b/ok-server.py new file mode 100755 index 0000000..58a71d6 --- /dev/null +++ b/ok-server.py @@ -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 = """ + + + + Test HTTP server + + +

Hello,

+

This is a test HTTP server.

+

Your request came from {ip}.

+

Have a nice day!

+ + +""" + +HTML_NICE = """ + + + + + Test HTTP server + + + + + +
+

Hello,

+

This is a test HTTP server.

+

Your request came from {ip}.

+

Have a nice day!

+
+ + +""" + +HTML_BOOTSTRAP = """ + + + + + Test HTTP server + + + + + +
+
+
+
+
+

Hello,

+

This is a test HTTP server.

+

Your request came from {ip}.

+
+

Have a nice day!

+
+
+
+
+
+ + + + +""" + + +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)