mirror of
https://github.com/block/buzz.git
synced 2026-10-28 13:01:10 +00:00
Signed-off-by: cyberpunk <d6839277d8e2b34d4f50da40d4de079dc36df4c282e87147aaaa2674c070cf5a@buzz.block.builderlab.xyz>
82 lines
3.1 KiB
Python
82 lines
3.1 KiB
Python
"""Loopback-only fault proxy: lose a committed ACK without changing the command."""
|
|
import hashlib
|
|
import http.client
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import threading
|
|
|
|
ROOT = Path(os.environ["LABEL_EVIDENCE"])
|
|
CONFIG = json.loads((ROOT / "config.json").read_text())
|
|
LOCK = threading.Lock()
|
|
SEQUENCE = 0
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def log_message(self, *_args):
|
|
pass
|
|
|
|
def do_GET(self):
|
|
self.forward()
|
|
|
|
def do_POST(self):
|
|
self.forward()
|
|
|
|
def forward(self):
|
|
global SEQUENCE
|
|
length = int(self.headers.get("content-length", "0"))
|
|
if length > 1024 * 1024:
|
|
self.send_error(413)
|
|
return
|
|
body = self.rfile.read(length)
|
|
with LOCK:
|
|
SEQUENCE += 1
|
|
number = SEQUENCE
|
|
backend = CONFIG["backends"][number % 2]
|
|
drop = self.path == "/events" and (ROOT / "drop-next-ack").exists()
|
|
if drop:
|
|
(ROOT / "drop-next-ack").unlink()
|
|
event = json.loads(body) if self.path == "/events" else {}
|
|
entry = {"sequence": number, "backend": backend, "path": self.path,
|
|
"event": event.get("id"), "body_sha256": hashlib.sha256(body).hexdigest(),
|
|
"auth_sha256": hashlib.sha256(self.headers.get("authorization", "").encode()).hexdigest()}
|
|
conn = http.client.HTTPConnection("127.0.0.1", backend, timeout=30)
|
|
try:
|
|
headers = {key: value for key, value in self.headers.items()
|
|
if key.lower() not in ("content-length", "connection", "accept-encoding")}
|
|
conn.request(self.command, self.path, body, headers)
|
|
response = conn.getresponse()
|
|
data = response.read(2 * 1024 * 1024 + 1)
|
|
if len(data) > 2 * 1024 * 1024:
|
|
raise ValueError("response exceeds fixture budget")
|
|
entry["status"] = response.status
|
|
if self.path == "/events":
|
|
entry["ack"] = json.loads(data)
|
|
entry["dropped"] = drop
|
|
with LOCK:
|
|
with (ROOT / "PROXY.jsonl").open("a") as log:
|
|
log.write(json.dumps(entry) + "\n")
|
|
if drop:
|
|
self.close_connection = True
|
|
self.connection.shutdown(2)
|
|
self.connection.close()
|
|
return
|
|
self.send_response(response.status)
|
|
for key, value in response.getheaders():
|
|
if key.lower() not in ("transfer-encoding", "content-length", "connection", "content-encoding"):
|
|
self.send_header(key, value)
|
|
self.send_header("Content-Length", str(len(data)))
|
|
self.end_headers()
|
|
self.wfile.write(data)
|
|
except Exception as error:
|
|
with LOCK:
|
|
with (ROOT / "PROXY_ERRORS.log").open("a") as log:
|
|
log.write(str(error) + "\n")
|
|
self.close_connection = True
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
ThreadingHTTPServer(("127.0.0.1", CONFIG["proxy"]), Handler).serve_forever()
|