rework of the server side
main change: the same server may be used for several instruments - client classes are 'Interactors' dealing with the parameters etc. - methods from history are added to the clients + improvements on the js client side
This commit is contained in:
160
webserver.py
160
webserver.py
@ -43,7 +43,91 @@ def to_json_sse(msg):
|
||||
return 'data: %s\n\n' % txt
|
||||
|
||||
|
||||
instrument = None
|
||||
class Server:
|
||||
"""singleton: created once in this module"""
|
||||
interactor_classes = None
|
||||
client_cls = None
|
||||
history_cls = None
|
||||
history = None
|
||||
single_instrument = None
|
||||
db = None
|
||||
|
||||
def __init__(self):
|
||||
self.instruments = {}
|
||||
self.clients = {}
|
||||
|
||||
def remove(self, client):
|
||||
try:
|
||||
del self.clients[client.id]
|
||||
except KeyError:
|
||||
logging.warning('client already removed %s', client.id)
|
||||
|
||||
def lookup_streams(self, instrument, stream=None, device=None):
|
||||
if self.single_instrument:
|
||||
instrument = self.single_instrument
|
||||
if stream:
|
||||
if isinstance(stream, str):
|
||||
streams = stream.split(',') if stream else []
|
||||
else:
|
||||
streams = stream
|
||||
else:
|
||||
streams = []
|
||||
device_names = devices = device.split(',') if device else []
|
||||
tags = {}
|
||||
if instrument:
|
||||
# tags['instrument'] = instrument
|
||||
stream_dict = self.db.get_streams(instrument, stream=list(streams), device=devices)
|
||||
streams.extend((s for s in stream_dict if s not in streams))
|
||||
if not devices:
|
||||
device_names = list(filter(None, (t.get('device') for t in stream_dict.values())))
|
||||
if streams:
|
||||
tags['stream'] = streams[0] if len(streams) == 1 else streams
|
||||
if devices:
|
||||
tags['device'] = devices[0] if len(devices) == 1 else devices
|
||||
return streams, tags, ','.join(device_names)
|
||||
|
||||
def register_client(self, instrument=None, stream=None, device=None):
|
||||
streams, tags, device_name = self.lookup_streams(instrument, stream, device)
|
||||
client = self.client_cls(self, streams, instrument or '', device_name)
|
||||
history = self.history_cls(self, instrument, device_name, tags)
|
||||
# history.db.debug = True
|
||||
# all relevant methods of the history instance are saved in client.handlers
|
||||
# so there is no reference needed to history anymore
|
||||
client.handlers.update(history.handlers)
|
||||
self.clients[client.id] = client
|
||||
return client
|
||||
|
||||
def run(self, port, db, history_cls, client_cls, single_instrument=None, **interactor_classes):
|
||||
self.single_instrument = single_instrument
|
||||
self.db = db
|
||||
self.history_cls = history_cls
|
||||
self.client_cls = client_cls
|
||||
self.interactor_classes = interactor_classes
|
||||
|
||||
app.debug = True
|
||||
|
||||
logging.basicConfig(filename='webserver.log', filemode='w', level=logging.INFO,
|
||||
format='%(asctime)s %(levelname)s %(message)s')
|
||||
|
||||
# srv = gevent.wsgi.WSGIServer(('', port), app, keyfile='key.key', certfile='key.crt')
|
||||
srv = gevent.pywsgi.WSGIServer(('', port), app, log=logging.getLogger('server'))
|
||||
|
||||
def handle_term(sig, frame):
|
||||
srv.stop()
|
||||
srv.close()
|
||||
|
||||
signal.signal(signal.SIGTERM, handle_term)
|
||||
|
||||
# def handle_pdb(sig, frame):
|
||||
# import pdb
|
||||
# print('PDB')
|
||||
# pdb.Pdb().set_trace(frame)
|
||||
# signal.signal(signal.SIGUSR1, handle_pdb)
|
||||
|
||||
srv.serve_forever()
|
||||
|
||||
|
||||
server = Server()
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
update_rider = circularlog.Rider("upd")
|
||||
@ -51,9 +135,11 @@ pollinterval = 0.2
|
||||
|
||||
|
||||
@app.route('/update')
|
||||
def get_update(path=None):
|
||||
def get_update(_=None):
|
||||
# Client Adress: socket.getfqdn(flask.request.remote_addr)
|
||||
client = instrument.new_client()
|
||||
kwargs = {k: flask.request.values.get(k) for k in ('instrument', 'stream', 'device')}
|
||||
|
||||
client = server.register_client(**kwargs)
|
||||
client.remote_info = circularlog.strtm() + " " + socket.getfqdn(flask.request.remote_addr.split(':')[-1])
|
||||
|
||||
@flask.stream_with_context
|
||||
@ -61,7 +147,8 @@ def get_update(path=None):
|
||||
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)
|
||||
msg = dict(type='id', id=client.id, instrument=kwargs.get('instrument', '<unknown>'),
|
||||
device=client.device_name)
|
||||
yield to_json_sse(msg)
|
||||
try:
|
||||
lastmsg = time.time()
|
||||
@ -87,11 +174,11 @@ def get_update(path=None):
|
||||
logging.info("except clause %r", repr(e))
|
||||
logging.info('CLOSED %s', client.id)
|
||||
print('CLOSE client')
|
||||
instrument.remove(client)
|
||||
server.remove(client)
|
||||
except Exception as e:
|
||||
logging.info('error')
|
||||
logging.error('%s', traceback.format_exc())
|
||||
instrument.remove(client)
|
||||
server.remove(client)
|
||||
# msg = dict(type='error',error=traceback.format_exc())
|
||||
# yield to_json_sse(msg)
|
||||
|
||||
@ -109,8 +196,8 @@ def dump_circular():
|
||||
@app.route('/clients')
|
||||
def show_clients():
|
||||
result = ""
|
||||
for id in instrument.clients:
|
||||
c = instrument.clients[id]
|
||||
for id in server.clients:
|
||||
c = server.clients[id]
|
||||
result += c.remote_info + " " + "; ".join(c.info()) + "<br>"
|
||||
return result
|
||||
|
||||
@ -123,9 +210,8 @@ def export():
|
||||
logging.info('GET %s %s', path, repr(kwargs))
|
||||
try:
|
||||
id = kwargs.pop('id')
|
||||
print('export')
|
||||
client = instrument.clients[id]
|
||||
bytes = client.w_export(**kwargs)
|
||||
client = server.clients[id]
|
||||
bytes = client.handlers['export'](**kwargs)
|
||||
return flask.send_file(
|
||||
bytes,
|
||||
as_attachment=True,
|
||||
@ -158,8 +244,8 @@ def reply():
|
||||
logging.info('GET %s %r', path, kwargs)
|
||||
try:
|
||||
id = kwargs.pop('id')
|
||||
client = instrument.clients[id]
|
||||
msg = getattr(client, "w_" + path[1:])(**kwargs)
|
||||
client = server.clients[id]
|
||||
msg = client.handlers[path[1:]](**kwargs)
|
||||
except Exception as e:
|
||||
logging.error('%s', traceback.format_exc())
|
||||
circularlog.log()
|
||||
@ -211,6 +297,9 @@ def replace_by_empty(file):
|
||||
|
||||
@app.route('/')
|
||||
def default():
|
||||
if not any(flask.request.values.get(k) for k in ('instrument', 'server', 'device')):
|
||||
if not server.single_instrument:
|
||||
return select_experiment()
|
||||
return general_file('SEAWebClient.html')
|
||||
|
||||
|
||||
@ -224,7 +313,7 @@ th {
|
||||
</style>
|
||||
<tr><th>instrument</th><th colspan=99>devices</th></tr>''']
|
||||
result = {}
|
||||
for stream, tags in instrument.get_streams().items():
|
||||
for stream, tags in server.db.get_streams().items():
|
||||
ins = tags.get('instrument', '0')
|
||||
result.setdefault(ins, []).append((stream, tags.get('device')))
|
||||
bare_streams = result.pop('0', [])
|
||||
@ -240,7 +329,9 @@ th {
|
||||
|
||||
@app.route('/select_experiment')
|
||||
def select_experiment():
|
||||
out = ['''<html><body>
|
||||
out = ['''<html><head>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<style>
|
||||
th {
|
||||
text-align: left;
|
||||
@ -249,7 +340,8 @@ th {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style><table>
|
||||
</style></head>
|
||||
<body><table>
|
||||
''']
|
||||
showtitle = 0
|
||||
ONEMONTH = 30 * 24 * 3600
|
||||
@ -271,7 +363,7 @@ a {
|
||||
starttime, endtime = now - ONEMONTH, now
|
||||
|
||||
chunk_list = []
|
||||
for key, chunk_dict in instrument.get_experiments(starttime, endtime).items():
|
||||
for key, chunk_dict in server.db.get_experiments(starttime, endtime).items():
|
||||
for (streams, devices), chunks in chunk_dict.items():
|
||||
chunk_list.extend((r[1], r[0], key, devices) for r in chunks)
|
||||
chunk_list.sort(reverse=True)
|
||||
@ -322,37 +414,3 @@ def general_file(file):
|
||||
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()
|
||||
|
Reference in New Issue
Block a user