"""The listening loop of the web service and of its bootstrap server. Only the Python standard library is used, so the bootstrap server, which must run whatever else is broken, can use it. """ import threading import time from http.server import ThreadingHTTPServer def serve(wanted, handler, log, interval): """Work out where to listen every interval seconds with wanted, and run an HTTP server of handler at each place. wanted answers ([(address, port), ...], '') or ([], the reason not to listen). A server whose place is no longer wanted is stopped, and a new one listens at each new place; a place that cannot be bound is tried again the next round. Every new message is logged with log. Does not return. """ servers, reported = {}, None while True: targets, reason = wanted() for target in [target for target in servers if target not in targets]: server, thread = servers.pop(target) server.shutdown() server.server_close() thread.join() messages = [reason] if reason else [] for target in targets: if target in servers: continue try: server = ThreadingHTTPServer(target, handler) except OSError as exc: messages.append(f'cannot listen on {target[0]}:{target[1]}: {exc}') continue thread = threading.Thread(target=server.serve_forever, name=f'server {target[0]}', daemon=True) thread.start() servers[target] = (server, thread) if servers: messages.append('serving on ' + ', '.join(f'http://{address}:{port}' for address, port in servers)) message = '; '.join(messages) if message and message != reported: log(message) reported = message time.sleep(interval)