Skip to content

Instantly share code, notes, and snippets.

@k4ml
Last active August 29, 2015 14:09
Show Gist options
  • Save k4ml/43b2fb8cfdb9d3b6d58d to your computer and use it in GitHub Desktop.
Save k4ml/43b2fb8cfdb9d3b6d58d to your computer and use it in GitHub Desktop.
Tailf to browser through Server Sent Events (SSE)
"""
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