1
0
mirror of https://github.com/fcwu/docker-ubuntu-vnc-desktop synced 2026-08-05 16:12:41 +02:00

refactor: code restructure

1. split frontend and backend
2. simplify supervisor configuration
3. align backend log format with supervisor
4. set nginx worker to 1
5. delete static web pages
This commit is contained in:
Doro Wu
2018-03-13 21:24:45 +08:00
parent f5da5d152f
commit 99af8cd699
21 changed files with 14 additions and 292 deletions
@@ -0,0 +1,15 @@
class Default(object):
DEBUG = True
class Development(Default):
PHASE = 'development'
class Staging(Default):
PHASE = 'staging'
class Production(Default):
PHASE = 'production'
DEBUG = False
@@ -0,0 +1,89 @@
#!/usr/bin/env python
import sys
import logging
import logging.handlers
#The terminal has 8 colors with codes from 0 to 7
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
#These are the sequences need to get colored ouput
RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
BOLD_SEQ = "\033[1m"
#The background is set with 40 plus the number of the color,
#and the foreground with 30
COLORS = {
'WARNING': COLOR_SEQ % (30 + YELLOW) + 'WARN ' + RESET_SEQ,
'INFO': COLOR_SEQ % (30 + WHITE) + 'INFO ' + RESET_SEQ,
'DEBUG': COLOR_SEQ % (30 + BLUE) + 'DEBUG' + RESET_SEQ,
'CRITICAL': COLOR_SEQ % (30 + YELLOW) + 'CRITI' + RESET_SEQ,
'ERROR': COLOR_SEQ % (30 + RED) + 'ERROR' + RESET_SEQ,
}
class ColoredFormatter(logging.Formatter):
def __init__(self, msg, use_color=True):
logging.Formatter.__init__(self, msg)
self.use_color = use_color
def format(self, record):
if self.use_color:
record.levelname = COLORS.get(record.levelname, record.levelname)
return logging.Formatter.format(self, record)
class LoggingConfiguration(object):
COLOR_FORMAT = "%(asctime)s" + \
" %(levelname)s %(message)s " + \
"(" + BOLD_SEQ + "%(filename)s" + RESET_SEQ + ":%(lineno)d)"
NO_COLOR_FORMAT = "%(asctime)s %(levelname)s " + \
"%(message)s " + \
"(%(filename)s:%(lineno)d)"
FILE_FORMAT = "%(asctime)s %(levelname)s " + \
"%(message)s "
@classmethod
def set(cls, log_level, log_filename, append=None, **kwargs):
""" Configure a rotating file logging
"""
logger = logging.getLogger()
logger.setLevel(log_level)
COLOR_FORMAT = cls.COLOR_FORMAT
NO_COLOR_FORMAT = cls.NO_COLOR_FORMAT
FILE_FORMAT = cls.FILE_FORMAT
if 'name' in kwargs:
COLOR_FORMAT = COLOR_FORMAT.replace('%(threadName)-22s',
'%-22s' % (kwargs['name']))
NO_COLOR_FORMAT = NO_COLOR_FORMAT.replace(
'%(threadName)-22s', '%-22s' % (kwargs['name']))
FILE_FORMAT = FILE_FORMAT.replace(
'%(threadName)-22s', '%s' % (kwargs['name']))
# Log to rotating file
try:
fh = logging.handlers.RotatingFileHandler(log_filename,
mode='a+',
backupCount=3)
fh = logging.FileHandler(log_filename, mode='a+')
fh.setFormatter(ColoredFormatter(FILE_FORMAT, False))
fh.setLevel(log_level)
logger.addHandler(fh)
if not append:
# Create a new log file on every new
fh.doRollover()
except:
pass
# Log to sys.stderr using log level passed through command line
if log_level != logging.NOTSET:
log_handler = logging.StreamHandler(sys.stdout)
if sys.platform.find('linux') >= 0:
formatter = ColoredFormatter(COLOR_FORMAT)
else:
formatter = ColoredFormatter(NO_COLOR_FORMAT, False)
log_handler.setFormatter(formatter)
log_handler.setLevel(log_level)
logger.addHandler(log_handler)
@@ -0,0 +1,17 @@
Flask==0.10.1
Flask-Login==0.2.11
Jinja2==2.7.3
MarkupSafe==0.23
Werkzeug==0.9.6
argparse==1.2.1
backports.ssl-match-hostname==3.4.0.2
docker-py==0.5.3
gevent==1.0.1
gevent-websocket==0.9.3
greenlet==0.4.5
itsdangerous==0.24
peewee==2.4.1
requests==2.4.3
six==1.8.0
websocket-client==0.21.0
wsgiref==0.1.2
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env python
import os
import time
import sys
import subprocess
import signal
def run_with_reloader(main_func, extra_files=None, interval=1):
"""Run the given function in an independent python interpreter."""
def find_files(directory="./"):
for root, dirs, files in os.walk(directory):
for basename in files:
if basename.endswith('.py'):
filename = os.path.join(root, basename)
yield filename
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
try:
os.setpgid(0, 0)
main_func()
except KeyboardInterrupt:
pass
return
procs = None
try:
while True:
print('* Restarting with reloader ' + str(sys.executable))
args = [sys.executable] + sys.argv
new_environ = os.environ.copy()
new_environ['WERKZEUG_RUN_MAIN'] = 'true'
procs = subprocess.Popen(args, env=new_environ)
mtimes = {}
restart = False
while not restart:
for filename in find_files():
try:
mtime = os.stat(filename).st_mtime
except OSError:
continue
old_time = mtimes.get(filename)
if old_time is None:
mtimes[filename] = mtime
continue
elif mtime > old_time:
print('* Detected change in %r, reloading' % filename)
restart = True
break
time.sleep(interval)
killpg(procs.pid, signal.SIGTERM)
except KeyboardInterrupt:
pass
finally:
killpg(procs.pid, signal.SIGTERM)
def killpg(pgid, send_signal=signal.SIGKILL):
print('kill PGID {}'.format(pgid))
try:
os.killpg(pgid, send_signal)
#os.killpg(pgid, signal.SIGKILL)
except:
pass
def main():
def run_server():
import socket
os.environ['CONFIG'] = CONFIG
from vnc import app
# websocket conflict: WebSocketHandler
if DEBUG or STAGING:
# from werkzeug.debug import DebuggedApplication
app.debug = True
# app = DebuggedApplication(app, evalex=True)
pgid = os.getpgid(0)
signal.signal(signal.SIGTERM, lambda *args: killpg(pgid))
signal.signal(signal.SIGHUP, lambda *args: killpg(pgid))
signal.signal(signal.SIGINT, lambda *args: killpg(pgid))
try:
app.run(host='', port=PORT)
except socket.error as e:
print(e)
DEBUG = True if '--debug' in sys.argv else False
STAGING = True if '--staging' in sys.argv else False
CONFIG = 'config.Development' if DEBUG else 'config.Production'
CONFIG = 'config.Staging' if STAGING else CONFIG
PORT = 6079
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
if DEBUG or STAGING:
main = lambda: run_with_reloader(run_server)
else:
main = run_server
main()
if __name__ == "__main__":
main()
@@ -0,0 +1,138 @@
from flask import (Flask,
request,
abort,
)
import os
import json
from functools import wraps
import subprocess
import time
# Flask app
app = Flask('novnc2')
CONFIG = os.environ.get('CONFIG') or 'config.Development'
app.config.from_object('config.Default')
app.config.from_object(CONFIG)
FIRST = 'RESOLUTION' not in os.environ
# logging
import logging
from log.config import LoggingConfiguration
LoggingConfiguration.set(
logging.DEBUG if os.getenv('DEBUG') else logging.INFO,
'/var/log/web.log'
)
def exception_to_json(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
result = func(*args, **kwargs)
return result
except (BadRequest,
KeyError,
ValueError,
) as e:
result = {'error': {'code': 400,
'message': str(e)}}
except PermissionDenied as e:
result = {'error': {'code': 403,
'message': ', '.join(e.args)}}
except (NotImplementedError, RuntimeError, AttributeError) as e:
result = {'error': {'code': 500,
'message': ', '.join(e.args)}}
return json.dumps(result)
return wrapper
class PermissionDenied(Exception):
pass
class BadRequest(Exception):
pass
HTML_INDEX = '''<html><head>
<script type="text/javascript">
var w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
x = w.innerWidth || e.clientWidth || g.clientWidth,
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
var url = "redirect.html?width=" + x + "&height=" + (parseInt(y));
window.location.href = url;
</script>
<title>Page Redirection</title>
</head><body></body></html>'''
HTML_REDIRECT = '''<html><head>
<script type="text/javascript">
var port = window.location.port;
if (!port)
port = window.location.protocol[4] == 's' ? 443 : 80;
window.location.href = "vnc.html?autoconnect=1&autoscale=0&quality=3";
</script>
<title>Page Redirection</title>
</head><body></body></html>'''
@app.route('/')
def index():
return HTML_INDEX
@app.route('/api/status')
def status():
global FIRST
return json.dumps({
'default_resolution': FIRST
})
@app.route('/redirect.html')
def redirectme():
global FIRST
if not FIRST:
return HTML_REDIRECT
env = {'width': 1024, 'height': 768}
if 'width' in request.args:
env['width'] = request.args['width']
if 'height' in request.args:
env['height'] = request.args['height']
# sed
cmd = (
'sed -i \'s#'
'^exec /usr/bin/Xvfb.*$'
'#'
'exec /usr/bin/Xvfb :1 -screen 0 {width}x{height}x16'
'#\' /usr/local/bin/xvfb.sh'
).format(**env),
subprocess.check_call(cmd, shell=True)
# supervisorctrl reload
subprocess.check_call(['supervisorctl', 'restart', 'x:'])
# check all running
for i in range(40):
output = subprocess.check_output(['supervisorctl', 'status'])
for line in output.strip().split('\n'):
if line.find('RUNNING') < 0:
break
else:
FIRST = False
return HTML_REDIRECT
time.sleep(1)
logging.info('wait services is ready...')
abort(500, 'service is not ready, please restart container')
if __name__ == '__main__':
app.run(host=app.config['ADDRESS'], port=app.config['PORT'])