Skip to content

Commit 9b69ea6

Browse files
author
daleeg
committed
[genesis] 初始化工程,完成基础的sanic sse逻辑
Signed-off-by: daleeg <[email protected]>
1 parent 323b59e commit 9b69ea6

20 files changed

+699
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,4 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
.idea/

CHANGELOG.md

Whitespace-only changes.

Makefile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
PYTHON ?= python3
2+
PYTEST ?= pytest
3+
MYPY ?= mypy
4+
5+
6+
dist: clean
7+
$(PYTHON) setup.py sdist bdist_wheel
8+
9+
clean:
10+
-rm -r build dist sanic_sse_py3.egg-info
11+
12+
pypi:
13+
twine upload dist/*

README.rst

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
sanic_sse
2+
########
3+
4+
5+
1. 安装
6+
==========
7+
8+
.. code-block:: shell
9+
10+
pip install sanic-sse-py3
11+
12+
2. 示例
13+
==========
14+
15+
- 2.1 代码
16+
17+
.. code-block:: python
18+
19+
import os
20+
import sys
21+
from sanic import Sanic
22+
from sanic.response import html
23+
from sse.api.urls import sse_bgp
24+
from sse import SseApp
25+
26+
app = Sanic(name="sse")
27+
28+
def init_app(_app):
29+
SseApp(_app)
30+
_app.blueprint(sse_bgp)
31+
32+
@app.route("/index", methods=["GET"])
33+
async def index(request):
34+
event = request.args.get("event", "test")
35+
url = f"sse/event/listen?event={event}"
36+
d = """
37+
<html>
38+
<body>
39+
<script>
40+
var source = new EventSource("%s");
41+
source.onmessage = function(e) {
42+
console.log("xxxxxxx");
43+
document.getElementById('response').innerHTML + e.data + "<br>";
44+
}
45+
</script>
46+
<h1>Getting server updates</h1>
47+
<div id="response"></div>
48+
</body>
49+
</html>
50+
""" % url
51+
return html(body=d)
52+
53+
if __name__ == "__main__":
54+
init_app(app)
55+
app.run(host="0.0.0.0", port=8000, workers=10)
56+
57+
58+
- 2.2 接口
59+
60+
.. code-block:: shell
61+
62+
GET /sse/event/send?event=test
63+
GET /sse/event/listen?event=test&client_id=
64+
GET /sse/event/terminate?event=test&client_id=
65+
66+
67+

docs/examples/s_sse.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import os
2+
import sys
3+
from sanic import Sanic
4+
from sanic.response import html
5+
6+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
7+
8+
from sse.api.urls import sse_bgp
9+
from sse import SseApp
10+
11+
app = Sanic(name="sse")
12+
13+
14+
def init_app(_app):
15+
SseApp(_app)
16+
_app.blueprint(sse_bgp)
17+
18+
19+
@app.route("/index", methods=["GET"])
20+
async def index(request):
21+
event = request.args.get("event", "test")
22+
url = f"sse/event/listen?event={event}"
23+
d = """
24+
<html>
25+
<body>
26+
<script>
27+
var source = new EventSource("%s");
28+
source.onmessage = function(e) {
29+
console.log("xxxxxxx");
30+
document.getElementById('response').innerHTML + e.data + "<br>";
31+
}
32+
</script>
33+
<h1>Getting server updates</h1>
34+
<div id="response"></div>
35+
</body>
36+
</html>
37+
""" % url
38+
return html(body=d)
39+
40+
41+
if __name__ == "__main__":
42+
init_app(app)
43+
app.run(host="0.0.0.0", port=8000, workers=10)

requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
aiopubsub-py3
2+
sanic

setup.cfg

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[flake8]
2+
ignore = E203, E266, E501, W503
3+
max-line-length = 80
4+
max-complexity = 18
5+
select = B,C,E,F,W,T4,B9
6+
exclude =
7+
docs/*

setup.py

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import os.path
2+
import re
3+
4+
from setuptools import find_packages, setup
5+
6+
7+
def read(*parts):
8+
with open(os.path.join(*parts)) as f:
9+
return f.read().strip()
10+
11+
12+
def read_version():
13+
regexp = re.compile(r"^__version__\W*=\W*\"([\d.abrc]+)\"")
14+
init_py = os.path.join(os.path.dirname(__file__), "sse", "__init__.py")
15+
with open(init_py) as f:
16+
for line in f:
17+
match = regexp.match(line)
18+
if match is not None:
19+
return match.group(1)
20+
raise RuntimeError(f"Cannot find version in {init_py}")
21+
22+
23+
classifiers = [
24+
"License :: OSI Approved :: MIT License",
25+
"Development Status :: 5 - Production/Stable",
26+
"Programming Language :: Python",
27+
"Programming Language :: Python :: 3.8",
28+
"Programming Language :: Python :: 3.9",
29+
"Programming Language :: Python :: 3.10",
30+
"Programming Language :: Python :: 3 :: Only",
31+
"Operating System :: POSIX",
32+
"Environment :: Web Environment",
33+
"Intended Audience :: Developers",
34+
"Topic :: Software Development",
35+
"Topic :: Software Development :: Libraries",
36+
"Framework :: AsyncIO",
37+
]
38+
39+
install_requires = [
40+
"sanic",
41+
"aiopubsub-py3"
42+
]
43+
44+
setup(
45+
name="sanic-sse-py3",
46+
version=read_version(),
47+
description="aio sanic sse ",
48+
long_description="\n\n".join((read("README.rst"), read("CHANGELOG.md"))),
49+
long_description_content_type="text/markdown",
50+
classifiers=classifiers,
51+
keywords="aio redis pubsub sanic sse",
52+
platforms=["POSIX"],
53+
url="https://github.com/daleeg/sanic_sse",
54+
license="MIT",
55+
packages=find_packages(exclude=["docs"]),
56+
install_requires=install_requires,
57+
python_requires=">=3.8",
58+
include_package_data=True,
59+
)

sse/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .sse import SseApp
2+
3+
__version__ = "1.0.0"
4+
5+
__all__ = ["SseApp"]

sse/api/__init__.py

Whitespace-only changes.

sse/api/urls.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from sanic import Blueprint
2+
3+
from .view import EventListenView, EventSendView, EventTerminateView
4+
5+
6+
__all__=["sse_bgp"]
7+
8+
sse_bp = Blueprint("sse_bp")
9+
sse_bp.add_route(EventListenView.as_view(), "/event/listen")
10+
sse_bp.add_route(EventSendView.as_view(), "/event/send")
11+
sse_bp.add_route(EventTerminateView.as_view(), "/event/terminate")
12+
13+
14+
blueprints = [
15+
sse_bp,
16+
]
17+
18+
sse_bgp = Blueprint.group(*blueprints, url_prefix="/sse")
19+
20+

sse/api/view.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import uuid
2+
3+
from sanic.request import Request
4+
from sanic.views import HTTPMethodView
5+
from sanic.response import json
6+
7+
from sse.core.request import get_group_id
8+
9+
10+
class EventListenView(HTTPMethodView):
11+
async def get(self, request: Request):
12+
sse = request.app.ctx.sse
13+
event = request.args.get("event")
14+
if not event:
15+
return json({"code": 1, "msg": "no event"})
16+
client_id = uuid.uuid4()
17+
group_id = get_group_id(request)
18+
return sse.sse_stream(event, client_id, group_id)
19+
20+
21+
class EventSendView(HTTPMethodView):
22+
async def get(self, request: Request):
23+
sse = request.app.ctx.sse
24+
event = request.args.get("event")
25+
if not event:
26+
return json({"code": 1, "msg": "no event"})
27+
remote_id = get_group_id(request)
28+
client_id = request.args.get("client_id")
29+
if not sse.is_registered(event, remote_id, client_id):
30+
return json({"code": 1, "msg": "not registered"})
31+
data = {
32+
"info": f"hello, {event}"
33+
}
34+
event_id = uuid.uuid4().hex
35+
await sse.send(data, event=event, client_id=client_id, event_id=event_id)
36+
print(sse._register.get_events())
37+
return json({"code": 0, "data": {"event": event, "data": data, "event_id": event_id}})
38+
39+
40+
class EventTerminateView(HTTPMethodView):
41+
async def get(self, request: Request):
42+
sse = request.app.ctx.sse
43+
event = request.args.get("event")
44+
if not event:
45+
return json({"code": 1, "msg": "no event"})
46+
client_id = request.args.get("client_id")
47+
group_id = get_group_id(request)
48+
if not sse.is_registered(event, client_id, group_id):
49+
return json({"code": 1, "msg": "not registered"})
50+
await sse.terminate(event=event, client_id=client_id)
51+
return json({"code": 0, "msg": "success"})

sse/core/channel.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from .storage import BaseStorageBackend
2+
3+
4+
class EventRegister(object):
5+
def __init__(self, storage: BaseStorageBackend):
6+
self._storage = storage
7+
8+
def register(self, event, client_id, group):
9+
if event in self._storage:
10+
event_info = self._storage[event]
11+
else:
12+
event_info = {}
13+
event_info[client_id] = group
14+
self._storage.set(event, event_info)
15+
16+
def unregister(self, event, client_id):
17+
if event not in self._storage:
18+
return
19+
event_info = self._storage[event]
20+
info = event_info.pop(client_id, None)
21+
if event_info:
22+
self._storage.set(event, event_info)
23+
else:
24+
self._storage.pop(event)
25+
return info
26+
27+
def is_registered(self, event, client_id, group):
28+
if event not in self._storage:
29+
return False
30+
event_info = self._storage[event]
31+
return not client_id or event_info.get(client_id) == group
32+
33+
def get_events(self):
34+
return self._storage.keys()
35+
36+
def get_clients(self):
37+
clients = set()
38+
for event in self.get_events():
39+
event_info = self._storage[event]
40+
for c in event_info.keys():
41+
clients.add(c)
42+
return list(clients)
43+
44+
def get_event_clients(self):
45+
result = []
46+
for event in self.get_events():
47+
event_info = self._storage[event]
48+
for c in event_info.keys():
49+
result.append([event, c])
50+
return result

sse/core/config.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import os
2+
from collections import namedtuple
3+
4+
Config = namedtuple("Config", ["ping_interval", "pubsub_options"])
5+
6+
7+
def load_config(app):
8+
config = getattr(app.ctx, "config", None) or {}
9+
app.ctx.config = merge_config(config)
10+
return app.ctx.config
11+
12+
13+
def merge_config(config):
14+
pubsub_options = config.get("pubsub_options", {})
15+
pubsub_options["host"] = pubsub_options.get("redis_host", os.getenv("SSE_REDIS_HOST", "127.0.0.1"))
16+
pubsub_options["port"] = int(pubsub_options.get("redis_port", os.getenv("SSE_REDIS_PORT", 16379)))
17+
pubsub_options["password"] = pubsub_options.get("redis_passwd", os.getenv("SSE_REDIS_PASSWD", ""))
18+
19+
return Config(
20+
ping_interval=config.get("ping_interval", int(os.getenv("SSE_PING_INTERVAL", 10))),
21+
pubsub_options=pubsub_options
22+
)

0 commit comments

Comments
 (0)