Removed flask-classful from webchat

This patch removed the dependency on flask-classful.  This required
making all of the flask web routing non class based.

This patch also changes the aprsis class to allow retries for failed
connections when the aprsis servers are full and not responding to
login requests.
This commit is contained in:
Hemna 2023-07-19 11:27:34 -04:00
parent e1183a7e30
commit 6a6e854caf
5 changed files with 127 additions and 120 deletions

View File

@ -152,9 +152,10 @@ class APRSISClient(Client):
except LoginError as e: except LoginError as e:
LOG.error(f"Failed to login to APRS-IS Server '{e}'") LOG.error(f"Failed to login to APRS-IS Server '{e}'")
connected = False connected = False
raise e time.sleep(backoff)
except Exception as e: except Exception as e:
LOG.error(f"Unable to connect to APRS-IS server. '{e}' ") LOG.error(f"Unable to connect to APRS-IS server. '{e}' ")
connected = False
time.sleep(backoff) time.sleep(backoff)
backoff = backoff * 2 backoff = backoff * 2
continue continue

View File

@ -112,23 +112,23 @@ class Aprsdis(aprslib.IS):
self._sendall(login_str) self._sendall(login_str)
self.sock.settimeout(5) self.sock.settimeout(5)
test = self.sock.recv(len(login_str) + 100) test = self.sock.recv(len(login_str) + 100)
self.logger.debug("Server: '%s'", test)
if is_py3: if is_py3:
test = test.decode("latin-1") test = test.decode("latin-1")
test = test.rstrip() test = test.rstrip()
self.logger.debug("Server: %s", test) self.logger.debug("Server: '%s'", test)
a, b, callsign, status, e = test.split(" ", 4) if not test:
raise LoginError(f"Server Response Empty: '{test}'")
_, _, callsign, status, e = test.split(" ", 4)
s = e.split(",") s = e.split(",")
if len(s): if len(s):
server_string = s[0].replace("server ", "") server_string = s[0].replace("server ", "")
else: else:
server_string = e.replace("server ", "") server_string = e.replace("server ", "")
self.logger.info(f"Connected to {server_string}")
self.server_string = server_string
stats.APRSDStats().set_aprsis_server(server_string)
if callsign == "": if callsign == "":
raise LoginError("Server responded with empty callsign???") raise LoginError("Server responded with empty callsign???")
if callsign != self.callsign: if callsign != self.callsign:
@ -141,6 +141,10 @@ class Aprsdis(aprslib.IS):
else: else:
self.logger.info("Login successful") self.logger.info("Login successful")
self.logger.info(f"Connected to {server_string}")
self.server_string = server_string
stats.APRSDStats().set_aprsis_server(server_string)
except LoginError as e: except LoginError as e:
self.logger.error(str(e)) self.logger.error(str(e))
self.close() self.close()
@ -148,6 +152,7 @@ class Aprsdis(aprslib.IS):
except Exception as e: except Exception as e:
self.close() self.close()
self.logger.error(f"Failed to login '{e}'") self.logger.error(f"Failed to login '{e}'")
self.logger.exception(e)
raise LoginError("Failed to login") raise LoginError("Failed to login")
def consumer(self, callback, blocking=True, immortal=False, raw=False): def consumer(self, callback, blocking=True, immortal=False, raw=False):

View File

