2017-02-20 22:32:14 +00:00
|
|
|
from sanic.exceptions import InvalidUsage
|
2017-02-20 20:25:44 +00:00
|
|
|
from sanic.server import HttpProtocol
|
|
|
|
from httptools import HttpParserUpgrade
|
2017-02-20 22:32:14 +00:00
|
|
|
from websockets import handshake, WebSocketCommonProtocol, InvalidHandshake
|
2017-02-20 20:25:44 +00:00
|
|
|
from websockets import ConnectionClosed # noqa
|
|
|
|
|
|
|
|
|
|
|
|
class WebSocketProtocol(HttpProtocol):
|
2017-05-26 04:11:26 +01:00
|
|
|
def __init__(self, *args, websocket_max_size=None,
|
|
|
|
websocket_max_queue=None, **kwargs):
|
2017-02-20 20:25:44 +00:00
|
|
|
super().__init__(*args, **kwargs)
|
2017-02-20 22:32:14 +00:00
|
|
|
self.websocket = None
|
2017-05-26 04:11:26 +01:00
|
|
|
self.websocket_max_size = websocket_max_size
|
|
|
|
self.websocket_max_queue = websocket_max_queue
|
2017-02-20 20:25:44 +00:00
|
|
|
|
2017-10-16 02:05:01 +01:00
|
|
|
# timeouts make no sense for websocket routes
|
|
|
|
def request_timeout_callback(self):
|
2017-02-25 02:09:32 +00:00
|
|
|
if self.websocket is None:
|
2017-10-16 02:05:01 +01:00
|
|
|
super().request_timeout_callback()
|
|
|
|
|
|
|
|
def response_timeout_callback(self):
|
|
|
|
if self.websocket is None:
|
|
|
|
super().response_timeout_callback()
|
|
|
|
|
|
|
|
def keep_alive_timeout_callback(self):
|
|
|
|
if self.websocket is None:
|
|
|
|
super().keep_alive_timeout_callback()
|
2017-02-25 02:09:32 +00:00
|
|
|
|
|
|
|
def connection_lost(self, exc):
|
|
|
|
if self.websocket is not None:
|
|
|
|
self.websocket.connection_lost(exc)
|
|
|
|
super().connection_lost(exc)
|
|
|
|
|
2017-02-20 20:25:44 +00:00
|
|
|
def data_received(self, data):
|
2017-02-20 22:32:14 +00:00
|
|
|
if self.websocket is not None:
|
2017-02-20 20:25:44 +00:00
|
|
|
# pass the data to the websocket protocol
|
2017-02-20 22:32:14 +00:00
|
|
|
self.websocket.data_received(data)
|
2017-02-20 20:25:44 +00:00
|
|
|
else:
|
|
|
|
try:
|
|
|
|
super().data_received(data)
|
|
|
|
except HttpParserUpgrade:
|
|
|
|
# this is okay, it just indicates we've got an upgrade request
|
|
|
|
pass
|
|
|
|
|
|
|
|
def write_response(self, response):
|
2017-02-20 22:32:14 +00:00
|
|
|
if self.websocket is not None:
|
2017-02-20 20:25:44 +00:00
|
|
|
# websocket requests do not write a response
|
|
|
|
self.transport.close()
|
|
|
|
else:
|
|
|
|
super().write_response(response)
|
|
|
|
|
2017-08-08 19:21:52 +01:00
|
|
|
async def websocket_handshake(self, request, subprotocols=None):
|
2017-02-20 20:25:44 +00:00
|
|
|
# let the websockets package do the handshake with the client
|
|
|
|
headers = []
|
|
|
|
|
|
|
|
def get_header(k):
|
|
|
|
return request.headers.get(k, '')
|
|
|
|
|
|
|
|
def set_header(k, v):
|
|
|
|
headers.append((k, v))
|
|
|
|
|
2017-02-20 22:32:14 +00:00
|
|
|
try:
|
|
|
|
key = handshake.check_request(get_header)
|
|
|
|
handshake.build_response(set_header, key)
|
|
|
|
except InvalidHandshake:
|
|
|
|
raise InvalidUsage('Invalid websocket request')
|
2017-02-20 20:25:44 +00:00
|
|
|
|
2017-08-08 19:21:52 +01:00
|
|
|
subprotocol = None
|
|
|
|
if subprotocols and 'Sec-Websocket-Protocol' in request.headers:
|
|
|
|
# select a subprotocol
|
|
|
|
client_subprotocols = [p.strip() for p in request.headers[
|
|
|
|
'Sec-Websocket-Protocol'].split(',')]
|
|
|
|
for p in client_subprotocols:
|
|
|
|
if p in subprotocols:
|
|
|
|
subprotocol = p
|
|
|
|
set_header('Sec-Websocket-Protocol', subprotocol)
|
|
|
|
break
|
|
|
|
|
2017-02-20 20:25:44 +00:00
|
|
|
# write the 101 response back to the client
|
|
|
|
rv = b'HTTP/1.1 101 Switching Protocols\r\n'
|
|
|
|
for k, v in headers:
|
|
|
|
rv += k.encode('utf-8') + b': ' + v.encode('utf-8') + b'\r\n'
|
|
|
|
rv += b'\r\n'
|
|
|
|
request.transport.write(rv)
|
|
|
|
|
|
|
|
# hook up the websocket protocol
|
2017-05-26 04:11:26 +01:00
|
|
|
self.websocket = WebSocketCommonProtocol(
|
|
|
|
max_size=self.websocket_max_size,
|
|
|
|
max_queue=self.websocket_max_queue
|
|
|
|
)
|
2017-08-08 19:21:52 +01:00
|
|
|
self.websocket.subprotocol = subprotocol
|
2017-02-20 22:32:14 +00:00
|
|
|
self.websocket.connection_made(request.transport)
|
|
|
|
return self.websocket
|