"""Deployment-only HTTP prefix adapter; modeling code remains unchanged.""" import http.client from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import re import subprocess import sys from urllib.parse import urlsplit PREFIX = '/box-sample' UPSTREAM_PORT = 8097 HOP = {'connection', 'transfer-encoding', 'keep-alive', 'upgrade', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer'} ROOT_URL = re.compile(rb'''(["'`])/(api/|src/|dxf_rasterizer\.html|model-report\.html)''') def rewrite(payload): return ROOT_URL.sub(lambda m: m[1] + PREFIX.encode() + b'/' + m[2], payload) class Gateway(BaseHTTPRequestHandler): def forward(self): parsed = urlsplit(self.path) if parsed.path == PREFIX: self.send_response(308) self.send_header('Location', PREFIX + '/' + ('?' + parsed.query if parsed.query else '')) self.send_header('Content-Length', '0') self.end_headers() return if not parsed.path.startswith(PREFIX + '/'): self.send_error(404) return if self.headers.get('Transfer-Encoding'): self.send_error(411, 'Content-Length required') return target = self.path[len(PREFIX):] connection = http.client.HTTPConnection('127.0.0.1', UPSTREAM_PORT, timeout=600) try: size = int(self.headers.get('Content-Length', '0')) if size < 0 or size > 512 * 1024 * 1024: self.send_error(413) return headers = {k: v for k, v in self.headers.items() if k.lower() not in HOP | {'accept-encoding', 'host'}} headers['Host'] = '127.0.0.1:' + str(UPSTREAM_PORT) connection.request(self.command, target, self.rfile.read(size) if size else None, headers) response = connection.getresponse() content_type = response.getheader('Content-Type', '').lower() text = any(t in content_type for t in ('json', 'javascript', 'text/html', 'text/css')) body = rewrite(response.read()) if text and self.command != 'HEAD' else None self.send_response(response.status) for key, value in response.getheaders(): if key.lower() in HOP | {'content-length', 'etag', 'content-md5'}: continue if key.lower() == 'location' and value.startswith('/') and not value.startswith('//'): value = PREFIX + value self.send_header(key, value) self.send_header('X-DMS-Deployment', 'box-sample') self.send_header('Cache-Control', 'no-store') if body is not None: self.send_header('Content-Length', str(len(body))) elif response.getheader('Content-Length') is not None: self.send_header('Content-Length', response.getheader('Content-Length')) self.end_headers() if self.command != 'HEAD': if body is not None: self.wfile.write(body) else: while chunk := response.read(65536): self.wfile.write(chunk) except (ValueError, OSError, http.client.HTTPException): self.send_error(502, 'Application not ready') finally: connection.close() do_GET = do_POST = do_HEAD = do_PUT = do_DELETE = forward if __name__ == '__main__': child = subprocess.Popen([sys.executable, 'src/backend/serve.py', '--host', '127.0.0.1', '--port', str(UPSTREAM_PORT)]) try: ThreadingHTTPServer(('0.0.0.0', 8080), Gateway).serve_forever() finally: child.terminate() child.wait(timeout=15)