paivana

HTTP paywall reverse proxy
Log | Files | Refs | Submodules | README | LICENSE

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()