-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Socket.IO for message transport #449
Changes from all commits
56054f3
3e552ab
266ee6e
5ab4612
c21ae60
a6e0259
a7a7003
e4a5ad7
df8d5e6
981fc16
4a69bb0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
0.1.14 | ||
0.1.15 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,8 @@ | |
|
||
from typing import Any, Callable, Coroutine, Dict, List, Optional, Type, Union | ||
|
||
from fastapi import FastAPI, WebSocket | ||
from fastapi.middleware import cors | ||
from starlette.websockets import WebSocketDisconnect | ||
from fastapi import FastAPI | ||
from socketio import ASGIApp, AsyncNamespace, AsyncServer | ||
|
||
from pynecone import constants, utils | ||
from pynecone.base import Base | ||
|
@@ -33,6 +32,9 @@ class App(Base): | |
# The backend API object. | ||
api: FastAPI = None # type: ignore | ||
|
||
# The Socket.IO AsyncServer. | ||
sio: AsyncServer = None | ||
|
||
# The state class to use for the app. | ||
state: Type[State] = DefaultState | ||
|
||
|
@@ -64,10 +66,23 @@ def __init__(self, *args, **kwargs): | |
self.state_manager.setup(state=self.state) | ||
|
||
# Set up the API. | ||
|
||
self.api = FastAPI() | ||
self.add_cors() | ||
self.add_default_endpoints() | ||
|
||
# Set up the Socket.IO AsyncServer. | ||
self.sio = AsyncServer(async_mode="asgi", cors_allowed_origins="*") | ||
|
||
# Create the socket app. Note event endpoint constant replaces the default 'socket.io' path. | ||
socket_app = ASGIApp(self.sio, socketio_path=str(constants.Endpoint.EVENT)) | ||
|
||
# Create the event namespace and attach the main app. Not related to the path above. | ||
event_namespace = EventNamespace("/event") | ||
event_namespace.app = self | ||
|
||
# Register the event namespace with the socket. | ||
self.sio.register_namespace(event_namespace) | ||
|
||
# Mount the socket app with the API. | ||
self.api.mount("/", socket_app) | ||
|
||
def __repr__(self) -> str: | ||
"""Get the string representation of the app. | ||
|
@@ -85,24 +100,6 @@ def __call__(self) -> FastAPI: | |
""" | ||
return self.api | ||
|
||
def add_default_endpoints(self): | ||
"""Add the default endpoints.""" | ||
# To test the server. | ||
self.api.get(str(constants.Endpoint.PING))(ping) | ||
|
||
# To make state changes. | ||
self.api.websocket(str(constants.Endpoint.EVENT))(event(app=self)) | ||
|
||
def add_cors(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we not need this anymore? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's taken care of when the
There's also python-scoketio does not have CORS options like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay got it nice |
||
"""Add CORS middleware to the app.""" | ||
self.api.add_middleware( | ||
cors.CORSMiddleware, | ||
allow_origins=["*"], | ||
allow_credentials=True, | ||
allow_methods=["*"], | ||
allow_headers=["*"], | ||
) | ||
|
||
def preprocess(self, state: State, event: Event) -> Optional[Delta]: | ||
"""Preprocess the event. | ||
|
||
|
@@ -327,52 +324,6 @@ def compile(self, force_compile: bool = False): | |
compiler.compile_components(custom_components) | ||
|
||
|
||
async def ping() -> str: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be useful for health checks, etc. |
||
"""Test API endpoint. | ||
|
||
Returns: | ||
The response. | ||
""" | ||
return "pong" | ||
|
||
|
||
def event(app: App): | ||
"""Websocket endpoint for events. | ||
|
||
Args: | ||
app: The app to add the endpoint to. | ||
|
||
Returns: | ||
The websocket endpoint. | ||
""" | ||
|
||
async def ws(websocket: WebSocket): | ||
"""Create websocket endpoint. | ||
|
||
Args: | ||
websocket: The websocket sending events. | ||
""" | ||
# Accept the connection. | ||
await websocket.accept() | ||
|
||
# Process events until the connection is closed. | ||
while True: | ||
# Get the event. | ||
try: | ||
event = Event.parse_raw(await websocket.receive_text()) | ||
except WebSocketDisconnect: | ||
# Close the connection. | ||
return | ||
|
||
# Process the event. | ||
update = await process(app, event) | ||
|
||
# Send the update. | ||
await websocket.send_text(update.json()) | ||
|
||
return ws | ||
|
||
|
||
async def process(app: App, event: Event) -> StateUpdate: | ||
"""Process an event. | ||
|
||
|
@@ -405,3 +356,52 @@ async def process(app: App, event: Event) -> StateUpdate: | |
|
||
# Return the update. | ||
return update | ||
|
||
|
||
class EventNamespace(AsyncNamespace): | ||
"""The event namespace.""" | ||
|
||
# The backend API object. | ||
app: App | ||
|
||
def on_connect(self, sid, environ): | ||
"""Event for when the websocket disconnects. | ||
|
||
Args: | ||
sid: The Socket.IO session id. | ||
environ: The request information, including HTTP headers. | ||
""" | ||
pass | ||
|
||
def on_disconnect(self, sid): | ||
"""Event for when the websocket disconnects. | ||
|
||
Args: | ||
sid: The Socket.IO session id. | ||
""" | ||
pass | ||
|
||
async def on_event(self, sid, data): | ||
"""Event for receiving front-end websocket events. | ||
|
||
Args: | ||
sid: The Socket.IO session id. | ||
data: The event data. | ||
""" | ||
# Get the event. | ||
event = Event.parse_raw(data) | ||
|
||
# Process the event. | ||
update = await process(self.app, event) | ||
|
||
# Emit the event. | ||
await self.emit(str(constants.SocketEvent.EVENT), update.json(), to=sid) | ||
|
||
async def on_ping(self, sid): | ||
"""Event for testing the API endpoint. | ||
|
||
Args: | ||
sid: The Socket.IO session id. | ||
""" | ||
# Emit the test event. | ||
await self.emit(str(constants.SocketEvent.PING), "pong", to=sid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should keep the
ping
endpointThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added it back in. For future reference it can be tested like so:
In the console you should see: