#!/usr/bin/env python3 """A loopback HTTP observation lab, using Python's standard library.""" import argparse import http.client from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import json import threading import time class Handler(BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" def do_GET(self): found = self.path == "/lesson" payload = { "message": "網路實驗:本機 HTTP 回應" if found else "resource not found", "path": self.path, "peer_seen_by_server": list(self.client_address), } body = json.dumps(payload, ensure_ascii=False).encode("utf-8") self.send_response(200 if found else 404) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def log_message(self, *args): pass def observe(port, path="/lesson"): connection = http.client.HTTPConnection("127.0.0.1", port, timeout=3) start = time.perf_counter() try: connection.connect() local = connection.sock.getsockname() remote = connection.sock.getpeername() connection.request("GET", path) response = connection.getresponse() body = response.read() return { "method": "GET", "request_path": path, "response_headers": response.getheaders(), "client_endpoint": list(local), "server_endpoint": list(remote), "status": response.status, "content_length": int(response.getheader("Content-Length")), "received_bytes": len(body), "elapsed_ms": round((time.perf_counter() - start) * 1000, 3), "response": json.loads(body.decode("utf-8")), } except (OSError, http.client.HTTPException) as exc: return {"error": type(exc).__name__, "request_path": path} finally: connection.close() def main(): parser = argparse.ArgumentParser(description=__doc__) mode = parser.add_mutually_exclusive_group() mode.add_argument("--serve", action="store_true") mode.add_argument("--client", type=int, metavar="PORT") parser.add_argument("--path", default="/lesson") args = parser.parse_args() if args.client is not None: print(json.dumps(observe(args.client, args.path), ensure_ascii=False, indent=2)) return server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) if args.serve: print(f"Loopback HTTP server: 127.0.0.1:{server.server_port}", flush=True) try: server.serve_forever() except KeyboardInterrupt: pass finally: server.server_close() return thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() try: result = observe(server.server_port, args.path) print(json.dumps(result, ensure_ascii=False, indent=2)) finally: server.shutdown() server.server_close() thread.join(timeout=2) if __name__ == "__main__": main()