summaryrefslogtreecommitdiff
path: root/game/python-extra/ws4py/server/tulipserver.py
diff options
context:
space:
mode:
Diffstat (limited to 'game/python-extra/ws4py/server/tulipserver.py')
-rw-r--r--game/python-extra/ws4py/server/tulipserver.py224
1 files changed, 224 insertions, 0 deletions
diff --git a/game/python-extra/ws4py/server/tulipserver.py b/game/python-extra/ws4py/server/tulipserver.py
new file mode 100644
index 0000000..2786c16
--- /dev/null
+++ b/game/python-extra/ws4py/server/tulipserver.py
@@ -0,0 +1,224 @@
+# -*- coding: utf-8 -*-
+import base64
+from hashlib import sha1
+from email.parser import BytesHeaderParser
+import io
+
+import asyncio
+
+from ws4py import WS_KEY, WS_VERSION
+from ws4py.exc import HandshakeError
+from ws4py.websocket import WebSocket
+
+LF = b'\n'
+CRLF = b'\r\n'
+SPACE = b' '
+EMPTY = b''
+
+__all__ = ['WebSocketProtocol']
+
+class WebSocketProtocol(asyncio.StreamReaderProtocol):
+ def __init__(self, handler_cls):
+ asyncio.StreamReaderProtocol.__init__(self, asyncio.StreamReader(),
+ self._pseudo_connected)
+ self.ws = handler_cls(self)
+
+ def _pseudo_connected(self, reader, writer):
+ pass
+
+ def connection_made(self, transport):
+ """
+ A peer is now connected and we receive an instance
+ of the underlying :class:`asyncio.Transport`.
+
+ We :class:`asyncio.StreamReader` is created
+ and the transport is associated before the
+ initial HTTP handshake is undertaken.
+ """
+ #self.transport = transport
+ #self.stream = asyncio.StreamReader()
+ #self.stream.set_transport(transport)
+ asyncio.StreamReaderProtocol.connection_made(self, transport)
+ # Let make it concurrent for others to tag along
+ f = asyncio.async(self.handle_initial_handshake())
+ f.add_done_callback(self.terminated)
+
+ @property
+ def writer(self):
+ return self._stream_writer
+
+ @property
+ def reader(self):
+ return self._stream_reader
+
+ def terminated(self, f):
+ if f.done() and not f.cancelled():
+ ex = f.exception()
+ if ex:
+ response = [b'HTTP/1.0 400 Bad Request']
+ response.append(b'Content-Length: 0')
+ response.append(b'Connection: close')
+ response.append(b'')
+ response.append(b'')
+ self.writer.write(CRLF.join(response))
+ self.ws.close_connection()
+
+ def close(self):
+ """
+ Initiate the websocket closing handshake
+ which will eventuall lead to the underlying
+ transport.
+ """
+ self.ws.close()
+
+ def timeout(self):
+ self.ws.close_connection()
+ if self.ws.started:
+ self.ws.closed(1002, "Peer connection timed-out")
+
+ def connection_lost(self, exc):
+ """
+ The peer connection is now, the closing
+ handshake won't work so let's not even try.
+ However let's make the websocket handler
+ be aware of it by calling its `closed`
+ method.
+ """
+ if exc is not None:
+ self.ws.close_connection()
+ if self.ws.started:
+ self.ws.closed(1002, "Peer connection was lost")
+
+ @asyncio.coroutine
+ def handle_initial_handshake(self):
+ """
+ Performs the HTTP handshake described in :rfc:`6455`. Note that
+ this implementation is really basic and it is strongly advised
+ against using it in production. It would probably break for
+ most clients. If you want a better support for HTTP, please
+ use a more reliable HTTP server implemented using asyncio.
+ """
+ request_line = yield from self.next_line()
+ method, uri, req_protocol = request_line.strip().split(SPACE, 2)
+
+ # GET required
+ if method.upper() != b'GET':
+ raise HandshakeError('HTTP method must be a GET')
+
+ headers = yield from self.read_headers()
+ if req_protocol == b'HTTP/1.1' and 'Host' not in headers:
+ raise ValueError("Missing host header")
+
+ for key, expected_value in [('Upgrade', 'websocket'),
+ ('Connection', 'upgrade')]:
+ actual_value = headers.get(key, '').lower()
+ if not actual_value:
+ raise HandshakeError('Header %s is not defined' % str(key))
+ if expected_value not in actual_value:
+ raise HandshakeError('Illegal value for header %s: %s' %
+ (key, actual_value))
+
+ response_headers = {}
+
+ ws_version = WS_VERSION
+ version = headers.get('Sec-WebSocket-Version')
+ supported_versions = ', '.join([str(v) for v in ws_version])
+ version_is_valid = False
+ if version:
+ try: version = int(version)
+ except: pass
+ else: version_is_valid = version in ws_version
+
+ if not version_is_valid:
+ response_headers['Sec-WebSocket-Version'] = supported_versions
+ raise HandshakeError('Unhandled or missing WebSocket version')
+
+ key = headers.get('Sec-WebSocket-Key')
+ if key:
+ ws_key = base64.b64decode(key.encode('utf-8'))
+ if len(ws_key) != 16:
+ raise HandshakeError("WebSocket key's length is invalid")
+
+ protocols = []
+ ws_protocols = []
+ subprotocols = headers.get('Sec-WebSocket-Protocol')
+ if subprotocols:
+ for s in subprotocols.split(','):
+ s = s.strip()
+ if s in protocols:
+ ws_protocols.append(s)
+
+ exts = []
+ ws_extensions = []
+ extensions = headers.get('Sec-WebSocket-Extensions')
+ if extensions:
+ for ext in extensions.split(','):
+ ext = ext.strip()
+ if ext in exts:
+ ws_extensions.append(ext)
+
+ self.ws.protocols = ws_protocols
+ self.ws.extensions = ws_extensions
+ self.ws.headers = headers
+
+ response = [req_protocol + b' 101 Switching Protocols']
+ response.append(b'Upgrade: websocket')
+ response.append(b'Content-Type: text/plain')
+ response.append(b'Content-Length: 0')
+ response.append(b'Connection: Upgrade')
+ response.append(b'Sec-WebSocket-Version:' + bytes(str(version), 'utf-8'))
+ response.append(b'Sec-WebSocket-Accept:' + base64.b64encode(sha1(key.encode('utf-8') + WS_KEY).digest()))
+ if ws_protocols:
+ response.append(b'Sec-WebSocket-Protocol:' + b', '.join(ws_protocols))
+ if ws_extensions:
+ response.append(b'Sec-WebSocket-Extensions:' + b','.join(ws_extensions))
+ response.append(b'')
+ response.append(b'')
+ self.writer.write(CRLF.join(response))
+ yield from self.handle_websocket()
+
+ @asyncio.coroutine
+ def handle_websocket(self):
+ """
+ Starts the websocket process until the
+ exchange is completed and terminated.
+ """
+ yield from self.ws.run()
+
+ @asyncio.coroutine
+ def read_headers(self):
+ """
+ Read all HTTP headers from the HTTP request
+ and returns a dictionary of them.
+ """
+ headers = b''
+ while True:
+ line = yield from self.next_line()
+ headers += line
+ if line == CRLF:
+ break
+ return BytesHeaderParser().parsebytes(headers)
+
+ @asyncio.coroutine
+ def next_line(self):
+ """
+ Reads data until \r\n is met and then return all read
+ bytes.
+ """
+ line = yield from self.reader.readline()
+ if not line.endswith(CRLF):
+ raise ValueError("Missing mandatory trailing CRLF")
+ return line
+
+if __name__ == '__main__':
+ from ws4py.async_websocket import EchoWebSocket
+
+ loop = asyncio.get_event_loop()
+
+ def start_server():
+ proto_factory = lambda: WebSocketProtocol(EchoWebSocket)
+ return loop.create_server(proto_factory, '', 9007)
+
+ s = loop.run_until_complete(start_server())
+ print('serving on', s.sockets[0].getsockname())
+ loop.run_forever()