Skip to content

Instantly share code, notes, and snippets.

@frennkie
Last active October 20, 2018 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save frennkie/836885af72be02e1ec05710f902ddbe5 to your computer and use it in GitHub Desktop.
Save frennkie/836885af72be02e1ec05710f902ddbe5 to your computer and use it in GitHub Desktop.
import base64
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler
import json
import logging
from random import randint
import sys
import socketserver
import threading
import time
import urllib.parse
from infoblitz import Dashboard, Metric
CTYPE_HTML = "text/html"
CTYPE_JSON = "application/json"
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s (%(threadName)-10s) [%(name)s] '
'%(levelname)s %(module)s:%(lineno)d - %(message)s', )
class QuietBaseHTTPRequestHandler(BaseHTTPRequestHandler):
"""Quiet http request handler
Subclasses SimpleHTTPRequestHandler in order to overwrite the log_message
method, letting us reduce output generated by the handler. Only standard
messages are overwritten, so errors will still be displayed.
"""
def __init__(self, request, client_address, server, board=None, board_lock=None):
super().__init__(request, client_address, server)
self.board = board
self.board_lock = board_lock
def do_GET(self):
parts = urllib.parse.urlsplit(self.path)
if parts.path.endswith('/favicon.ico'):
ctype = 'image/x-icon'
content = bytes(base64.b64decode(
"AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAA"
"AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoJiIKKCYiWgAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAoJiIgKCYiuygmIhgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAoJiJDKCYi7SgmIlIAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoJiJz"
"KCYi/SgmIqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAACgmIgooJiKmKCYi/ygmIuAoJiIOAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgmIh8oJiLPKCYi/ygm"
"Iv4oJiI/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAACgmIkEoJiLrKCYi/ygmIv8oJiKMAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAACgmInAoJiL8KCYi/ygmIv8oJiL/"
"KCYiySgmIpwoJiJzKCYiKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgm"
"IhYoJiJyKCYinCgmIsIoJiL8KCYi/ygmIv8oJiL/KCYinygmIgkAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoJiJTKCYi/ygm"
"Iv8oJiL5KCYiaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAoJiIeKCYi7ygmIv8oJiLjKCYiNwAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoJiIDKCYixCgmIv8oJiK+"
"KCYiFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAKCYigigmIv8oJiKJKCYiAwAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCYiPigmIvAoJiJSAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"KCYiEigmIrooJiInAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAACgmIlooJiIMAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP/3"
"AAD/7wAA/88AAP8fAAD+PwAA/D8AAPgfAAD4DwAA/j8AAPx/AAD4/wAA"
"8f8AAPf/AADv/wAA//8AAA=="
))
elif not parts.path.endswith('/'):
# redirect browser - doing basically what apache does
self.send_response(HTTPStatus.MOVED_PERMANENTLY)
new_parts = (parts[0], parts[1], parts[2] + '/',
parts[3], parts[4])
new_url = urllib.parse.urlunsplit(new_parts)
self.send_header("Location", new_url)
self.end_headers()
return None
elif parts.path.endswith('/json/'):
ctype = CTYPE_JSON
content = bytes(json.dumps(json.loads('{"hello": "world"}')), "UTF-8")
else:
ctype = CTYPE_HTML
content = bytes("<html><head><title>RaspiBlitz Info Dashboard</title></head>", "UTF-8")
content += bytes("<body><h1>RaspiBlitz Info Dashboard</h1>", "UTF-8")
content += bytes("<p>The Dashboard Version is: v{}</p>".format(self.board.version.val), "UTF-8")
content += bytes("<p>The API Endpoint (JSON) is located here: <a href=\"/json/\">/json/</a></p>", "UTF-8")
content += bytes("</body></html>", "UTF-8")
self.send_response(200)
self.send_header("Content-type", ctype)
self.send_header("Content-Length", len(content))
self.end_headers()
self.wfile.write(content)
def log_message(self, *args):
"""Overwrite so messages are logged to logging instead of STDOUT"""
pass
class ThreadedHTTPServer(object):
"""Runs BaseHTTPServer in a thread
Lets you start and stop an instance of SimpleHTTPServer.
"""
def __init__(self, host, port, board=None, board_lock=None, name=None, request_handler=QuietBaseHTTPRequestHandler):
"""Prepare thread and socket server
Creates the socket server that will use the HTTP request handler. Also
prepares the thread to run the serve_forever method of the socket
server as a daemon once it is started
"""
request_handler.board = board
request_handler.board_lock = board_lock
socketserver.TCPServer.allow_reuse_address = True
self.server = socketserver.TCPServer((host, port), request_handler)
self.server_thread = threading.Thread(name=name, target=self.server.serve_forever)
self.server_thread.daemon = True
def __enter__(self):
self.start()
return self
def __exit__(self, type, value, traceback):
self.stop()
def start(self):
"""Start the HTTP server
Starts the serve_forever method of Socket running the request handler
as a daemon thread
"""
self.server_thread.start()
def stop(self):
"""Stop the HTTP server
Stops the server and cleans up the port assigned to the socket
"""
self.server.shutdown()
self.server.server_close()
# Benefit of using class instead of function: Can use clean signature instead of kwargs..!
class DashboardPrinter(threading.Thread):
def __init__(self, group=None, target=None, name="DB_Printer",
board=None, board_lock=None, interval=None,
daemon=True, args=(), kwargs=None, ):
super().__init__(group, target, name, daemon=daemon, args=args, kwargs=kwargs)
self.board = board
self.board_lock = board_lock
self.interval = interval
def run(self):
while True:
# logging.debug("Dashboard: {} (v{})".format(board.name.val, board.version.val))
with self.board_lock:
print("Dashboard: {} (v{})".format(self.board.name.val, self.board.version.val))
time.sleep(self.interval)
class DashboardUpdater(threading.Thread):
def __init__(self, group=None, target=None, name="DB_Updater",
board=None, board_lock=None, interval=None,
daemon=True, args=(), kwargs=None, ):
super().__init__(group, target, name, daemon=daemon, args=args, kwargs=kwargs)
self.board = board
self.board_lock = board_lock
self.interval = interval
def run(self):
while True:
logging.debug("Updating Dashboard")
with self.board_lock:
self.board.version.val = "0.{:}".format(randint(0, 100))
time.sleep(self.interval)
def main():
host = "localhost"
port = 8000
print_interval = 5
update_interval = 10
board = Dashboard("bitcoin")
board.timeout = 10
board.interface = "eth0"
board.name = Metric("RaspiBlitz", style="yellow")
board.version = Metric("0.94", style="yellow")
main_thread = threading.current_thread()
board_lock = threading.Lock()
dashboard_updater_thread = DashboardUpdater(board=board, board_lock=board_lock, interval=update_interval)
dashboard_printer_thread = DashboardPrinter(board=board, board_lock=board_lock, interval=print_interval)
web_server_thread = ThreadedHTTPServer(host, port, board, board_lock, name="Web_Server")
logging.info("Starting Dashboard Updater")
dashboard_updater_thread.start()
logging.info("Starting Dashboard Printer")
dashboard_printer_thread.start()
logging.info("Starting Web Server: http://{}:{}".format(host, port))
web_server_thread.start()
# for info/debug only
logging.debug("Listing all Threads:")
for t in threading.enumerate():
if t is main_thread:
pass
logging.debug('Thread: %s', t.getName())
try:
while True: # run in loop that can be interrupted with CTRL+c
time.sleep(1) # ToDO check.. not quite sure..
except KeyboardInterrupt:
logging.debug("Stopping server loop")
web_server_thread.stop()
sys.exit(0)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment