upstream_py.py (4980B)
1 #!/usr/bin/env python3 2 # upstream_py: Python-based upstream HTTP server used by the 3 # paivana reverse-proxy tests. Implements the same small set 4 # of canned endpoints as upstream_mhd.c. 5 6 import sys 7 import time 8 from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer 9 10 UPSTREAM_NAME = "py" 11 12 13 class Handler(BaseHTTPRequestHandler): 14 protocol_version = "HTTP/1.1" 15 16 def log_message(self, fmt, *args): 17 sys.stderr.write("upstream_py: " + (fmt % args) + "\n") 18 19 def _send_text(self, code, body, content_type="text/plain"): 20 if isinstance(body, str): 21 body = body.encode("utf-8") 22 self.send_response(code) 23 self.send_header("X-Upstream", UPSTREAM_NAME) 24 self.send_header("Content-Type", content_type) 25 self.send_header("Content-Length", str(len(body))) 26 self.end_headers() 27 self.wfile.write(body) 28 29 def _read_body(self): 30 cl = int(self.headers.get("Content-Length", "0") or 0) 31 if cl > 0: 32 return self.rfile.read(cl) 33 return b"" 34 35 def do_OPTIONS(self): 36 self.send_response(204) 37 self.send_header("X-Upstream", UPSTREAM_NAME) 38 self.send_header("Allow", 39 "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS") 40 self.send_header("Content-Length", "0") 41 self.end_headers() 42 43 def do_HEAD(self): 44 if self.path == "/hello" or self.path.startswith("/large/"): 45 # no body regardless 46 body_len = 0 47 if self.path.startswith("/large/"): 48 try: 49 n = int(self.path[len("/large/"):]) 50 body_len = max(0, min(n, 10 * 1024 * 1024)) 51 except ValueError: 52 body_len = 0 53 self.send_response(200) 54 self.send_header("X-Upstream", UPSTREAM_NAME) 55 self.send_header("Content-Type", "application/octet-stream") 56 self.send_header("Content-Length", str(body_len)) 57 self.end_headers() 58 return 59 self._send_text(404, "not found\n") 60 61 def do_GET(self): 62 if self.path == "/hello": 63 self._send_text(200, f"Hello from {UPSTREAM_NAME}\n") 64 return 65 if self.path.startswith("/status/"): 66 try: 67 code = int(self.path[len("/status/"):]) 68 except ValueError: 69 code = 500 70 if code < 100 or code > 599: 71 code = 500 72 self._send_text(code, f"status {code}\n") 73 return 74 if self.path.startswith("/large/"): 75 try: 76 n = int(self.path[len("/large/"):]) 77 except ValueError: 78 n = 0 79 n = max(0, min(n, 10 * 1024 * 1024)) 80 buf = bytes(((ord('A') + i % 26) for i in range(n))) 81 self._send_text(200, buf, "application/octet-stream") 82 return 83 if self.path.startswith("/slow/"): 84 try: 85 ms = int(self.path[len("/slow/"):]) 86 except ValueError: 87 ms = 0 88 ms = max(0, min(ms, 30000)) 89 time.sleep(ms / 1000.0) 90 self._send_text(200, "slept\n") 91 return 92 if self.path == "/echo-headers": 93 parts = [] 94 for k, v in self.headers.items(): 95 parts.append(f"{k}: {v}\n") 96 self._send_text(200, "".join(parts)) 97 return 98 self._send_text(404, "not found\n") 99 100 def do_POST(self): 101 body = self._read_body() 102 if self.path == "/echo": 103 self._send_text(200, body, "application/octet-stream") 104 return 105 if self.path == "/upload": 106 self._send_text(200, f"Received {len(body)} bytes\n") 107 return 108 self._send_text(404, "not found\n") 109 110 def do_PUT(self): 111 body = self._read_body() 112 if self.path == "/put": 113 self._send_text(200, f"PUT received {len(body)}\n") 114 return 115 self._send_text(404, "not found\n") 116 117 def do_PATCH(self): 118 body = self._read_body() 119 if self.path == "/patch": 120 self._send_text(200, f"PATCH received {len(body)}\n") 121 return 122 self._send_text(404, "not found\n") 123 124 def do_DELETE(self): 125 # drain any body 126 self._read_body() 127 if self.path.startswith("/item"): 128 self.send_response(204) 129 self.send_header("X-Upstream", UPSTREAM_NAME) 130 self.send_header("Content-Length", "0") 131 self.end_headers() 132 return 133 self._send_text(404, "not found\n") 134 135 136 def main(): 137 port = 8403 138 if len(sys.argv) > 1: 139 port = int(sys.argv[1]) 140 server = ThreadingHTTPServer(("", port), Handler) 141 sys.stderr.write(f"upstream_py listening on port {port}\n") 142 sys.stderr.flush() 143 try: 144 server.serve_forever() 145 except KeyboardInterrupt: 146 pass 147 server.server_close() 148 149 150 if __name__ == "__main__": 151 main()