zhizhijie
3 小时以前 799ec6799ad9e994f7d369f059ca7682962f648f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# -*- 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()