#!/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)