from gevent import monkey monkey.patch_all() import sys import time import signal import socket import traceback import logging import json import gevent import gevent.pywsgi import gevent.queue import flask import circularlog def guess_mimetype(filename): if filename.endswith('.js'): mimetype = 'text/javascript' elif filename.endswith('.css'): mimetype = 'text/css' elif filename.endswith('.ico'): mimetype = 'image/x-icon' elif filename.endswith(".png"): mimetype = "image/png" else: mimetype = 'text/html' return mimetype class MyEncoder(json.JSONEncoder): def default(self, obj): try: return super().default(obj) except TypeError: return int(obj) # try to convert SECoP Enum # SSE 'protocol' is described here: https://bit.ly/UPFyxY def to_json_sse(msg): txt = json.dumps(msg, separators=(',', ': '), cls=MyEncoder) logging.debug('data: %s', txt) return 'data: %s\n\n' % txt instrument = None app = flask.Flask(__name__) update_rider = circularlog.Rider("upd") pollinterval = 0.2 @app.route('/update') def get_update(path=None): # Client Adress: socket.getfqdn(flask.request.remote_addr) client = instrument.new_client() client.remote_info = circularlog.strtm() + " " + socket.getfqdn(flask.request.remote_addr.split(':')[-1]) @flask.stream_with_context def generator(): logging.info('UPDATE %s %s', client.id, socket.getfqdn(flask.request.remote_addr.split(':')[-1])) # msg = dict(type='id', id=client.id, title=instrument.title); # yield to_json_sse(msg) msg = dict(type='id', id=client.id, instrument=instrument.title, device=instrument.device) yield to_json_sse(msg) try: lastmsg = time.time() while True: if client.info() == "": print(time.time()-lastmsg) messages = client.poll() for msg in messages: update_rider.put('-', repr(msg)) yield to_json_sse(msg) if messages: lastmsg = time.time() else: if time.time() > lastmsg + 30: if not client.info(): raise GeneratorExit("no activity") logging.info('HEARTBEAT %s (%s)', client.id, "; ".join(client.info())) yield to_json_sse(dict(type='heartbeat')) lastmsg = time.time() else: gevent.sleep(pollinterval) except GeneratorExit as e: logging.info("except clause %r", repr(e)) logging.info('CLOSED %s', client.id) print('CLOSE client') instrument.remove(client) pass except Exception as e: logging.info('error') logging.error('%s', traceback.format_exc()) instrument.remove(client) # msg = dict(type='error',error=traceback.format_exc()) # yield to_json_sse(msg) resp = flask.Response(generator(), mimetype='text/event-stream') resp.headers['Access-Control-Allow-Origin'] = '*' return resp @app.route('/circular') def dump_circular(): circularlog.log() return "log" @app.route('/clients') def show_clients(): result = "" for id in instrument.clients: c = instrument.clients[id] result += c.remote_info + " " + "; ".join(c.info()) + "
" return result @app.route('/export') def export(): args = flask.request.args kwargs = dict((k, args.get(k)) for k in args) path = flask.request.path logging.info('GET %s %s', path, repr(kwargs)) try: id = kwargs.pop('id') print('export') client = instrument.clients[id] bytes = client.w_export(**kwargs) return flask.send_file( bytes, as_attachment=True, download_name='export.tsv', mimetype='text/tab-separated-values' ) except Exception as e: logging.error('%s', traceback.format_exc()) circularlog.log() msg = dict(type='error', request=path[1:], error=repr(e)) logging.error('MSG: %r', msg) resp = flask.Response(json.dumps(msg), mimetype='application/json') resp.headers['Access-Control-Allow-Origin'] = '*' return resp @app.route('/getblock') @app.route('/updateblock') @app.route('/sendcommand') @app.route('/console') @app.route('/graph') @app.route('/updategraph') @app.route('/gettime') @app.route('/getvars', methods=["GET", "POST"]) def reply(): args = flask.request.values kwargs = dict((k, args.get(k)) for k in args) path = flask.request.path logging.info('GET %s %r', path, kwargs) try: id = kwargs.pop('id') client = instrument.clients[id] msg = getattr(client, "w_" + path[1:])(**kwargs) except Exception as e: logging.error('%s', traceback.format_exc()) circularlog.log() msg = dict(type='error', request=path[1:], error=repr(e)) jsonmsg = json.dumps(msg) if len(jsonmsg) < 120: logging.info('REPLY %s %s', path, jsonmsg) else: logging.info('REPLY %s %s...', path, jsonmsg[:80]) logging.debug('REPLY %s %r', path, jsonmsg) resp = flask.Response(jsonmsg, mimetype='application/json') resp.headers['Access-Control-Allow-Origin'] = '*' return resp @app.route('/test/') def subdir_test_file(file): gevent.sleep(2) resp = flask.send_file("client/test/"+file, mimetype=guess_mimetype(file)) return resp @app.route('/components/curves_settings_popup/color_selector/') @app.route('/components/curves_settings_popup/') @app.route('/components/action_entry/') @app.route('/components/export_popup/') @app.route('/components/dates_popup/') @app.route('/components/menu_popup/') @app.route('/components/help_popup/') @app.route('/components/help_entry/') @app.route('/components/control/') @app.route('/components/divider/') @app.route('/components/states_indicator/dates/') @app.route('/res/') @app.route('/jsFiles/') @app.route('/cssFiles/') @app.route('/externalFiles/') def subdir_file(file): subdir = "/".join(flask.request.path.split("/")[1:-1]) resp = flask.send_file("client/" + subdir+"/"+file, mimetype=guess_mimetype(file)) # resp.headers['Content-Security-Policy'] = "sandbox; script-src 'unsafe-inline';" return resp @app.route('/externalFiles/maps/.map') def replace_by_empty(file): return "" @app.route('/') def main(): return general_file('SEAWebClient.html') @app.route('/select') def default(): out = [''''''] result = {} for stream, tags in instrument.get_stream_tags().items(): ins = tags.get('instrument', '0') result.setdefault(ins, []).append((stream, tags.get('device'))) bare_streams = result.pop('0', []) for ins, streams in result.items(): out.append(f'') out.extend(f'' for s, d in streams) out.append('') for stream, device in bare_streams: out.append(f'') out.extend(['
InstrumentDevices
{ins}{d or s}
{stream}{device}
', '']) return '\n'.join(out) @app.route('/') def general_file(file): subdir = "client/" try: resp = flask.send_file(subdir+file, mimetype=guess_mimetype(file)) except FileNotFoundError: logging.warning('file %s not found', file) return 'file not found' # resp.headers['Content-Security-Policy'] = "sandbox; script-src 'unsafe-inline';" return resp def hostport_split(hostport): h = hostport.split(':') return (h[0], int(h[1])) # def handle_pdb(sig, frame): # import pdb # print('PDB') # pdb.Pdb().set_trace(frame) def main(cls, **config): global instrument if not config: for arg in sys.argv[1:]: key, _, value = arg.partition('=') config[key] = value port = int(config['port']) inst_name = config['instrument'] instrument = cls(inst_name, config) # signal.signal(signal.SIGUSR1, handle_pdb) def handle_term(sig, _): server.stop() server.close() signal.signal(signal.SIGTERM, handle_term) app.debug = True logging.basicConfig(filename='log/%s.log' % inst_name, filemode='w', level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') #server = gevent.wsgi.WSGIServer(('', port), app, keyfile='key.key', certfile='key.crt') server = gevent.pywsgi.WSGIServer(('', port), app, log=logging.getLogger('server')) server.serve_forever()