"""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('')
res.append("")
res.append("
")
res.append("%s" % (title))
if refresh:
res.append("\n" % refresh)
if extras:
res.append(extras)
res.append("")
res.append('')
return res
def buildpage(self):
res = self.buildhead(refresh=60, extras=tcss)
res.append("Heartbeat status %s
" % VER)
res += hbdclass.ubHost.buildhosttable()
res += hbdclass.ubHost.buildmsgtable(msgs_getter())
res.append(
" %s (%s)
" % (time.strftime("%H:%M:%S", time.localtime(get_now())), config.get("tz", "CET-1CDT"))
)
res.append("")
return res
def builderror(self, code, cause, lcause):
res = []
res.append('')
res.append("")
res.append("%s %s" % (code, cause))
res.append("")
res.append("%s
" % (cause))
res.append("%s
" % lcause)
res.append("
")
res.append(
"hbd (Unix) Server at %s:%s" % (config.get("hbd_host"), config.get("hbd_port"))
)
res.append("")
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
" % (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