Last active
August 30, 2023 09:51
-
-
Save YoRyan/6b4d629cca7422d15f93754938168fb7 to your computer and use it in GitHub Desktop.
Serve up a text stream with dynamic updates using Python and EventSource. This took me more time to create than I'm willing to admit and I'm proud.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import threading | |
from copy import copy | |
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
from sys import stdin | |
class ProducerConsumer(): | |
def __init__(self): | |
self.lock = threading.Condition() | |
self.consumers = set() | |
self.waiting = set() | |
self.item = None | |
def consume(self): | |
with self.lock: | |
ident = threading.get_ident() | |
self.consumers.add(ident) | |
while True: | |
self.lock.wait_for(lambda: ident in self.waiting) | |
self.waiting.remove(ident) | |
self.lock.notify() | |
try: | |
yield copy(self.item) | |
except GeneratorExit: | |
break | |
self.consumers.remove(ident) | |
def produce(self, item): | |
with self.lock: | |
self.lock.wait_for(lambda: len(self.waiting) == 0) | |
self.item = item | |
self.waiting = self.consumers.copy() | |
self.lock.notify() | |
Q = ProducerConsumer() | |
def main(): | |
ReadStdinThread().start() | |
server = ThreadingHTTPServer(('', 8080), RequestHandler) | |
server.serve_forever() | |
class ReadStdinThread(threading.Thread): | |
def run(self): | |
for line in stdin: | |
Q.produce(line) | |
class RequestHandler(BaseHTTPRequestHandler): | |
def do_GET(self): | |
self.send_response(200) | |
if self.path == '/stream': | |
self.send_header('Content-type', 'text/event-stream') | |
self.end_headers() | |
for line in Q.consume(): | |
try: | |
self.wfile.write(f'data: {line.rstrip()}\n\n'.encode()) | |
self.wfile.flush() | |
except BrokenPipeError: | |
break | |
else: | |
self.send_header('Content-type', 'text/html') | |
self.end_headers() | |
html = """ | |
<!doctype html> | |
<html> | |
<head> | |
<title>pipe</title> | |
</head> | |
<body> | |
<pre id="output"></pre> | |
<script type="text/javascript"> | |
const Output = document.getElementById("output"); | |
var EvtSource = new EventSource('/stream'); | |
EvtSource.onmessage = function (event) { | |
Output.appendChild(document.createTextNode(event.data)); | |
Output.appendChild(document.createElement("br")); | |
}; | |
</script> | |
</body> | |
</html> | |
""" | |
self.wfile.write(html.encode()) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment