# -*- coding: utf-8 -*-
|
"""Dev static server for the built frontend + /api reverse proxy to backend :8090.
|
Used because `npm run serve` cannot run inside the sandbox (node realpath on home dir is blocked).
|
Run: python dev-proxy.py (serves ./dist on :8080, proxies /api -> http://localhost:8090)
|
"""
|
import http.server
|
import os
|
import socketserver
|
import sys
|
import urllib.error
|
import urllib.request
|
|
DIST = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dist')
|
BACKEND = 'http://localhost:8090'
|
PORT = int(os.environ.get('FRONT_PORT', '8080'))
|
|
MIME = {
|
'.html': 'text/html; charset=utf-8',
|
'.js': 'application/javascript; charset=utf-8',
|
'.css': 'text/css; charset=utf-8',
|
'.json': 'application/json; charset=utf-8',
|
'.map': 'application/json',
|
'.png': 'image/png',
|
'.jpg': 'image/jpeg',
|
'.jpeg': 'image/jpeg',
|
'.gif': 'image/gif',
|
'.svg': 'image/svg+xml',
|
'.ico': 'image/x-icon',
|
'.woff': 'font/woff',
|
'.woff2': 'font/woff2',
|
'.ttf': 'font/ttf',
|
}
|
|
|
class Handler(http.server.BaseHTTPRequestHandler):
|
protocol_version = 'HTTP/1.1'
|
|
def do_GET(self):
|
if self.path.startswith('/api/'):
|
self.proxy()
|
else:
|
self.serve_static()
|
|
def do_POST(self):
|
if self.path.startswith('/api/'):
|
self.proxy()
|
else:
|
self.send_error(404, 'Not Found')
|
|
def serve_static(self):
|
path = self.path.split('?')[0]
|
if path in ('', '/'):
|
path = '/index.html'
|
rel = path.lstrip('/')
|
fp = os.path.normpath(os.path.join(DIST, rel))
|
if not fp.startswith(DIST):
|
self.send_error(403)
|
return
|
if os.path.isdir(fp):
|
fp = os.path.join(fp, 'index.html')
|
if not os.path.isfile(fp):
|
fp = os.path.join(DIST, 'index.html') # SPA fallback
|
try:
|
with open(fp, 'rb') as f:
|
data = f.read()
|
except OSError:
|
self.send_error(404)
|
return
|
ext = os.path.splitext(fp)[1].lower()
|
self.send_response(200)
|
self.send_header('Content-Type', MIME.get(ext, 'application/octet-stream'))
|
self.send_header('Content-Length', str(len(data)))
|
self.end_headers()
|
self.wfile.write(data)
|
|
def proxy(self):
|
url = BACKEND + self.path
|
body = None
|
headers = {}
|
if self.command == 'POST':
|
length = int(self.headers.get('Content-Length') or 0)
|
body = self.rfile.read(length)
|
ct = self.headers.get('Content-Type')
|
if ct:
|
headers['Content-Type'] = ct
|
req = urllib.request.Request(url, data=body, method=self.command, headers=headers)
|
try:
|
with urllib.request.urlopen(req, timeout=180) as resp:
|
data = resp.read()
|
self.send_response(resp.status)
|
self._copy_header(resp, 'Content-Type')
|
self._copy_header(resp, 'Content-Disposition')
|
self.send_header('Content-Length', str(len(data)))
|
self.end_headers()
|
self.wfile.write(data)
|
except urllib.error.HTTPError as e:
|
data = e.read()
|
self.send_response(e.code)
|
self._copy_header(e, 'Content-Type')
|
self.send_header('Content-Length', str(len(data)))
|
self.end_headers()
|
self.wfile.write(data)
|
except Exception as e: # noqa: BLE001
|
self.send_error(502, str(e))
|
|
def _copy_header(self, resp, name):
|
val = resp.headers.get(name)
|
if val:
|
self.send_header(name, val)
|
|
def log_message(self, fmt, *args):
|
sys.stderr.write('%s - %s\n' % (self.address_string(), fmt % args))
|
|
|
if __name__ == '__main__':
|
socketserver.TCPServer.allow_reuse_address = True
|
with socketserver.ThreadingTCPServer(('', PORT), Handler) as httpd:
|
print('serving %s on :%d, proxy /api -> %s' % (DIST, PORT, BACKEND), flush=True)
|
httpd.serve_forever()
|