def test_helper_with_binary_message(self): msg = os.urandom(16) s = Stream() m = s.binary_message(msg) self.assertIsInstance(m, BinaryMessage) self.assertTrue(m.is_binary) self.assertFalse(m.is_text) self.assertEqual(m.opcode, OPCODE_BINARY) self.assertIsInstance(m.data, bytes) self.assertEqual(len(m), 16) self.assertEqual(len(m.data), 16) self.assertEqual(m.data, msg)
class WebSocketBaseClient(object): upgrade_header = 'Upgrade' def __init__(self, url, protocols=None, version='8'): self.stream = Stream() self.url = url self.protocols = protocols self.version = version self.key = b64encode(os.urandom(16)) self.client_terminated = False self.server_terminated = False @property def handshake_headers(self): parts = urlsplit(self.url) host = parts.netloc if ':' in host: host, port = parts.netloc.split(':') headers = [('Host', host), ('Connection', 'Upgrade'), (self.upgrade_header, 'websocket'), ('Sec-WebSocket-Key', self.key), ('Sec-WebSocket-Origin', self.url), ('Sec-WebSocket-Version', self.version)] if self.protocols: headers.append( ('Sec-WebSocket-Protocol', ','.join(self.protocols))) return headers @property def handshake_request(self): parts = urlsplit(self.url) headers = self.handshake_headers request = ["GET %s HTTP/1.1" % parts.path] for header, value in headers: request.append("%s: %s" % (header, value)) request.append('\r\n') return '\r\n'.join(request) def process_response_line(self, response_line): protocol, code, status = response_line.split(' ', 2) if code != '101': raise HandshakeError("Invalid response status: %s %s" % (code, status)) def process_handshake_header(self, headers): protocols = [] extensions = [] headers = headers.strip() for header_line in headers.split('\r\n'): header, value = header_line.split(':', 1) header = header.strip().lower() value = value.strip().lower() if header == 'upgrade' and value != 'websocket': raise HandshakeError("Invalid Upgrade header: %s" % value) elif header == 'connection' and value != 'upgrade': raise HandshakeError("Invalid Connection header: %s" % value) elif header == 'sec-websocket-accept': match = b64encode(sha1(self.key + WS_KEY).digest()) if value != match.lower(): raise HandshakeError("Invalid challenge response: %s" % value) elif header == 'sec-websocket-protocol': protocols = ','.join(value) elif header == 'sec-websocket-extensions': extensions = ','.join(value) return protocols, extensions def opened(self, protocols, extensions): pass def received_message(self, m): pass def closed(self, code, reason=None): pass @property def terminated(self): return self.client_terminated is True and self.server_terminated is True def close(self, reason='', code=1000): if not self.client_terminated: self.client_terminated = True self.write_to_connection( self.stream.close(code=code, reason=reason)) def connect(self): raise NotImplemented() def write_to_connection(self, bytes): raise NotImplemented() def read_from_connection(self, amount): raise NotImplemented() def close_connection(self): raise NotImplemented() def send(self, payload, binary=False): if isinstance(payload, basestring): if not binary: self.write_to_connection( self.stream.text_message(payload).single(mask=True)) else: self.write_to_connection( self.stream.binary_message(payload).single(mask=True)) elif isinstance(payload, dict): self.write_to_connection( self.stream.text_message( json.dumps(payload)).single(mask=True)) elif type(payload) == types.GeneratorType: bytes = payload.next() first = True for chunk in payload: if not binary: self.write_to_connection( self.stream.text_message(bytes).fragment(first=first, mask=True)) else: self.write_to_connection( self.stream.binary_message(payload).fragment( first=first, mask=True)) bytes = chunk first = False if not binary: self.write_to_connection( self.stream.text_message(bytes).fragment(last=True, mask=True)) else: self.write_to_connection( self.stream.text_message(bytes).fragment(last=True, mask=True))
class WebSocketHandler(object): def __init__(self, sock, protocols, extensions): """ A handler appropriate for servers. This handler runs the connection's read and parsing in a thread, meaning that incoming messages will be alerted in a different thread from the one that created the handler. @param sock: opened connection after the websocket upgrade @param protocols: list of protocols from the handshake @param extensions: list of extensions from the handshake """ self.stream = Stream() self.protocols = protocols self.extensions = extensions self.sock = sock self.sock.settimeout(30.0) self.client_terminated = False self.server_terminated = False self._th = threading.Thread(target=self._receive) def opened(self): """ Called by the server when the upgrade handshake has succeeeded. Starts the internal loop that reads bytes from the connection and hands it over to the stream for parsing. """ self._th.start() def close(self, code=1000, reason=''): """ Call this method to initiate the websocket connection closing by sending a close frame to the connected peer. Once this method is called, the server_terminated attribute is set. Calling this method several times is safe as the closing frame will be sent only the first time. @param code: status code describing why the connection is closed @param reason: a human readable message describing why the connection is closed """ if not self.server_terminated: self.server_terminated = True self.write_to_connection(self.stream.close(code=code, reason=reason)) def closed(self, code, reason=None): """ Called by the server when the websocket connection is finally closed. @param code: status code @param reason: human readable message of the closing exchange """ pass @property def terminated(self): """ Returns True if both the client and server have been marked as terminated. """ return self.client_terminated is True and self.server_terminated is True def write_to_connection(self, bytes): """ Writes the provided bytes to the underlying connection. @param bytes: data tio send out """ return self.sock.sendall(bytes) def read_from_connection(self, amount): """ Reads bytes from the underlying connection. @param amount: quantity to read (if possible) """ return self.sock.recv(amount) def close_connection(self): """ Shutdowns then closes the underlying connection. """ try: self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() except: pass def ponged(self, pong): """ Pong message received on the stream. @param pong: messaging.PongControlMessage instance """ pass def received_message(self, m): """ Message received on the stream. @param pong: messaging.TextMessage or messaging.BinaryMessage instance """ pass def send(self, payload, binary=False): """ Sends the given payload out. If payload is some bytes or a bytearray, then it is sent as a single message not fragmented. If payload is a generator, each chunk is sent as part of fragmented message. @param payload: string, bytes, bytearray or a generator @param binary: if set, handles the payload as a binary message """ if isinstance(payload, basestring) or isinstance(payload, bytearray): if not binary: self.write_to_connection(self.stream.text_message(payload).single()) else: self.write_to_connection(self.stream.binary_message(payload).single()) elif type(payload) == types.GeneratorType: bytes = payload.next() first = True for chunk in payload: if not binary: self.write_to_connection(self.stream.text_message(bytes).fragment(first=first)) else: self.write_to_connection(self.stream.binary_message(payload).fragment(first=first)) bytes = chunk first = False if not binary: self.write_to_connection(self.stream.text_message(bytes).fragment(last=True)) else: self.write_to_connection(self.stream.text_message(bytes).fragment(last=True)) def _receive(self): """ Performs the operation of reading from the underlying connection in order to feed the stream of bytes. We start with a small size of two bytes to be read from the connection so that we can quickly parse an incoming frame header. Then the stream indicates whatever size must be read from the connection since it knows the frame payload length. Note that we perform some automatic opererations: * On a closing message, we respond with a closing message and finally close the connection * We respond to pings with pong messages. * Whenever an error is raised by the stream parsing, we initiate the closing of the connection with the appropiate error code. """ next_size = 2 try: self.sock.setblocking(1) while not self.terminated: bytes = self.read_from_connection(next_size) if not bytes and next_size > 0: break s = self.stream next_size = s.parser.send(bytes) if s.closing is not None: if not self.server_terminated: next_size = 2 self.close(s.closing.code, s.closing.reason) else: self.client_terminated = True break elif s.errors: errors = s.errors[:] for error in errors: self.close(error.code, error.reason) s.errors.remove(error) break elif s.has_message: self.received_message(s.message) s.message.data = None s.message = None for ping in s.pings: self.write_to_connection(s.pong(str(ping.data))) s.pings = [] for pong in s.pongs: self.ponged(pong) s.pongs = [] except: print "".join(traceback.format_exception(*exc_info())) finally: self.client_terminated = self.server_terminated = True self.close_connection() if self.stream.closing: self.closed(self.stream.closing.code, self.stream.closing.reason) else: self.closed(1006)
class WebSocketBaseClient(object): def __init__(self, url, protocols=None, version='8'): self.stream = Stream() self.url = url self.protocols = protocols self.version = version self.key = b64encode(os.urandom(16)) self.client_terminated = False self.server_terminated = False @property def handshake_headers(self): parts = urlsplit(self.url) host = parts.netloc if ':' in host: host, port = parts.netloc.split(':') headers = [ ('Host', host), ('Connection', 'Upgrade'), ('Upgrade', 'websocket'), ('Sec-WebSocket-Key', self.key), ('Sec-WebSocket-Origin', self.url), ('Sec-WebSocket-Version', self.version) ] if self.protocols: headers.append(('Sec-WebSocket-Protocol', ','.join(self.protocols))) return headers @property def handshake_request(self): parts = urlsplit(self.url) headers = self.handshake_headers request = ["GET %s HTTP/1.1" % parts.path] for header, value in headers: request.append("%s: %s" % (header, value)) request.append('\r\n') return '\r\n'.join(request) def process_response_line(self, response_line): protocol, code, status = response_line.split(' ', 2) if code != '101': raise HandshakeError("Invalid response status: %s %s" % (code, status)) def process_handshake_header(self, headers): protocols = [] extensions = [] headers = headers.strip() for header_line in headers.split('\r\n'): header, value = header_line.split(':', 1) header = header.strip().lower() value = value.strip().lower() if header == 'upgrade' and value != 'websocket': raise HandshakeError("Invalid Upgrade header: %s" % value) elif header == 'connection' and value != 'upgrade': raise HandshakeError("Invalid Connection header: %s" % value) elif header == 'sec-websocket-accept': match = b64encode(sha1(self.key + WS_KEY).digest()) if value != match.lower(): raise HandshakeError("Invalid challenge response: %s" % value) elif header == 'sec-websocket-protocol': protocols = ','.join(value) elif header == 'sec-websocket-extensions': extensions = ','.join(value) return protocols, extensions def opened(self, protocols, extensions): pass def received_message(self, m): pass def closed(self, code, reason=None): pass @property def terminated(self): return self.client_terminated is True and self.server_terminated is True def close(self, reason='', code=1000): if not self.client_terminated: self.client_terminated = True self.write_to_connection(self.stream.close(code=code, reason=reason)) def connect(self): raise NotImplemented() def write_to_connection(self, bytes): raise NotImplemented() def read_from_connection(self, amount): raise NotImplemented() def close_connection(self): raise NotImplemented() def send(self, payload, binary=False): if isinstance(payload, basestring): if not binary: self.write_to_connection(self.stream.text_message(payload).single(mask=True)) else: self.write_to_connection(self.stream.binary_message(payload).single(mask=True)) elif isinstance(payload, dict): self.write_to_connection(self.stream.text_message(json.dumps(payload)).single(mask=True)) elif type(payload) == types.GeneratorType: bytes = payload.next() first = True for chunk in payload: if not binary: self.write_to_connection(self.stream.text_message(bytes).fragment(first=first, mask=True)) else: self.write_to_connection(self.stream.binary_message(payload).fragment(first=first, mask=True)) bytes = chunk first = False if not binary: self.write_to_connection(self.stream.text_message(bytes).fragment(last=True, mask=True)) else: self.write_to_connection(self.stream.text_message(bytes).fragment(last=True, mask=True))
class WebSocketHandler(object): def __init__(self, sock, protocols, extensions): """ A handler appropriate for servers. This handler runs the connection's read and parsing in a thread, meaning that incoming messages will be alerted in a different thread from the one that created the handler. @param sock: opened connection after the websocket upgrade @param protocols: list of protocols from the handshake @param extensions: list of extensions from the handshake """ self.stream = Stream() self.protocols = protocols self.extensions = extensions self.sock = sock self.sock.settimeout(30.0) self.client_terminated = False self.server_terminated = False self._lock = threading.Lock() self._th = threading.Thread(target=self._receive) def opened(self): """ Called by the server when the upgrade handshake has succeeeded. Starts the internal loop that reads bytes from the connection and hands it over to the stream for parsing. """ self._th.start() def close(self, code=1000, reason=''): """ Call this method to initiate the websocket connection closing by sending a close frame to the connected peer. Once this method is called, the server_terminated attribute is set. Calling this method several times is safe as the closing frame will be sent only the first time. @param code: status code describing why the connection is closed @param reason: a human readable message describing why the connection is closed """ if not self.server_terminated: self.server_terminated = True self.write_to_connection( self.stream.close(code=code, reason=reason)) def closed(self, code, reason=None): """ Called by the server when the websocket connection is finally closed. @param code: status code @param reason: human readable message of the closing exchange """ pass @property def terminated(self): """ Returns True if both the client and server have been marked as terminated. """ return self.client_terminated is True and self.server_terminated is True def write_to_connection(self, bytes): """ Writes the provided bytes to the underlying connection. @param bytes: data tio send out """ return self.sock.sendall(bytes) def read_from_connection(self, amount): """ Reads bytes from the underlying connection. @param amount: quantity to read (if possible) """ return self.sock.recv(amount) def close_connection(self): """ Shutdowns then closes the underlying connection. """ try: self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() except: pass def ponged(self, pong): """ Pong message received on the stream. @param pong: messaging.PongControlMessage instance """ pass def received_message(self, m): """ Message received on the stream. @param pong: messaging.TextMessage or messaging.BinaryMessage instance """ pass def send(self, payload, binary=False): """ Sends the given payload out. If payload is some bytes or a bytearray, then it is sent as a single message not fragmented. If payload is a generator, each chunk is sent as part of fragmented message. @param payload: string, bytes, bytearray or a generator @param binary: if set, handles the payload as a binary message """ if isinstance(payload, basestring) or isinstance(payload, bytearray): if not binary: self.write_to_connection( self.stream.text_message(payload).single()) else: self.write_to_connection( self.stream.binary_message(payload).single()) elif type(payload) == types.GeneratorType: bytes = payload.next() first = True for chunk in payload: if not binary: self.write_to_connection( self.stream.text_message(bytes).fragment(first=first)) else: self.write_to_connection( self.stream.binary_message(payload).fragment( first=first)) bytes = chunk first = False if not binary: self.write_to_connection( self.stream.text_message(bytes).fragment(last=True)) else: self.write_to_connection( self.stream.text_message(bytes).fragment(last=True)) def _receive(self): """ Performs the operation of reading from the underlying connection in order to feed the stream of bytes. We start with a small size of two bytes to be read from the connection so that we can quickly parse an incoming frame header. Then the stream indicates whatever size must be read from the connection since it knows the frame payload length. Note that we perform some automatic opererations: * On a closing message, we respond with a closing message and finally close the connection * We respond to pings with pong messages. * Whenever an error is raised by the stream parsing, we initiate the closing of the connection with the appropiate error code. """ next_size = 2 try: self.sock.setblocking(1) while not self.terminated: bytes = self.read_from_connection(next_size) if not bytes and next_size > 0: break with self._lock: s = self.stream next_size = s.parser.send(bytes) if s.closing is not None: if not self.server_terminated: next_size = 2 self.close(s.closing.code, s.closing.reason) else: self.client_terminated = True break elif s.errors: errors = s.errors[:] for error in s.errors: self.close(error.code, error.reason) s.errors.remove(error) break elif s.has_message: self.received_message(s.message) s.message.data = None s.message = None for ping in s.pings: self.write_to_connection(s.pong(str(ping.data))) s.pings = [] for pong in s.pongs: self.ponged(pong) s.pongs = [] except: print "".join(traceback.format_exception(*exc_info())) finally: self.client_terminated = self.server_terminated = True self.close_connection() if self.stream.closing: self.closed(self.stream.closing.code, self.stream.closing.reason) else: self.closed(1006)
class WebSocket(object): def __init__(self, sock, environ, protocols=None, extensions=None): self.stream = Stream() self.protocols = protocols self.extensions = extensions self.environ = environ self.sock = sock self.sock.settimeout(30.0) self.client_terminated = False self.server_terminated = False self._lock = Semaphore() def close(self, code=1000, reason=''): """ Call this method to initiate the websocket connection closing by sending a close frame to the connected peer. Once this method is called, the server_terminated attribute is set. Calling this method several times is safe as the closing frame will be sent only the first time. @param code: status code describing why the connection is closed @param reason: a human readable message describing why the connection is closed """ if not self.server_terminated: self.server_terminated = True self.write_to_connection( self.stream.close(code=code, reason=reason)) self.close_connection() @property def terminated(self): """ Returns True if both the client and server have been marked as terminated. """ return self.client_terminated is True and self.server_terminated is True def write_to_connection(self, bytes): """ Writes the provided bytes to the underlying connection. @param bytes: data tio send out """ return self.sock.sendall(bytes) def read_from_connection(self, amount): """ Reads bytes from the underlying connection. @param amount: quantity to read (if possible) """ return self.sock.recv(amount) def close_connection(self): """ Shutdowns then closes the underlying connection. """ try: self.sock.shutdown(socket.SHUT_RDWR) finally: self.sock.close() def send(self, payload, binary=False): """ Sends the given payload out. If payload is some bytes or a bytearray, then it is sent as a single message not fragmented. If payload is a generator, each chunk is sent as part of fragmented message. @param payload: string, bytes, bytearray or a generator @param binary: if set, handles the payload as a binary message """ if isinstance(payload, basestring) or isinstance(payload, bytearray): if not binary: self.write_to_connection( self.stream.text_message(payload).single()) else: self.write_to_connection( self.stream.binary_message(payload).single()) elif type(payload) == types.GeneratorType: bytes = payload.next() first = True for chunk in payload: if not binary: self.write_to_connection( self.stream.text_message(bytes).fragment(first=first)) else: self.write_to_connection( self.stream.binary_message(payload).fragment( first=first)) bytes = chunk first = False if not binary: self.write_to_connection( self.stream.text_message(bytes).fragment(last=True)) else: self.write_to_connection( self.stream.text_message(bytes).fragment(last=True)) def receive(self, message_obj=False): """ Performs the operation of reading from the underlying connection in order to feed the stream of bytes. We start with a small size of two bytes to be read from the connection so that we can quickly parse an incoming frame header. Then the stream indicates whatever size must be read from the connection since it knows the frame payload length. Note that we perform some automatic opererations: * On a closing message, we respond with a closing message and finally close the connection * We respond to pings with pong messages. * Whenever an error is raised by the stream parsing, we initiate the closing of the connection with the appropriate error code. """ next_size = 2 #try: while not self.terminated: bytes = self.read_from_connection(next_size) if not bytes and next_size > 0: raise IOError() message = None with self._lock: s = self.stream next_size = s.parser.send(bytes) if s.closing is not None: if not self.server_terminated: next_size = 2 self.close(s.closing.code, s.closing.reason) else: self.client_terminated = True raise IOError() elif s.errors: errors = s.errors[:] for error in s.errors: self.close(error.code, error.reason) s.errors.remove(error) raise IOError() elif s.has_message: if message_obj: message = s.message s.message = None else: message = str(s.message) s.message.data = None s.message = None for ping in s.pings: self.write_to_connection(s.pong(str(ping.data))) s.pings = [] s.pongs = [] if message is not None: return message
class WebSocket(object): def __init__(self, sock, environ, protocols=None, extensions=None): self.stream = Stream() self.protocols = protocols self.extensions = extensions self.environ = environ self.sock = sock self.sock.settimeout(30.0) self.client_terminated = False self.server_terminated = False self._lock = Semaphore() def close(self, code=1000, reason=''): """ Call this method to initiate the websocket connection closing by sending a close frame to the connected peer. Once this method is called, the server_terminated attribute is set. Calling this method several times is safe as the closing frame will be sent only the first time. @param code: status code describing why the connection is closed @param reason: a human readable message describing why the connection is closed """ if not self.server_terminated: self.server_terminated = True self.write_to_connection(self.stream.close(code=code, reason=reason)) self.close_connection() @property def terminated(self): """ Returns True if both the client and server have been marked as terminated. """ return self.client_terminated is True and self.server_terminated is True def write_to_connection(self, bytes): """ Writes the provided bytes to the underlying connection. @param bytes: data tio send out """ return self.sock.sendall(bytes) def read_from_connection(self, amount): """ Reads bytes from the underlying connection. @param amount: quantity to read (if possible) """ return self.sock.recv(amount) def close_connection(self): """ Shutdowns then closes the underlying connection. """ try: self.sock.shutdown(socket.SHUT_RDWR) finally: self.sock.close() def send(self, payload, binary=False): """ Sends the given payload out. If payload is some bytes or a bytearray, then it is sent as a single message not fragmented. If payload is a generator, each chunk is sent as part of fragmented message. @param payload: string, bytes, bytearray or a generator @param binary: if set, handles the payload as a binary message """ if isinstance(payload, basestring) or isinstance(payload, bytearray): if not binary: self.write_to_connection(self.stream.text_message(payload).single()) else: self.write_to_connection(self.stream.binary_message(payload).single()) elif type(payload) == types.GeneratorType: bytes = payload.next() first = True for chunk in payload: if not binary: self.write_to_connection(self.stream.text_message(bytes).fragment(first=first)) else: self.write_to_connection(self.stream.binary_message(payload).fragment(first=first)) bytes = chunk first = False if not binary: self.write_to_connection(self.stream.text_message(bytes).fragment(last=True)) else: self.write_to_connection(self.stream.text_message(bytes).fragment(last=True)) def receive(self, message_obj=False): """ Performs the operation of reading from the underlying connection in order to feed the stream of bytes. We start with a small size of two bytes to be read from the connection so that we can quickly parse an incoming frame header. Then the stream indicates whatever size must be read from the connection since it knows the frame payload length. Note that we perform some automatic opererations: * On a closing message, we respond with a closing message and finally close the connection * We respond to pings with pong messages. * Whenever an error is raised by the stream parsing, we initiate the closing of the connection with the appropriate error code. """ next_size = 2 #try: while not self.terminated: bytes = self.read_from_connection(next_size) if not bytes and next_size > 0: raise IOError() message = None with self._lock: s = self.stream next_size = s.parser.send(bytes) if s.closing is not None: if not self.server_terminated: next_size = 2 self.close(s.closing.code, s.closing.reason) else: self.client_terminated = True raise IOError() elif s.errors: errors = s.errors[:] for error in s.errors: self.close(error.code, error.reason) s.errors.remove(error) raise IOError() elif s.has_message: if message_obj: message = s.message s.message = None else: message = str(s.message) s.message.data = None s.message = None for ping in s.pings: self.write_to_connection(s.pong(str(ping.data))) s.pings = [] s.pongs = [] if message is not None: return message