Last active
August 29, 2015 14:09
-
-
Save k4ml/43b2fb8cfdb9d3b6d58d to your computer and use it in GitHub Desktop.
Tailf to browser through Server Sent Events (SSE)
This file contains hidden or 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
""" | |
Tail file to browser using Server Sent Events (SSE) + gevent. | |
Based on http://the.taoofmac.com/space/blog/2014/11/16/1940#python-patterns--take-two | |
Twisted+websocket implementation - https://github.com/k4ml/websocket_stdout_example | |
Requirements:- | |
sudo pip install bottle | |
sudo apt-get install python-gevent | |
References | |
========== | |
http://www.html5rocks.com/en/tutorials/eventsource/basics/ | |
http://stackoverflow.com/questions/260273/most-efficient-way-to-search-the-last-x-lines-of-a-file-in-python | |
https://www.reddit.com/r/learnpython/comments/1uufki/tail_f_equivalent_in_python/ | |
""" | |
import time | |
import json | |
from bottle import run | |
from gevent import monkey; monkey.patch_all() | |
from bottle import request, response, get, route | |
def sse_pack(d): | |
"""Pack data in SSE format""" | |
buffer = '' | |
for k in ['retry','id','event','data']: | |
if k in d.keys(): | |
buffer += '%s: %s\n' % (k, d[k]) | |
return buffer + '\n' | |
def tail(fn, sleep=0.1): | |
f = open(fn) | |
f.seek (0, 2) # Seek @ EOF | |
fsize = f.tell() # Get Size | |
f.seek (max (fsize-1024, 0), 0) # Set pos @ last n chars | |
while True: | |
l = f.readline() | |
if l: | |
yield l | |
else: | |
time.sleep(sleep) | |
@get("/") | |
def index(): | |
return """<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
</head> | |
<body> | |
<script> | |
var source = new EventSource('/stream'); | |
source.addEventListener('delta', function(e) { | |
var data = JSON.parse(e.data); | |
document.body.innerHTML += data.key + '<br>'; | |
}, false); | |
</script> | |
</body> | |
</html> | |
""" | |
@get("/stream") | |
def stream_generator(): | |
# Keep event IDs consistent | |
event_id = 0 | |
if 'Last-Event-Id' in request.headers: | |
event_id = int(request.headers['Last-Event-Id']) + 1 | |
# Set up our message payload with a retry value in case of connection failure | |
# (that's also the polling interval to be used as fallback by our polyfill) | |
msg = { | |
'retry': '2000' | |
} | |
# Provide an initial data dump to each new client | |
response.headers['content-type'] = 'text/event-stream' | |
response.headers['Access-Control-Allow-Origin'] = '*' | |
msg.update({ | |
'event': 'init', | |
'data' : json.dumps({'key': 'hello'}), | |
'id' : event_id | |
}) | |
yield sse_pack(msg) | |
# Now give them deltas as they arrive (say, from a message broker) | |
event_id += 1 | |
msg['event'] = 'delta' | |
for data in tail('/var/log/syslog'): | |
# block until you get new data (from a queue, pub/sub, zmq, etc.) | |
msg.update({ | |
'event': 'delta', | |
'data' : json.dumps({'key': data}), | |
'id' : event_id | |
}) | |
print sse_pack(msg) | |
yield sse_pack(msg) | |
event_id += 1 | |
run(server="gevent") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment