-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
websocket support #469
websocket support #469
Conversation
Let me know if this is something you are open to incorporate into the project. If you think this is worthwhile, I can take it to the finish line, which means:
If you have any issues against merging this patch, let me know, and I'll look into releasing this as a separate extension. Thanks! |
b02ed96
to
83802dc
Compare
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.
Overall good, couple of points just about naming and backslashes. This implementation is good and I think once we have some tests written into it we can merge in. I'll make this a part of the 0.4.0
release.
sanic/app.py
Outdated
@@ -168,6 +170,40 @@ def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None): | |||
self.route(uri=uri, methods=methods, host=host)(handler) | |||
return handler | |||
|
|||
# Decorator | |||
def ws(self, uri, host=None): |
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.
Can we name something other than ws
I don't think it's necessarily explicit in what it actually is. I think something like websocket
or web_socket
would be good here.
sanic/app.py
Outdated
@@ -459,6 +495,9 @@ def run(self, host="127.0.0.1", port=8000, debug=False, before_start=None, | |||
:param protocol: Subclass of asyncio protocol class | |||
:return: Nothing | |||
""" | |||
if protocol is None: | |||
protocol = WebSocketProtocol if self.needs_websocket \ |
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.
Prefer parenthesis to backslashes here.
backlog=100, stop_event=None): | ||
"""Asynchronous version of `run`. | ||
|
||
NOTE: This does not support multiprocessing and is not the preferred | ||
way to run a Sanic application. | ||
""" | ||
if protocol is None: |
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.
Prefer parenthesis to backslashes here.
sanic/ws.py
Outdated
class WebSocketProtocol(HttpProtocol): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.ws = None |
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.
I would say ws
in this instance too should be renamed to websocket
or web_socket
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.
+1 for websocket vs web_socket
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.
I'd say the same 👍
@seemethere @r0fls added a unit test and addressed your comments. The coverage report in tox is weird, coverage is off while the tests run, so the low coverage numbers you get on all the modules is incorrect. I enabled coverage here and measured 87% for the new websocket.py file, and 85% for the entire sanic package. Let me know if you have any more feedback. |
I've been playing around with this branch and I'm getting an exception raised after a idle websocket client connection (maybe about a minute). I created a client directly using websockets, here's the gist, which didn't have this problem. Here's the Sanic version and the stack trace I recieve: 2017-02-25 00:28:17,904: ERROR: NoneType: None
2017-02-25 00:28:17,905: ERROR: Traceback (most recent call last):
File "/Users/jamesstidard/Development/Miscellany/Hello-Sanic/venv/src/sanic/sanic/app.py", line 431, in handle_request
response = await response
File "/usr/local/var/pyenv/versions/3.6.0/lib/python3.6/asyncio/coroutines.py", line 128, in throw
return self.gen.throw(type, value, traceback)
File "/Users/jamesstidard/Development/Miscellany/Hello-Sanic/venv/src/sanic/sanic/app.py", line 193, in websocket_handler
await handler(request, ws, *args, **kwargs)
File "/usr/local/var/pyenv/versions/3.6.0/lib/python3.6/asyncio/coroutines.py", line 128, in throw
return self.gen.throw(type, value, traceback)
File "/Users/jamesstidard/Development/Miscellany/Hello-Sanic/hellosanic/serve.py", line 29, in websocket
message = await ws.recv()
File "/Users/jamesstidard/Development/Miscellany/Hello-Sanic/venv/lib/python3.6/site-packages/websockets/protocol.py", line 284, in recv
loop=self.loop, return_when=asyncio.FIRST_COMPLETED)
File "/usr/local/var/pyenv/versions/3.6.0/lib/python3.6/asyncio/tasks.py", line 307, in wait
return (yield from _wait(fs, timeout, return_when, loop))
File "/usr/local/var/pyenv/versions/3.6.0/lib/python3.6/asyncio/tasks.py", line 390, in _wait
yield from waiter
concurrent.futures._base.CancelledError
2017-02-25 00:28:17,906: ERROR: Exception occurred while handling uri: "/ws"
Traceback (most recent call last):
File "/path/to/project/venv/src/sanic/sanic/app.py", line 431, in handle_request
response = await response
File "/usr/local/var/pyenv/versions/3.6.0/lib/python3.6/asyncio/coroutines.py", line 128, in throw
return self.gen.throw(type, value, traceback)
File "/path/to/project/venv/src/sanic/sanic/app.py", line 193, in websocket_handler
await handler(request, ws, *args, **kwargs)
File "/usr/local/var/pyenv/versions/3.6.0/lib/python3.6/asyncio/coroutines.py", line 128, in throw
return self.gen.throw(type, value, traceback)
File "/path/to/project/hellosanic/serve.py", line 29, in websocket
message = await ws.recv()
File "/path/to/project/venv/lib/python3.6/site-packages/websockets/protocol.py", line 284, in recv
loop=self.loop, return_when=asyncio.FIRST_COMPLETED)
File "/usr/local/var/pyenv/versions/3.6.0/lib/python3.6/asyncio/tasks.py", line 307, in wait
return (yield from _wait(fs, timeout, return_when, loop))
File "/usr/local/var/pyenv/versions/3.6.0/lib/python3.6/asyncio/tasks.py", line 390, in _wait
yield from waiter
concurrent.futures._base.CancelledError Let me know if there's anymore details I can give you, or if I'm just doing something silly. |
@jamesstidard I missed that, thanks. Sanic imposes a 60 second timeout on handlers, I need to circumvent that for websocket routes. |
@miguelgrinberg No problem. Thank you for taking the time to implement WebSockets - I got the impression it was a low priority. |
@jamesstidard yeah, probably not high priority for the project admins, but I personally would like to have it so that I can have sanic support in my socketio server. |
e285ffe
to
60f0ccf
Compare
@jamesstidard Let me know if your websocket client is now happy. |
@miguelgrinberg Seems to be behaving, Thank you. |
I wouldn't say that it's not high priority. Just haven't had the time to implement it. I am curious though, from an ops perspective it might make more sense to have the websocket application be it's own process (outside of the sanic app). What logic there is for having them run in the same loop, aside from convenience? |
Not that I don't support this, since it at least makes sense on a small scale. On a large scale though, is it really the ideal way to run in production? |
@r0fls As I mentioned above, I'm particularly interested in extending my Socket.IO server on to sanic (I already have aiohttp support). The Socket.IO protocol requires a combined HTTP + WebSocket interface on the same host and port, so it is really inconvenient if the servers are running separately, even if they share the same process as you currently recommend. As far as plain websocket, I think there are use cases that benefit from a separate servers approach, like you say, but there are cases where that is an inconvenience. If you are into microservices, for example, you are probably well set up to have a separate websocket server that can share access to storage and other services. If you have a monolithic server, then there's going to be all sorts of problems if you are forced to host just the websocket service separately. |
11aaeb2
to
104a7c7
Compare
@miguelgrinberg |
@miguelgrinberg I'm confused by your statement that "timeouts make no sense for websocket routes". I agree that makes sense, but the code doesn't seem to keep the connection alive. Was that intended, or was that more of a TODO? I.e. currently, it seems that we need to currently manage the ping/pong to keep sanic from disconnecting. |
@dfee That statement I made was in relation to the bug reported in #545 (comment). The timeouts I was talking about were the ones sanic imposes on all routes, which default to 60 seconds if I remember correctly. A WebSocket route will last for as long as the two parties are connected, so you can't apply that kind of timeout.
Can you give me more details, or a way to reproduce this? |
@miguelgrinberg gotcha. Here's what I've done to work around that auto-disconnect (I manually intervene):
|
@dfee this "auto-disconnect" that you mention, is it the client or the server doing it? Sounds like if you need to send pings to the client to prevent a disconnection it might be the client that has that requirement. I guess we can add a background ping to prevent this, but I'm not sure this is a problem in general, it might be specific to the client that you are using. |
I'm using the latest version of Chrome. Does it not time out for you? |
@dfee I don't recall getting timeouts, but maybe I did not leave the connection idle long enough. As far as I know, there isn't a required ping/pong frequency in the standard, this is left to handle by the client and server on their own. I'll look at how other ws servers do this, I honestly have no idea what the right frequency to ping might be. Consider that if you have 10K clients, you'll be running as many pings every 30 seconds. |
Oh man. I tried for a couple hours this morning to get to the root of it. I finally did. This is not a problem with I needed to add the following to my nginx config:
Thanks for your help @miguelgrinberg |
websocket support
websocket support
Fixes #12