restructure code and use frappy.client for SECoP
- separation of SECoP part from webserver - use frappy client instead of own code for SECoP communication
This commit is contained in:
268
webserver.py
Executable file
268
webserver.py
Executable file
@ -0,0 +1,268 @@
|
||||
from gevent import monkey
|
||||
monkey.patch_all()
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import traceback
|
||||
import logging
|
||||
import json
|
||||
from collections import deque
|
||||
from datetime import date, datetime
|
||||
import tcp_lineserver
|
||||
import gevent
|
||||
import gevent.pywsgi
|
||||
import gevent.queue
|
||||
from gevent.lock import RLock
|
||||
import flask
|
||||
import pprint
|
||||
import random
|
||||
import uuid
|
||||
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, tcp_lineserver.Disconnected) 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()) + "<br>"
|
||||
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')
|
||||
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))
|
||||
logging.info('REPLY %s %r', path, msg)
|
||||
resp = flask.Response(json.dumps(msg), mimetype='application/json')
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
|
||||
|
||||
@app.route('/test/<file>')
|
||||
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/<file>')
|
||||
@app.route('/components/curves_settings_popup/<file>')
|
||||
@app.route('/components/action_entry/<file>')
|
||||
@app.route('/components/export_popup/<file>')
|
||||
@app.route('/components/dates_popup/<file>')
|
||||
@app.route('/components/menu_popup/<file>')
|
||||
@app.route('/components/help_popup/<file>')
|
||||
@app.route('/components/help_entry/<file>')
|
||||
@app.route('/components/control/<file>')
|
||||
@app.route('/components/divider/<file>')
|
||||
@app.route('/components/states_indicator/dates/<file>')
|
||||
@app.route('/res/<file>')
|
||||
@app.route('/jsFiles/<file>')
|
||||
@app.route('/cssFiles/<file>')
|
||||
@app.route('/externalFiles/<file>')
|
||||
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/<file>.map')
|
||||
def replace_by_empty(file):
|
||||
return ""
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def default():
|
||||
return general_file('SEAWebClient.html')
|
||||
|
||||
|
||||
@app.route('/<file>')
|
||||
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 handle_term(sig, _):
|
||||
server.stop()
|
||||
server.close()
|
||||
|
||||
|
||||
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)
|
||||
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()
|
Reference in New Issue
Block a user