Skip to content
This repository was archived by the owner on Oct 24, 2023. It is now read-only.

Commit 0f065cb

Browse files
committed
init. commit
0 parents  commit 0f065cb

File tree

4 files changed

+196
-0
lines changed

4 files changed

+196
-0
lines changed

Dockerfile

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM python:3.6
2+
MAINTAINER Xi Shen <[email protected]>
3+
4+
COPY requirements.txt /app/
5+
RUN pip install --requirement /app/requirements.txt
6+
7+
COPY main.py /app/
8+
9+
EXPOSE 80
10+
WORKDIR /app
11+
ENTRYPOINT ["python", "main.py"]

__init__.py

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""
2+
Weibo Streaming
3+
===============
4+
5+
Turn `weibo`_ statues into a HTTP Stream.
6+
7+
- Please refer to *weibo* `API documents`_ for detail weibo statuses API
8+
- Please refer to `chunked`_ encoding for how HTTP streaming implementation specification
9+
10+
.. _weibo: http://www.weibo.com
11+
.. _API documents: http://open.weibo.com/wiki/%E5%BE%AE%E5%8D%9AAPI
12+
.. _chunked: https://en.wikipedia.org/wiki/Chunked_transfer_encoding
13+
"""
14+
import logging
15+
16+
from tornado.escape import json_decode
17+
from tornado.httpclient import AsyncHTTPClient
18+
19+
20+
class Fib(object):
21+
"""
22+
Generate Fibonacci sequence
23+
24+
>>> fib = Fib()
25+
>>> fib.next()
26+
1
27+
28+
>>> fib.next()
29+
1
30+
31+
>>> fib.next()
32+
2
33+
>>> fib.next()
34+
3
35+
>>> fib.next()
36+
5
37+
>>> fib.reset()
38+
>>> fib.next()
39+
1
40+
"""
41+
42+
def __init__(self):
43+
self._counter = 0
44+
self._a = 1
45+
self._b = 1
46+
self._c = -1
47+
48+
def __iter__(self):
49+
return self
50+
51+
def __next__(self):
52+
self._counter += 1
53+
if self._counter <= 2:
54+
return 1
55+
else:
56+
self._c = self._a + self._b
57+
self._a = self._b
58+
self._b = self._c
59+
60+
return self._c
61+
62+
def next(self):
63+
return self.__next__()
64+
65+
def reset(self):
66+
self._counter = 0
67+
self._a = 1
68+
self._b = 1
69+
self._c = -1
70+
71+
72+
app_logger = logging.getLogger('tornado.application')
73+
74+
75+
class WeiboClient(object):
76+
_weibo_public_timeline_url = 'https://api.weibo.com/2/statuses/public_timeline.json?access_token={}'
77+
78+
def __init__(self, access_token):
79+
self._http_client = AsyncHTTPClient()
80+
self._access_token = access_token
81+
self.last_id = 0
82+
83+
def public_timeline(self):
84+
response = yield self._http_client.fetch(WeiboClient._weibo_public_timeline_url.format(self._access_token))
85+
86+
if response.code == 200:
87+
json_body = json_decode(response.body)
88+
statuses = [s for s in json_body['statuses'] if s['id'] > self.last_id]
89+
90+
if len(statuses) > 0:
91+
self.last_id = statuses[0]['id']
92+
93+
return statuses
94+
else:
95+
app_logger.error('weibo api respond {}'.format(response.code))
96+
return []

main.py

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import logging
2+
3+
from tornado import gen
4+
from tornado import ioloop
5+
from tornado import web
6+
from tornado.escape import json_encode
7+
from tornado.options import define, options
8+
9+
from . import Fib, WeiboClient
10+
11+
weibo_public_timeline_url = 'https://api.weibo.com/2/statuses/public_timeline.json?access_token={}'
12+
access_logger = logging.getLogger('tornado.access')
13+
app_logger = logging.getLogger('tornado.application')
14+
sleep_duration_origin = 3
15+
sleep_duration_increase_factor = 2
16+
17+
# chunked transfer encoding delimiter
18+
CRLF = '\r\n'
19+
20+
# define tornado command line arguments
21+
define('weibo_access_token')
22+
define('debug', default=False)
23+
24+
25+
def remote_ip(request):
26+
return request.headers.get('X-Real-IP') or request.remote_ip
27+
28+
29+
# noinspection PyAbstractClass
30+
class MainHandler(web.RequestHandler):
31+
def get(self):
32+
self.write('''
33+
access /public_timeline to get a weibo public status stream
34+
''')
35+
36+
37+
# noinspection PyAbstractClass
38+
class PublicTimelineHandler(web.RequestHandler):
39+
@gen.coroutine
40+
def get(self):
41+
client = WeiboClient(options.weibo_access_token)
42+
access_logger.info('start streaming to {}'.format(remote_ip(self.request)))
43+
self.set_header('transfer-encoding', 'chunked')
44+
self.set_header('content-type', 'application/json; charset=utf-8')
45+
fib = Fib()
46+
47+
while True:
48+
statuses = yield client.public_timeline()
49+
if not self.request.connection.stream.closed():
50+
statuses_count = len(statuses)
51+
if statuses_count > 0:
52+
app_logger.info('received {} new statuses'.format(statuses_count))
53+
for s in statuses:
54+
chunked = json_encode(s)
55+
chunked_size = len(chunked)
56+
self.write('{:x}{}'.format(chunked_size + 1, CRLF))
57+
self.write('{}\n{}'.format(chunked, CRLF))
58+
self.flush()
59+
app_logger.info('last id updated to {}'.format(client.last_id))
60+
fib.reset()
61+
sleep_duration = fib.next()
62+
else:
63+
sleep_duration = fib.next()
64+
app_logger.warn('no new statuses (sleeping {} seconds)'.format(sleep_duration))
65+
66+
yield gen.sleep(sleep_duration)
67+
else:
68+
# access_logger.info('stop streaming to {}'.format(remote_ip(self.request))
69+
return
70+
# self.write('0' + CRLF)
71+
# self.write(CRLF)
72+
73+
def on_connection_close(self):
74+
access_logger.info('close connection to {}'.format(remote_ip(self.request)))
75+
self.finish()
76+
77+
78+
if __name__ == '__main__':
79+
options.parse_command_line()
80+
81+
app = web.Application([
82+
(r'/', MainHandler),
83+
(r'/public_timeline', PublicTimelineHandler)
84+
],
85+
debug=options.debug)
86+
# listen to default HTTP port
87+
app.listen(80)
88+
ioloop.IOLoop.current().start()

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tornado==4.4.1

0 commit comments

Comments
 (0)