@ -12,7 +12,6 @@ import click
import flask import flask
from flask import request from flask import request
from flask.logging import default_handler from flask.logging import default_handler
import flask_classful
from flask_httpauth import HTTPBasicAuth from flask_httpauth import HTTPBasicAuth
from flask_socketio import Namespace, SocketIO from flask_socketio import Namespace, SocketIO
from oslo_config import cfg from oslo_config import cfg
@ -31,9 +30,16 @@ from aprsd.utils import objectstore, trace
CONF = cfg.CONF CONF = cfg.CONF
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
auth = HTTPBasicAuth() auth = HTTPBasicAuth()
users = None users = {}
socketio = None socketio = None
flask_app = flask.Flask(
"aprsd",
static_url_path="/static",
static_folder="web/chat/static",
template_folder="web/chat/templates",
)
def signal_handler(sig, frame): def signal_handler(sig, frame):
@ -174,106 +180,108 @@ class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
) )
class WebChatFlask(flask_classful.FlaskView): def set_config():
global users
def set_config(self):
global users
self.users = {}
user = CONF.admin.user
self.users[user] = generate_password_hash(CONF.admin.password)
users = self.users
def _get_transport(self, stats): def _get_transport(stats):
if CONF.aprs_network.enabled: if CONF.aprs_network.enabled:
transport = "aprs-is" transport = "aprs-is"
aprs_connection = ( aprs_connection = (
"APRS-IS Server: <a href='http://status.aprs2.net' >" "APRS-IS Server: <a href='http://status.aprs2.net' >"
"{}</a>".format(stats["stats"]["aprs-is"]["server"]) "{}</a>".format(stats["stats"]["aprs-is"]["server"])
)
else:
# We might be connected to a KISS socket?
if client.KISSClient.is_enabled():
transport = client.KISSClient.transport()
if transport == client.TRANSPORT_TCPKISS:
aprs_connection = (
"TCPKISS://{}:{}".format(
CONF.kiss_tcp.host,
CONF.kiss_tcp.port,
)
)
elif transport == client.TRANSPORT_SERIALKISS:
# for pep8 violation
aprs_connection = (
"SerialKISS://{}@{} baud".format(
CONF.kiss_serial.device,
CONF.kiss_serial.baudrate,
),
)
return transport, aprs_connection
@auth.login_required
def index(self):
ua_str = request.headers.get("User-Agent")
# this takes about 2 seconds :(
user_agent = ua_parse(ua_str)
LOG.debug(f"Is mobile? {user_agent.is_mobile}")
stats = self._stats()
if user_agent.is_mobile:
html_template = "mobile.html"
else:
html_template = "index.html"
# For development
# html_template = "mobile.html"
LOG.debug(f"Template {html_template}")
transport, aprs_connection = self._get_transport(stats)
LOG.debug(f"transport {transport} aprs_connection {aprs_connection}")
stats["transport"] = transport
stats["aprs_connection"] = aprs_connection
LOG.debug(f"initial stats = {stats}")
return flask.render_template(
html_template,
initial_stats=stats,
aprs_connection=aprs_connection,
callsign=CONF.callsign,
version=aprsd.__version__,
) )
else:
# We might be connected to a KISS socket?
if client.KISSClient.is_enabled():
transport = client.KISSClient.transport()
if transport == client.TRANSPORT_TCPKISS:
aprs_connection = (
"TCPKISS://{}:{}".format(
CONF.kiss_tcp.host,
CONF.kiss_tcp.port,
)
)
elif transport == client.TRANSPORT_SERIALKISS:
# for pep8 violation
aprs_connection = (
"SerialKISS://{}@{} baud".format(
CONF.kiss_serial.device,
CONF.kiss_serial.baudrate,
),
)
@auth.login_required return transport, aprs_connection
def send_message_status(self):
LOG.debug(request)
msgs = SentMessages()
info = msgs.get_all()
return json.dumps(info)
def _stats(self):
stats_obj = stats.APRSDStats()
now = datetime.datetime.now()
time_format = "%m-%d-%Y %H:%M:%S" @auth.login_required
stats_dict = stats_obj.stats() @flask_app.route("/")
# Webchat doesnt need these def index():
del stats_dict["aprsd"]["watch_list"] ua_str = request.headers.get("User-Agent")
del stats_dict["aprsd"]["seen_list"] # this takes about 2 seconds :(
# del stats_dict["email"] user_agent = ua_parse(ua_str)
# del stats_dict["plugins"] LOG.debug(f"Is mobile? {user_agent.is_mobile}")
# del stats_dict["messages"] stats = _stats()
result = { if user_agent.is_mobile:
"time": now.strftime(time_format), html_template = "mobile.html"
"stats": stats_dict, else:
} html_template = "index.html"
return result # For development
# html_template = "mobile.html"
def stats(self): LOG.debug(f"Template {html_template}")
return json.dumps(self._stats())
transport, aprs_connection = _get_transport(stats)
LOG.debug(f"transport {transport} aprs_connection {aprs_connection}")
stats["transport"] = transport
stats["aprs_connection"] = aprs_connection
LOG.debug(f"initial stats = {stats}")
return flask.render_template(
html_template,
initial_stats=stats,
aprs_connection=aprs_connection,
callsign=CONF.callsign,
version=aprsd.__version__,
)
@auth.login_required
@flask_app.route("//send-message-status")
def send_message_status():
LOG.debug(request)
msgs = SentMessages()
info = msgs.get_all()
return json.dumps(info)
def _stats():
stats_obj = stats.APRSDStats()
now = datetime.datetime.now()
time_format = "%m-%d-%Y %H:%M:%S"
stats_dict = stats_obj.stats()
# Webchat doesnt need these
del stats_dict["aprsd"]["watch_list"]
del stats_dict["aprsd"]["seen_list"]
# del stats_dict["email"]
# del stats_dict["plugins"]
# del stats_dict["messages"]
result = {
"time": now.strftime(time_format),
"stats": stats_dict,
}
return result
@flask_app.route("/stats")
def get_stats():
return json.dumps(_stats())
class SendMessageNamespace(Namespace): class SendMessageNamespace(Namespace):
@ -377,21 +385,9 @@ def setup_logging(flask_app, loglevel, quiet):
@trace.trace @trace.trace
def init_flask(loglevel, quiet): def init_flask(loglevel, quiet):
global socketio global socketio, flask_app
flask_app = flask.Flask(
"aprsd",
static_url_path="/static",
static_folder="web/chat/static",
template_folder="web/chat/templates",
)
setup_logging(flask_app, loglevel, quiet) setup_logging(flask_app, loglevel, quiet)
server = WebChatFlask()
server.set_config()
flask_app.route("/", methods=["GET"])(server.index)
flask_app.route("/stats", methods=["GET"])(server.stats)
# flask_app.route("/send-message", methods=["GET"])(server.send_message)
flask_app.route("/send-message-status", methods=["GET"])(server.send_message_status)
socketio = SocketIO( socketio = SocketIO(
flask_app, logger=False, engineio_logger=False, flask_app, logger=False, engineio_logger=False,
@ -407,7 +403,7 @@ def init_flask(loglevel, quiet):
"/sendmsg", "/sendmsg",
), ),
) )
return socketio, flask_app return socketio
# main() ### # main() ###
@ -448,6 +444,8 @@ def webchat(ctx, flush, port):
LOG.info(f"APRSD Started version: {aprsd.__version__}") LOG.info(f"APRSD Started version: {aprsd.__version__}")
CONF.log_opt_values(LOG, logging.DEBUG) CONF.log_opt_values(LOG, logging.DEBUG)
user = CONF.admin.user
users[user] = generate_password_hash(CONF.admin.password)
# Initialize the client factory and create # Initialize the client factory and create
# The correct client object ready for use # The correct client object ready for use
@ -466,7 +464,7 @@ def webchat(ctx, flush, port):
packets.WatchList() packets.WatchList()
packets.SeenList() packets.SeenList()
(socketio, app) = init_flask(loglevel, quiet) socketio = init_flask(loglevel, quiet)
rx_thread = rx.APRSDPluginRXThread( rx_thread = rx.APRSDPluginRXThread(
packet_queue=threads.packet_queue, packet_queue=threads.packet_queue,
) )
@ -482,7 +480,7 @@ def webchat(ctx, flush, port):
keepalive.start() keepalive.start()
LOG.info("Start socketio.run()") LOG.info("Start socketio.run()")
socketio.run( socketio.run(
app, flask_app,
ssl_context="adhoc", ssl_context="adhoc",
host=CONF.admin.web_ip, host=CONF.admin.web_ip,
port=port, port=port,

View File

@ -187,6 +187,7 @@ def index():
plugin_count=plugin_count, plugin_count=plugin_count,
) )
@auth.login_required @auth.login_required
def messages(): def messages():
track = packets.PacketTrack() track = packets.PacketTrack()
@ -197,9 +198,10 @@ def messages():
return flask.render_template("messages.html", messages=json.dumps(msgs)) return flask.render_template("messages.html", messages=json.dumps(msgs))
@auth.login_required @auth.login_required
@app.route("/packets") @app.route("/packets")
def packets(): def get_packets():
LOG.debug("/packets called") LOG.debug("/packets called")
packet_list = aprsd_rpc_client.RPCClient().get_packet_list() packet_list = aprsd_rpc_client.RPCClient().get_packet_list()
if packet_list: if packet_list:
@ -212,6 +214,7 @@ def packets():
else: else:
return json.dumps([]) return json.dumps([])
@auth.login_required @auth.login_required
@app.route("/plugins") @app.route("/plugins")
def plugins(): def plugins():
@ -221,6 +224,7 @@ def plugins():
return "reloaded" return "reloaded"
@auth.login_required @auth.login_required
@app.route("/save") @app.route("/save")
def save(): def save():
@ -230,7 +234,6 @@ def save():
return json.dumps({"messages": "saved"}) return json.dumps({"messages": "saved"})
class LogUpdateThread(threads.APRSDThread): class LogUpdateThread(threads.APRSDThread):
def __init__(self): def __init__(self):

View File

@ -39,9 +39,9 @@ class TestSendMessageCommand(unittest.TestCase):
CliRunner() CliRunner()
self.config_and_init() self.config_and_init()
socketio, flask_app = webchat.init_flask("DEBUG", False) socketio = webchat.init_flask("DEBUG", False)
self.assertIsInstance(socketio, flask_socketio.SocketIO) self.assertIsInstance(socketio, flask_socketio.SocketIO)
self.assertIsInstance(flask_app, flask.Flask) self.assertIsInstance(webchat.flask_app, flask.Flask)
@mock.patch("aprsd.packets.tracker.PacketTrack.remove") @mock.patch("aprsd.packets.tracker.PacketTrack.remove")
@mock.patch("aprsd.cmds.webchat.socketio") @mock.patch("aprsd.cmds.webchat.socketio")