refactor
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
"""HTTP server and handler scaffolds (thin wrappers around http.server)."""
|
||||
from http import server
|
||||
import json
|
||||
import time
|
||||
import urllib.parse
|
||||
|
||||
from urllib3 import request
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
class HttpServer(server.ThreadingHTTPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
def threaded(self):
|
||||
pass
|
||||
|
||||
|
||||
def make_handler_class(
|
||||
config,
|
||||
hbdclass,
|
||||
msgs_getter,
|
||||
log=None,
|
||||
email=None,
|
||||
pushmsg=None,
|
||||
msg_to_websockets=None,
|
||||
tcss=None,
|
||||
DEBUG=0,
|
||||
verbose=False,
|
||||
get_now=None,
|
||||
VER="",
|
||||
):
|
||||
"""Return a BaseHTTPRequestHandler subclass bound to runtime objects.
|
||||
|
||||
`msgs_getter` should be a callable that returns a list-like of messages.
|
||||
"""
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
get_now = get_now or (lambda: time.time())
|
||||
|
||||
class CustomHandler(server.BaseHTTPRequestHandler):
|
||||
|
||||
server_version = f"HeartbeatHTTP/{VER}"
|
||||
|
||||
def version_string(self):
|
||||
return self.server_version
|
||||
|
||||
def handle(self):
|
||||
try:
|
||||
return server.BaseHTTPRequestHandler.handle(self)
|
||||
except Exception as e:
|
||||
self.log_error("Request went away: %r", e)
|
||||
self.close_connection = 1
|
||||
return
|
||||
|
||||
def do_HEAD(self):
|
||||
self.setheaders(200)
|
||||
|
||||
def setheaders(self, code, headerdict={}):
|
||||
self.send_response(code)
|
||||
self.send_header(
|
||||
"Last-Modified",
|
||||
time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(get_now())),
|
||||
)
|
||||
for h in headerdict:
|
||||
self.send_header(h, headerdict[h])
|
||||
self.end_headers()
|
||||
|
||||
def buildhead(self, title="Heartbeat", refresh=None, extras=None):
|
||||
res = []
|
||||
res.append('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">')
|
||||
res.append("<html>")
|
||||
res.append("<head>")
|
||||
res.append("<title>%s</title>" % (title))
|
||||
if refresh:
|
||||
res.append("<meta http-equiv = Refresh content = %d>\n" % refresh)
|
||||
if extras:
|
||||
res.append(extras)
|
||||
res.append("</head>")
|
||||
res.append('<body BGCOLOR = "#FFFFFF" LINK = "#008000" VLINK = "#008000">')
|
||||
return res
|
||||
|
||||
def buildpage(self):
|
||||
res = self.buildhead(refresh=60, extras=tcss)
|
||||
res.append("<H2>Heartbeat status %s</h2>" % VER)
|
||||
res += hbdclass.ubHost.buildhosttable()
|
||||
res += hbdclass.ubHost.buildmsgtable(msgs_getter())
|
||||
res.append(
|
||||
"<p> %s (%s)</p>" % (time.strftime("%H:%M:%S", time.localtime(get_now())), config.get("tz", "CET-1CDT"))
|
||||
)
|
||||
res.append("</body></html>")
|
||||
return res
|
||||
|
||||
def builderror(self, code, cause, lcause):
|
||||
res = []
|
||||
res.append('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">')
|
||||
res.append("<html><head>")
|
||||
res.append("<title>%s %s</title>" % (code, cause))
|
||||
res.append("</head><body>")
|
||||
res.append("<h1>%s</h1>" % (cause))
|
||||
res.append("<p>%s</p>" % lcause)
|
||||
res.append("<hr>")
|
||||
res.append(
|
||||
"<address>hbd (Unix) Server at %s:%s</address>" % (config.get("hbd_host"), config.get("hbd_port"))
|
||||
)
|
||||
res.append("</body></html>")
|
||||
return code, res
|
||||
|
||||
def do_GET(self):
|
||||
xsig = 0
|
||||
rqAcceptEncoding = self.headers.get("Accept-encoding", {})
|
||||
headerdict = {"Content-Type": "text/html; charset = ISO-8859-1"}
|
||||
qr = urllib.parse.urlparse(self.path)
|
||||
qa = urllib.parse.parse_qs(qr.query)
|
||||
|
||||
if qr.path == "/":
|
||||
res = self.buildpage()
|
||||
|
||||
elif qr.path == "/c": # command on host /c?h=melschserver&c=sudo%20ls
|
||||
uname = qa.get("h", [None])[0]
|
||||
ucmd = qa.get("c", [None])[0]
|
||||
if not ucmd or not uname:
|
||||
code, res = self.builderror(400, "Argument error", "need h= and c= arguments")
|
||||
elif uname not in hbdclass.Host.hosts:
|
||||
code, res = self.builderror(400, "Data error", "h=%s not found" % uname)
|
||||
else:
|
||||
hbdclass.Host.hosts[uname].cmds.append(("CMD", {"cmd": urllib.parse.unquote(ucmd)}))
|
||||
res = self.buildhead()
|
||||
res.append("cmd %s queued for host %s" % (uname, ucmd))
|
||||
|
||||
elif qr.path == "/d": # drop host /d?h=melschserver
|
||||
uname = qa.get("h", [None])[0]
|
||||
if not uname:
|
||||
code, res = self.builderror(400, "Argument error", "need h= argument")
|
||||
if uname not in hbdclass.Host.hosts:
|
||||
code, res = self.builderror(400, "Data error", "h=%s not found" % uname)
|
||||
else:
|
||||
if log:
|
||||
log(uname, "dropped")
|
||||
del hbdclass.Host.hosts[uname]
|
||||
res = self.buildhead()
|
||||
res.append("Done")
|
||||
|
||||
elif qr.path == "/n": # register name
|
||||
uname = qa.get("h", [None])[0]
|
||||
if not uname:
|
||||
code, res = self.builderror(400, "Argument error", "need h= argument")
|
||||
if uname not in hbdclass.Host.hosts:
|
||||
code, res = self.builderror(400, "Data error", "h=%s not found" % uname)
|
||||
else:
|
||||
ll = hbdclass.Host.hosts[uname].registerDns()
|
||||
res = self.buildhead()
|
||||
res.append(ll)
|
||||
if log:
|
||||
log(uname, ll)
|
||||
|
||||
elif qr.path == "/u": # update
|
||||
uname = urllib.parse.unquote(qa.get("h", [None])[0])
|
||||
ucode = qa.get("c", [None])[0]
|
||||
if not ucode or not uname:
|
||||
code, res = self.builderror(400, "Argument error", "need h= and c= arguments")
|
||||
elif uname != "All" and uname not in hbdclass.Host.hosts:
|
||||
code, res = self.builderror(400, "Data error", "h=%s not found" % uname)
|
||||
else:
|
||||
res = self.buildhead()
|
||||
if uname != "All":
|
||||
names = [uname]
|
||||
else:
|
||||
names = []
|
||||
for n in hbdclass.Host.hosts:
|
||||
if hbdclass.Host.hosts[n].cver >= 2: # earliest version that supports update
|
||||
names.append(n)
|
||||
for n in names:
|
||||
err = None
|
||||
try:
|
||||
from hbd import proto
|
||||
# read code from a file name, fallback to sending ucode as data
|
||||
err = None
|
||||
# attempt to send update command to host
|
||||
r = {"csum": None, "code": ucode}
|
||||
hbdclass.Host.hosts[n].cmds.append(("UPD", r))
|
||||
except Exception as e:
|
||||
err = str(e)
|
||||
res.append("update started for %s: %s<br>" % (n, err if err else "OK"))
|
||||
res.append("Done")
|
||||
|
||||
elif qr.path == "/api/0/hosts": # api access to host table
|
||||
headerdict = {"Content-Type": "application/json; charset=utf-8"}
|
||||
lst = []
|
||||
for h in hbdclass.Host.hosts:
|
||||
lst.append(hbdclass.Host.hosts[h].jsons())
|
||||
res = ["[" + ",".join(lst) + "]"]
|
||||
|
||||
elif qr.path == "/api/0/messages": # api access to host table
|
||||
headerdict = {"Content-Type": "application/json; charset=utf-8"}
|
||||
lst = msgs_getter()[-30:]
|
||||
res = [json.dumps(lst)]
|
||||
|
||||
elif qr.path == "/r": # restart
|
||||
res = self.buildhead()
|
||||
res.append("restart request")
|
||||
xsig = 1 # signal.SIGHUP will be handled by application
|
||||
if log:
|
||||
log(None, "restart request")
|
||||
elif qr.path == "/live": # show
|
||||
heartbeat_ws_url = f"wss://{host}:50006/hbd"
|
||||
res = templates.TemplateResponse(
|
||||
"heartbeat.html",
|
||||
{
|
||||
"title": "Heartbeat",
|
||||
"header": "Heartbeat",
|
||||
"request": request,
|
||||
"heartbeat_ws_url": heartbeat_ws_url,
|
||||
"extra_scripts": extra_scripts,
|
||||
},
|
||||
)
|
||||
else:
|
||||
code, res = self.builderror(404, "Not Found", "requested URL was not found on this server.")
|
||||
|
||||
if "deflate" in rqAcceptEncoding:
|
||||
headerdict["Content-Encoding"] = "deflate"
|
||||
towrite = __import__("zlib").compress("\n".join(res).encode(), 6)
|
||||
else:
|
||||
towrite = "\n".join(res)
|
||||
headerdict["Content-Length"] = len(towrite)
|
||||
headerdict["Cache-Control"] = "private, must-revalidate, max-age=0"
|
||||
headerdict["Expires"] = "Thu, 01 Jan 1970 00:00:00 GMT"
|
||||
self.setheaders(200 if 'res' in locals() else code, headerdict)
|
||||
self.wfile.write(towrite if isinstance(towrite, bytes) else towrite.encode())
|
||||
|
||||
if xsig:
|
||||
# inform application via setting a flag on the server instance
|
||||
try:
|
||||
self.server.xsig = xsig
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return CustomHandler
|
||||
Reference in New Issue
Block a user