2 websocket - WebSocket client library for Python
4 Copyright (C) 2010 Hiroki Ohtani(liris)
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 from urlparse import urlparse
45 websocket python client.
46 =========================
48 This version support only hybi-13.
49 Please see http://tools.ietf.org/html/rfc6455 for protocol.
53 # websocket supported version.
56 # closing frame status codes.
58 STATUS_GOING_AWAY = 1001
59 STATUS_PROTOCOL_ERROR = 1002
60 STATUS_UNSUPPORTED_DATA_TYPE = 1003
61 STATUS_STATUS_NOT_AVAILABLE = 1005
62 STATUS_ABNORMAL_CLOSED = 1006
63 STATUS_INVALID_PAYLOAD = 1007
64 STATUS_POLICY_VIOLATION = 1008
65 STATUS_MESSAGE_TOO_BIG = 1009
66 STATUS_INVALID_EXTENSION = 1010
67 STATUS_UNEXPECTED_CONDITION = 1011
68 STATUS_TLS_HANDSHAKE_ERROR = 1015
70 logger = logging.getLogger()
73 class WebSocketException(Exception):
75 websocket exeception class.
80 class WebSocketConnectionClosedException(WebSocketException):
82 If remote host closed the connection or some network error happened,
83 this exception will be raised.
87 class WebSocketTimeoutException(WebSocketException):
89 WebSocketTimeoutException will be raised at socket timeout during read/write data.
93 default_timeout = None
97 def enableTrace(tracable):
99 turn on/off the tracability.
101 tracable: boolean value. if set True, tracability is enabled.
104 traceEnabled = tracable
106 if not logger.handlers:
107 logger.addHandler(logging.StreamHandler())
108 logger.setLevel(logging.DEBUG)
111 def setdefaulttimeout(timeout):
113 Set the global timeout setting to connect.
115 timeout: default socket timeout time. This value is second.
117 global default_timeout
118 default_timeout = timeout
121 def getdefaulttimeout():
123 Return the global timeout setting(second) to connect.
125 return default_timeout
130 parse url and the result is tuple of
131 (hostname, port, resource path and the flag of secure mode)
136 raise ValueError("url is invalid")
138 scheme, url = url.split(":", 1)
140 parsed = urlparse(url, scheme="http")
142 hostname = parsed.hostname
144 raise ValueError("hostname is invalid")
153 elif scheme == "wss":
158 raise ValueError("scheme %s is invalid" % scheme)
161 resource = parsed.path
166 resource += "?" + parsed.query
168 return (hostname, port, resource, is_secure)
171 def create_connection(url, timeout=None, **options):
173 connect to url and return websocket object.
175 Connect to url and return the WebSocket object.
176 Passing optional timeout parameter will set the timeout on the socket.
177 If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used.
178 You can customize using 'options'.
179 If you set "header" list object, you can set your own custom header.
181 >>> conn = create_connection("ws://echo.websocket.org/",
182 ... header=["User-Agent: MyProgram",
183 ... "x-custom: header"])
186 timeout: socket timeout time. This value is integer.
187 if you set None for this value, it means "use default_timeout value"
189 options: current support option is only "header".
190 if you set header as dict value, the custom HTTP headers are added.
192 sockopt = options.get("sockopt", [])
193 sslopt = options.get("sslopt", {})
194 websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
195 websock.settimeout(timeout if timeout is not None else default_timeout)
196 websock.connect(url, **options)
199 _MAX_INTEGER = (1 << 32) -1
200 _AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
201 _MAX_CHAR_BYTE = (1<<8) -1
203 # ref. Websocket gets an update, and it breaks stuff.
204 # http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
207 def _create_sec_websocket_key():
209 return base64.encodestring(uid.bytes).strip()
212 _HEADERS_TO_CHECK = {
213 "upgrade": "websocket",
214 "connection": "upgrade",
221 see http://tools.ietf.org/html/rfc5234
222 and http://tools.ietf.org/html/rfc6455#section-5.2
225 # operation code values.
232 # available operation code value tuple
233 OPCODES = (OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
234 OPCODE_PING, OPCODE_PONG)
236 # opcode human readable string
239 OPCODE_BINARY: "binary",
240 OPCODE_CLOSE: "close",
245 # data length threashold.
250 def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
251 opcode=OPCODE_TEXT, mask=1, data=""):
253 Constructor for ABNF.
254 please check RFC for arguments.
263 self.get_mask_key = os.urandom
266 def create_frame(data, opcode):
268 create frame to send text, binary and other data.
270 data: data to send. This is string value(byte array).
271 if opcode is OPCODE_TEXT and this value is uniocde,
272 data value is conveted into unicode string, automatically.
274 opcode: operation code. please see OPCODE_XXX.
276 if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode):
277 data = data.encode("utf-8")
278 # mask must be set if send data from client
279 return ABNF(1, 0, 0, 0, opcode, 1, data)
283 format this object to string(byte array) to send data to server.
285 if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
286 raise ValueError("not 0 or 1")
287 if self.opcode not in ABNF.OPCODES:
288 raise ValueError("Invalid OPCODE")
289 length = len(self.data)
290 if length >= ABNF.LENGTH_63:
291 raise ValueError("data is too long")
293 frame_header = chr(self.fin << 7
294 | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
296 if length < ABNF.LENGTH_7:
297 frame_header += chr(self.mask << 7 | length)
298 elif length < ABNF.LENGTH_16:
299 frame_header += chr(self.mask << 7 | 0x7e)
300 frame_header += struct.pack("!H", length)
302 frame_header += chr(self.mask << 7 | 0x7f)
303 frame_header += struct.pack("!Q", length)
306 return frame_header + self.data
308 mask_key = self.get_mask_key(4)
309 return frame_header + self._get_masked(mask_key)
311 def _get_masked(self, mask_key):
312 s = ABNF.mask(mask_key, self.data)
313 return mask_key + "".join(s)
316 def mask(mask_key, data):
318 mask or unmask data. Just do xor for each byte
320 mask_key: 4 byte string(byte).
322 data: data to mask/unmask.
324 _m = array.array("B", mask_key)
325 _d = array.array("B", data)
326 for i in xrange(len(_d)):
331 class WebSocket(object):
333 Low level WebSocket interface.
334 This class is based on
335 The WebSocket protocol draft-hixie-thewebsocketprotocol-76
336 http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
338 We can connect to the websocket server and send/recieve data.
339 The following example is a echo client.
342 >>> ws = websocket.WebSocket()
343 >>> ws.connect("ws://echo.websocket.org")
344 >>> ws.send("Hello, Server")
349 get_mask_key: a callable to produce new mask keys, see the set_mask_key
350 function's docstring for more details
351 sockopt: values for socket.setsockopt.
352 sockopt must be tuple and each element is argument of sock.setscokopt.
353 sslopt: dict object for ssl socket option.
356 def __init__(self, get_mask_key=None, sockopt=None, sslopt=None):
358 Initalize WebSocket object.
364 self.connected = False
365 self.sock = socket.socket()
367 self.sock.setsockopt(*opts)
369 self.get_mask_key = get_mask_key
372 return self.sock.fileno()
374 def set_mask_key(self, func):
376 set function to create musk key. You can custumize mask key generator.
377 Mainly, this is for testing purpose.
379 func: callable object. the fuct must 1 argument as integer.
380 The argument means length of mask key.
381 This func must be return string(byte array),
382 which length is argument specified.
384 self.get_mask_key = func
386 def gettimeout(self):
388 Get the websocket timeout(second).
390 return self.sock.gettimeout()
392 def settimeout(self, timeout):
394 Set the timeout to the websocket.
396 timeout: timeout time(second).
398 self.sock.settimeout(timeout)
400 timeout = property(gettimeout, settimeout)
402 def connect(self, url, **options):
404 Connect to url. url is websocket url scheme. ie. ws://host:port/resource
405 You can customize using 'options'.
406 If you set "header" dict object, you can set your own custom header.
409 >>> ws.connect("ws://echo.websocket.org/",
410 ... header={"User-Agent: MyProgram",
411 ... "x-custom: header"})
413 timeout: socket timeout time. This value is integer.
414 if you set None for this value,
415 it means "use default_timeout value"
417 options: current support option is only "header".
418 if you set header as dict value,
419 the custom HTTP headers are added.
422 hostname, port, resource, is_secure = _parse_url(url)
423 # TODO: we need to support proxy
424 self.sock.connect((hostname, port))
427 if self.sslopt is None:
431 self.sock = ssl.wrap_socket(self.sock, **sslopt)
433 raise WebSocketException("SSL not available.")
435 self._handshake(hostname, port, resource, **options)
437 def _handshake(self, host, port, resource, **options):
440 headers.append("GET %s HTTP/1.1" % resource)
441 headers.append("Upgrade: websocket")
442 headers.append("Connection: Upgrade")
446 hostport = "%s:%d" % (host, port)
447 headers.append("Host: %s" % hostport)
449 if "origin" in options:
450 headers.append("Origin: %s" % options["origin"])
452 headers.append("Origin: http://%s" % hostport)
454 key = _create_sec_websocket_key()
455 headers.append("Sec-WebSocket-Key: %s" % key)
456 headers.append("Sec-WebSocket-Version: %s" % VERSION)
457 if "header" in options:
458 headers.extend(options["header"])
463 header_str = "\r\n".join(headers)
464 self._send(header_str)
466 logger.debug("--- request header ---")
467 logger.debug(header_str)
468 logger.debug("-----------------------")
470 status, resp_headers = self._read_headers()
473 raise WebSocketException("Handshake Status %d" % status)
475 success = self._validate_header(resp_headers, key)
478 raise WebSocketException("Invalid WebSocket Header")
480 self.connected = True
482 def _validate_header(self, headers, key):
483 for k, v in _HEADERS_TO_CHECK.iteritems():
484 r = headers.get(k, None)
491 result = headers.get("sec-websocket-accept", None)
494 result = result.lower()
496 value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
497 hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower()
498 return hashed == result
500 def _read_headers(self):
504 logger.debug("--- response header ---")
507 line = self._recv_line()
514 status_info = line.split(" ", 2)
515 status = int(status_info[1])
517 kv = line.split(":", 1)
520 headers[key.lower()] = value.strip().lower()
522 raise WebSocketException("Invalid header")
525 logger.debug("-----------------------")
527 return status, headers
529 def send(self, payload, opcode=ABNF.OPCODE_TEXT):
531 Send the data as string.
533 payload: Payload must be utf-8 string or unicoce,
534 if the opcode is OPCODE_TEXT.
535 Otherwise, it must be string(byte array)
537 opcode: operation code to send. Please see OPCODE_XXX.
539 frame = ABNF.create_frame(payload, opcode)
540 if self.get_mask_key:
541 frame.get_mask_key = self.get_mask_key
542 data = frame.format()
544 logger.debug("send: " + repr(data))
549 def send_binary(self, payload):
550 return self.send(payload, ABNF.OPCODE_BINARY)
552 def ping(self, payload=""):
556 payload: data payload to send server.
558 self.send(payload, ABNF.OPCODE_PING)
560 def pong(self, payload):
564 payload: data payload to send server.
566 self.send(payload, ABNF.OPCODE_PONG)
570 Receive string data(byte array) from the server.
572 return value: string(byte array) value.
574 opcode, data = self.recv_data()
579 Recieve data with operation code.
581 return value: tuple of operation code and string(byte array) value.
584 frame = self.recv_frame()
587 # 'NoneType' object has no attribute 'opcode'
588 raise WebSocketException("Not a valid frame %s" % frame)
589 elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
590 return (frame.opcode, frame.data)
591 elif frame.opcode == ABNF.OPCODE_CLOSE:
593 return (frame.opcode, None)
594 elif frame.opcode == ABNF.OPCODE_PING:
595 self.pong(frame.data)
597 def recv_frame(self):
599 recieve data as frame from server.
601 return value: ABNF frame object.
603 header_bytes = self._recv_strict(2)
606 b1 = ord(header_bytes[0])
612 b2 = ord(header_bytes[1])
618 length_data = self._recv_strict(2)
619 length = struct.unpack("!H", length_data)[0]
621 length_data = self._recv_strict(8)
622 length = struct.unpack("!Q", length_data)[0]
626 mask_key = self._recv_strict(4)
627 data = self._recv_strict(length)
629 recieved = header_bytes + length_data + mask_key + data
630 logger.debug("recv: " + repr(recieved))
633 data = ABNF.mask(mask_key, data)
635 frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, mask, data)
638 def send_close(self, status=STATUS_NORMAL, reason=""):
640 send close data to the server.
642 status: status code to send. see STATUS_XXX.
644 reason: the reason to close. This must be string.
646 if status < 0 or status >= ABNF.LENGTH_16:
647 raise ValueError("code is invalid range")
648 self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
650 def close(self, status=STATUS_NORMAL, reason=""):
652 Close Websocket object
654 status: status code to send. see STATUS_XXX.
656 reason: the reason to close. This must be string.
659 if status < 0 or status >= ABNF.LENGTH_16:
660 raise ValueError("code is invalid range")
663 self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
664 timeout = self.sock.gettimeout()
665 self.sock.settimeout(3)
667 frame = self.recv_frame()
668 if logger.isEnabledFor(logging.ERROR):
669 recv_status = struct.unpack("!H", frame.data)[0]
670 if recv_status != STATUS_NORMAL:
671 logger.error("close status: " + repr(recv_status))
674 self.sock.settimeout(timeout)
675 self.sock.shutdown(socket.SHUT_RDWR)
678 self._closeInternal()
680 def _closeInternal(self):
681 self.connected = False
684 def _send(self, data):
686 return self.sock.send(data)
687 except socket.timeout as e:
688 raise WebSocketTimeoutException(e.message)
689 except Exception as e:
690 if "timed out" in e.message:
691 raise WebSocketTimeoutException(e.message)
695 def _recv(self, bufsize):
697 bytes = self.sock.recv(bufsize)
699 raise WebSocketConnectionClosedException()
701 except socket.timeout as e:
702 raise WebSocketTimeoutException(e.message)
703 except Exception as e:
704 if "timed out" in e.message:
705 raise WebSocketTimeoutException(e.message)
710 def _recv_strict(self, bufsize):
714 bytes += self._recv(remaining)
715 remaining = bufsize - len(bytes)
719 def _recv_line(self):
729 class WebSocketApp(object):
731 Higher level of APIs are provided.
732 The interface is like JavaScript WebSocket object.
734 def __init__(self, url, header=[],
735 on_open=None, on_message=None, on_error=None,
736 on_close=None, keep_running=True, get_mask_key=None):
739 header: custom header for websocket handshake.
740 on_open: callable object which is called at opening websocket.
741 this function has one argument. The arugment is this class object.
742 on_message: callbale object which is called when recieved data.
743 on_message has 2 arguments.
744 The 1st arugment is this class object.
745 The passing 2nd arugment is utf-8 string which we get from the server.
746 on_error: callable object which is called when we get error.
747 on_error has 2 arguments.
748 The 1st arugment is this class object.
749 The passing 2nd arugment is exception object.
750 on_close: callable object which is called when closed the connection.
751 this function has one argument. The arugment is this class object.
752 keep_running: a boolean flag indicating whether the app's main loop should
753 keep running, defaults to True
754 get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's
755 docstring for more information
759 self.on_open = on_open
760 self.on_message = on_message
761 self.on_error = on_error
762 self.on_close = on_close
763 self.keep_running = keep_running
764 self.get_mask_key = get_mask_key
767 def send(self, data, opcode=ABNF.OPCODE_TEXT):
770 data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode.
771 opcode: operation code of data. default is OPCODE_TEXT.
773 if self.sock.send(data, opcode) == 0:
774 raise WebSocketConnectionClosedException()
778 close websocket connection.
780 self.keep_running = False
783 def _send_ping(self, interval):
784 while self.keep_running:
788 def run_forever(self, sockopt=None, sslopt=None, ping_interval=0):
790 run event loop for WebSocket framework.
791 This loop is infinite loop and is alive during websocket is available.
792 sockopt: values for socket.setsockopt.
793 sockopt must be tuple and each element is argument of sock.setscokopt.
794 sslopt: ssl socket optional dict.
795 ping_interval: automatically send "ping" command every specified period(second)
796 if set to 0, not send automatically.
803 raise WebSocketException("socket is already opened")
807 self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt)
808 self.sock.connect(self.url, header=self.header)
809 self._callback(self.on_open)
812 thread = threading.Thread(target=self._send_ping, args=(ping_interval,))
813 thread.setDaemon(True)
816 while self.keep_running:
817 data = self.sock.recv()
820 self._callback(self.on_message, data)
822 self._callback(self.on_error, e)
827 self._callback(self.on_close)
830 def _callback(self, callback, *args):
833 callback(self, *args)
836 if logger.isEnabledFor(logging.DEBUG):
837 _, _, tb = sys_exc_info()
838 traceback.print_tb(tb)
841 if __name__ == "__main__":
843 ws = create_connection("ws://echo.websocket.org/")
844 print("Sending 'Hello, World'...")
845 ws.send("Hello, World")
847 print("Receiving...")
849 print("Received '%s'" % result)