1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
# Copyright (C) 2013-2014 Aleksey Lim
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from sugar_network.db import files
from sugar_network.toolkit.router import route
from sugar_network.toolkit.coroutine import this
from sugar_network.toolkit import coroutine
_logger = logging.getLogger('model.routes')
class FrontRoutes(object):
def __init__(self):
self._spooler = coroutine.Spooler()
this.broadcast = self._broadcast
@route('GET', mime_type='text/html')
def hello(self):
return _HELLO_HTML
@route('OPTIONS')
def options(self, request, response):
if request.environ['HTTP_ORIGIN']:
response['Access-Control-Allow-Methods'] = \
request.environ['HTTP_ACCESS_CONTROL_REQUEST_METHOD']
response['Access-Control-Allow-Headers'] = \
request.environ['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
else:
response['Allow'] = 'GET, HEAD, POST, PUT, DELETE'
response.content_length = 0
@route('GET', cmd='subscribe', mime_type='text/event-stream')
def subscribe(self, request=None, response=None, **condition):
"""Subscribe to Server-Sent Events."""
if request is not None and not condition:
condition = request
if response is not None:
response.content_type = 'text/event-stream'
response['Cache-Control'] = 'no-cache'
return self._pull_events(request, condition)
@route('GET', ['robots.txt'], mime_type='text/plain')
def robots(self, request, response):
return 'User-agent: *\nDisallow: /\n'
@route('GET', ['favicon.ico'])
def favicon(self, request, response):
return files.get('favicon.ico')
def _broadcast(self, event):
_logger.debug('Broadcast event: %r', event)
self._spooler.notify_all(event)
def _pull_events(self, request, condition):
_logger.debug('Start %s-nth subscription', self._spooler.waiters + 1)
# Unblock `GET /?cmd=subscribe` call to let non-greenlet application
# initiate a subscription and do not stuck in waiting for the 1st event
yield {'event': 'pong'}
subscription = None
if request is not None:
subscription = request.content_stream
if subscription is not None:
coroutine.spawn(self._wait_for_closing, subscription)
while True:
event = self._spooler.wait()
if not isinstance(event, dict):
if event is subscription:
break
else:
continue
for key, value in condition.items():
if value.startswith('!'):
if event.get(key) == value[1:]:
break
elif event.get(key) != value:
break
else:
yield event
_logger.debug('Stop %s-nth subscription', self._spooler.waiters)
def _wait_for_closing(self, rfile):
try:
coroutine.select([rfile.fileno()], [], [])
finally:
self._spooler.notify_all(rfile)
_HELLO_HTML = """\
<h2>Welcome to Sugar Network API!</h2>
Visit the <a href="http://wiki.sugarlabs.org/go/Sugar_Network/API">
Sugar Labs Wiki</a> to learn how it can be used.
"""
|