"""Simple HTTP server classes with GET path rewriting and request/header logging.Now includes a reference WSGI server and tftpdaemon script."""importloggingimportthreadingfromfunctoolsimportpartialfromhttp.serverimportHTTPServer,SimpleHTTPRequestHandlerfromimportlib.metadataimportversionfromsocketserverimportThreadingMixInfromthreadingimportTimerfromtypingimportCallable,Unionfromurllib.parseimporturlparsefromwsgiref.simple_serverimportmake_serverfromwsgiref.validateimportvalidator__version__=version('pyserv')__all__=["__description__","__version__","GetHandler","GetServer","GetServerWSGI","RepeatTimer","munge_url",]__description__="A collection of simple servers for HTTP, WSGI, and TFTP"
[docs]defmunge_url(ota_url):""" Parse the url sent by OTA command for file path and host string. :param ota_url: (possibly) broken GET path :return tuple: netloc and path from ``urlparse`` """url_data=urlparse(str(ota_url))file_path=url_data.pathhost_str=url_data.netloclogging.debug('request file: %s',file_path.lstrip("/"))logging.debug('request host: %s',host_strifhost_strelse'None')returnhost_str,file_path
classThreadingHTTPServer(ThreadingMixIn,HTTPServer):""" Backwards-compatible server class for Python <3.7 on older distros, eg, Ubuntu bionic LTS. """
[docs]classGetHandler(SimpleHTTPRequestHandler):""" Munge the incoming request path from Dialog OTA. Runs `urlparse` on the url and updates the GET handler path. We also log the result. """
[docs]deflog_message(self,format,*args):# pylint: disable=W0622""" We need a custom log handler, otherwise Get message goes to `sys.stdout` only. """logging.info("%s - - [%s] %s",self.address_string(),self.log_date_time_string(),format%args,)
[docs]classGetServer(threading.Thread):""" Threaded wrapper class for custom ThreadingHTTPServer instance. Usage:: s = GetServer('', 8080) s.start() s.stop() """def__init__(self,iface,port,directory="."):"""Setup server, iface, and port"""super().__init__()self.iface=ifaceself.port=int(port)self.directory=directoryself.handler=partial(GetHandler,directory=self.directory)self.server=ThreadingHTTPServer((self.iface,self.port),self.handler)
[docs]defrun(self):"""Start main server thread"""self.server.serve_forever()
[docs]defstop(self):"""Stop main server thread"""self.server.shutdown()self.server.socket.close()
[docs]classGetServerWSGI(threading.Thread):""" Threaded wrapper class for custom flask WSGIServer instance. Usage:: s = GetServerWSGI(my_app, 8080, is_flask=True, validate=False) s.start() s.stop() """def__init__(self,flask_app,port,is_flask=False,validate=False):super().__init__()self.port=int(port)self.app=flask_appifvalidate:self.app=validator(flask_app)self.server=make_server('',self.port,self.app)ifis_flask:self.context=flask_app.app_context()self.context.push()
[docs]defrun(self):"""Start main server thread"""self.server.serve_forever()
[docs]defstop(self):"""Stop main server thread"""self.server.shutdown()
[docs]classRepeatTimer:""" A non-blocking timer thread to execute a user func repeatedly. Usage:: def hello(name): print(f"Hello {name}") rt = RepeatTimer(1, hello, "World") # it auto-starts try: sleep(5) # run other stuff finally: rt.stop() # best in a try/finally block Author: https://stackoverflow.com/a/38317060/14874218 """def__init__(self,interval:Union[int,float],function:Callable,*args,**kwargs):self._timer:Timerself.interval=intervalself.function=functionself.args=argsself.kwargs=kwargsself.is_running:bool=Falseself.start()def_run(self):self.is_running=Falseself.start()self.function(*self.args,**self.kwargs)
[docs]defstart(self):""" Safely (re)start thread timer. """ifnotself.is_running:# pragma: no coverself._timer=Timer(self.interval,self._run)self._timer.start()self.is_running=True