Skip to content

Instantly share code, notes, and snippets.

@YoRyan
Last active August 30, 2023 09:51
Show Gist options
  • Save YoRyan/6b4d629cca7422d15f93754938168fb7 to your computer and use it in GitHub Desktop.
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.
#!/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