1""" 2websocket - WebSocket client library for Python 3 4Copyright (C) 2010 Hiroki Ohtani(liris) 5 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. 10 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. 15 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 19 20""" 21 22 23import socket 24 25try: 26 import ssl 27 from ssl import SSLError 28 HAVE_SSL = True 29except ImportError: 30 # dummy class of SSLError for ssl none-support environment. 31 class SSLError(Exception): 32 pass 33 34 HAVE_SSL = False 35 36from urlparse import urlparse 37import os 38import array 39import struct 40import uuid 41import hashlib 42import base64 43import threading 44import time 45import logging 46import traceback 47import sys 48 49""" 50websocket python client. 51========================= 52 53This version support only hybi-13. 54Please see http://tools.ietf.org/html/rfc6455 for protocol. 55""" 56 57 58# websocket supported version. 59VERSION = 13 60 61# closing frame status codes. 62STATUS_NORMAL = 1000 63STATUS_GOING_AWAY = 1001 64STATUS_PROTOCOL_ERROR = 1002 65STATUS_UNSUPPORTED_DATA_TYPE = 1003 66STATUS_STATUS_NOT_AVAILABLE = 1005 67STATUS_ABNORMAL_CLOSED = 1006 68STATUS_INVALID_PAYLOAD = 1007 69STATUS_POLICY_VIOLATION = 1008 70STATUS_MESSAGE_TOO_BIG = 1009 71STATUS_INVALID_EXTENSION = 1010 72STATUS_UNEXPECTED_CONDITION = 1011 73STATUS_TLS_HANDSHAKE_ERROR = 1015 74 75logger = logging.getLogger() 76 77 78class WebSocketException(Exception): 79 """ 80 websocket exeception class. 81 """ 82 pass 83 84 85class WebSocketConnectionClosedException(WebSocketException): 86 """ 87 If remote host closed the connection or some network error happened, 88 this exception will be raised. 89 """ 90 pass 91 92class WebSocketTimeoutException(WebSocketException): 93 """ 94 WebSocketTimeoutException will be raised at socket timeout during read/write data. 95 """ 96 pass 97 98default_timeout = None 99traceEnabled = False 100 101 102def enableTrace(tracable): 103 """ 104 turn on/off the tracability. 105 106 tracable: boolean value. if set True, tracability is enabled. 107 """ 108 global traceEnabled 109 traceEnabled = tracable 110 if tracable: 111 if not logger.handlers: 112 logger.addHandler(logging.StreamHandler()) 113 logger.setLevel(logging.DEBUG) 114 115 116def setdefaulttimeout(timeout): 117 """ 118 Set the global timeout setting to connect. 119 120 timeout: default socket timeout time. This value is second. 121 """ 122 global default_timeout 123 default_timeout = timeout 124 125 126def getdefaulttimeout(): 127 """ 128 Return the global timeout setting(second) to connect. 129 """ 130 return default_timeout 131 132 133def _parse_url(url): 134 """ 135 parse url and the result is tuple of 136 (hostname, port, resource path and the flag of secure mode) 137 138 url: url string. 139 """ 140 if ":" not in url: 141 raise ValueError("url is invalid") 142 143 scheme, url = url.split(":", 1) 144 145 parsed = urlparse(url, scheme="http") 146 if parsed.hostname: 147 hostname = parsed.hostname 148 else: 149 raise ValueError("hostname is invalid") 150 port = 0 151 if parsed.port: 152 port = parsed.port 153 154 is_secure = False 155 if scheme == "ws": 156 if not port: 157 port = 80 158 elif scheme == "wss": 159 is_secure = True 160 if not port: 161 port = 443 162 else: 163 raise ValueError("scheme %s is invalid" % scheme) 164 165 if parsed.path: 166 resource = parsed.path 167 else: 168 resource = "/" 169 170 if parsed.query: 171 resource += "?" + parsed.query 172 173 return (hostname, port, resource, is_secure) 174 175 176def create_connection(url, timeout=None, **options): 177 """ 178 connect to url and return websocket object. 179 180 Connect to url and return the WebSocket object. 181 Passing optional timeout parameter will set the timeout on the socket. 182 If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used. 183 You can customize using 'options'. 184 If you set "header" list object, you can set your own custom header. 185 186 >>> conn = create_connection("ws://echo.websocket.org/", 187 ... header=["User-Agent: MyProgram", 188 ... "x-custom: header"]) 189 190 191 timeout: socket timeout time. This value is integer. 192 if you set None for this value, it means "use default_timeout value" 193 194 options: current support option is only "header". 195 if you set header as dict value, the custom HTTP headers are added. 196 """ 197 sockopt = options.get("sockopt", []) 198 sslopt = options.get("sslopt", {}) 199 websock = WebSocket(sockopt=sockopt, sslopt=sslopt) 200 websock.settimeout(timeout if timeout is not None else default_timeout) 201 websock.connect(url, **options) 202 return websock 203 204_MAX_INTEGER = (1 << 32) -1 205_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) 206_MAX_CHAR_BYTE = (1<<8) -1 207 208# ref. Websocket gets an update, and it breaks stuff. 209# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html 210 211 212def _create_sec_websocket_key(): 213 uid = uuid.uuid4() 214 return base64.encodestring(uid.bytes).strip() 215 216 217_HEADERS_TO_CHECK = { 218 "upgrade": "websocket", 219 "connection": "upgrade", 220 } 221 222 223class ABNF(object): 224 """ 225 ABNF frame class. 226 see http://tools.ietf.org/html/rfc5234 227 and http://tools.ietf.org/html/rfc6455#section-5.2 228 """ 229 230 # operation code values. 231 OPCODE_CONT = 0x0 232 OPCODE_TEXT = 0x1 233 OPCODE_BINARY = 0x2 234 OPCODE_CLOSE = 0x8 235 OPCODE_PING = 0x9 236 OPCODE_PONG = 0xa 237 238 # available operation code value tuple 239 OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, 240 OPCODE_PING, OPCODE_PONG) 241 242 # opcode human readable string 243 OPCODE_MAP = { 244 OPCODE_CONT: "cont", 245 OPCODE_TEXT: "text", 246 OPCODE_BINARY: "binary", 247 OPCODE_CLOSE: "close", 248 OPCODE_PING: "ping", 249 OPCODE_PONG: "pong" 250 } 251 252 # data length threashold. 253 LENGTH_7 = 0x7d 254 LENGTH_16 = 1 << 16 255 LENGTH_63 = 1 << 63 256 257 def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0, 258 opcode=OPCODE_TEXT, mask=1, data=""): 259 """ 260 Constructor for ABNF. 261 please check RFC for arguments. 262 """ 263 self.fin = fin 264 self.rsv1 = rsv1 265 self.rsv2 = rsv2 266 self.rsv3 = rsv3 267 self.opcode = opcode 268 self.mask = mask 269 self.data = data 270 self.get_mask_key = os.urandom 271 272 def __str__(self): 273 return "fin=" + str(self.fin) \ 274 + " opcode=" + str(self.opcode) \ 275 + " data=" + str(self.data) 276 277 @staticmethod 278 def create_frame(data, opcode): 279 """ 280 create frame to send text, binary and other data. 281 282 data: data to send. This is string value(byte array). 283 if opcode is OPCODE_TEXT and this value is uniocde, 284 data value is conveted into unicode string, automatically. 285 286 opcode: operation code. please see OPCODE_XXX. 287 """ 288 if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode): 289 data = data.encode("utf-8") 290 # mask must be set if send data from client 291 return ABNF(1, 0, 0, 0, opcode, 1, data) 292 293 def format(self): 294 """ 295 format this object to string(byte array) to send data to server. 296 """ 297 if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]): 298 raise ValueError("not 0 or 1") 299 if self.opcode not in ABNF.OPCODES: 300 raise ValueError("Invalid OPCODE") 301 length = len(self.data) 302 if length >= ABNF.LENGTH_63: 303 raise ValueError("data is too long") 304 305 frame_header = chr(self.fin << 7 306 | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 307 | self.opcode) 308 if length < ABNF.LENGTH_7: 309 frame_header += chr(self.mask << 7 | length) 310 elif length < ABNF.LENGTH_16: 311 frame_header += chr(self.mask << 7 | 0x7e) 312 frame_header += struct.pack("!H", length) 313 else: 314 frame_header += chr(self.mask << 7 | 0x7f) 315 frame_header += struct.pack("!Q", length) 316 317 if not self.mask: 318 return frame_header + self.data 319 else: 320 mask_key = self.get_mask_key(4) 321 return frame_header + self._get_masked(mask_key) 322 323 def _get_masked(self, mask_key): 324 s = ABNF.mask(mask_key, self.data) 325 return mask_key + "".join(s) 326 327 @staticmethod 328 def mask(mask_key, data): 329 """ 330 mask or unmask data. Just do xor for each byte 331 332 mask_key: 4 byte string(byte). 333 334 data: data to mask/unmask. 335 """ 336 _m = array.array("B", mask_key) 337 _d = array.array("B", data) 338 for i in xrange(len(_d)): 339 _d[i] ^= _m[i % 4] 340 return _d.tostring() 341 342 343class WebSocket(object): 344 """ 345 Low level WebSocket interface. 346 This class is based on 347 The WebSocket protocol draft-hixie-thewebsocketprotocol-76 348 http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 349 350 We can connect to the websocket server and send/recieve data. 351 The following example is a echo client. 352 353 >>> import websocket 354 >>> ws = websocket.WebSocket() 355 >>> ws.connect("ws://echo.websocket.org") 356 >>> ws.send("Hello, Server") 357 >>> ws.recv() 358 'Hello, Server' 359 >>> ws.close() 360 361 get_mask_key: a callable to produce new mask keys, see the set_mask_key 362 function's docstring for more details 363 sockopt: values for socket.setsockopt. 364 sockopt must be tuple and each element is argument of sock.setscokopt. 365 sslopt: dict object for ssl socket option. 366 """ 367 368 def __init__(self, get_mask_key=None, sockopt=None, sslopt=None): 369 """ 370 Initalize WebSocket object. 371 """ 372 if sockopt is None: 373 sockopt = [] 374 if sslopt is None: 375 sslopt = {} 376 self.connected = False 377 self.sock = socket.socket() 378 for opts in sockopt: 379 self.sock.setsockopt(*opts) 380 self.sslopt = sslopt 381 self.get_mask_key = get_mask_key 382 # Buffers over the packets from the layer beneath until desired amount 383 # bytes of bytes are received. 384 self._recv_buffer = [] 385 # These buffer over the build-up of a single frame. 386 self._frame_header = None 387 self._frame_length = None 388 self._frame_mask = None 389 self._cont_data = None 390 391 def fileno(self): 392 return self.sock.fileno() 393 394 def set_mask_key(self, func): 395 """ 396 set function to create musk key. You can custumize mask key generator. 397 Mainly, this is for testing purpose. 398 399 func: callable object. the fuct must 1 argument as integer. 400 The argument means length of mask key. 401 This func must be return string(byte array), 402 which length is argument specified. 403 """ 404 self.get_mask_key = func 405 406 def gettimeout(self): 407 """ 408 Get the websocket timeout(second). 409 """ 410 return self.sock.gettimeout() 411 412 def settimeout(self, timeout): 413 """ 414 Set the timeout to the websocket. 415 416 timeout: timeout time(second). 417 """ 418 self.sock.settimeout(timeout) 419 420 timeout = property(gettimeout, settimeout) 421 422 def connect(self, url, **options): 423 """ 424 Connect to url. url is websocket url scheme. ie. ws://host:port/resource 425 You can customize using 'options'. 426 If you set "header" dict object, you can set your own custom header. 427 428 >>> ws = WebSocket() 429 >>> ws.connect("ws://echo.websocket.org/", 430 ... header={"User-Agent: MyProgram", 431 ... "x-custom: header"}) 432 433 timeout: socket timeout time. This value is integer. 434 if you set None for this value, 435 it means "use default_timeout value" 436 437 options: current support option is only "header". 438 if you set header as dict value, 439 the custom HTTP headers are added. 440 441 """ 442 hostname, port, resource, is_secure = _parse_url(url) 443 # TODO: we need to support proxy 444 self.sock.connect((hostname, port)) 445 if is_secure: 446 if HAVE_SSL: 447 if self.sslopt is None: 448 sslopt = {} 449 else: 450 sslopt = self.sslopt 451 self.sock = ssl.wrap_socket(self.sock, **sslopt) 452 else: 453 raise WebSocketException("SSL not available.") 454 455 self._handshake(hostname, port, resource, **options) 456 457 def _handshake(self, host, port, resource, **options): 458 sock = self.sock 459 headers = [] 460 headers.append("GET %s HTTP/1.1" % resource) 461 headers.append("Upgrade: websocket") 462 headers.append("Connection: Upgrade") 463 if port == 80: 464 hostport = host 465 else: 466 hostport = "%s:%d" % (host, port) 467 headers.append("Host: %s" % hostport) 468 469 if "origin" in options: 470 headers.append("Origin: %s" % options["origin"]) 471 else: 472 headers.append("Origin: http://%s" % hostport) 473 474 key = _create_sec_websocket_key() 475 headers.append("Sec-WebSocket-Key: %s" % key) 476 headers.append("Sec-WebSocket-Version: %s" % VERSION) 477 if "header" in options: 478 headers.extend(options["header"]) 479 480 headers.append("") 481 headers.append("") 482 483 header_str = "\r\n".join(headers) 484 self._send(header_str) 485 if traceEnabled: 486 logger.debug("--- request header ---") 487 logger.debug(header_str) 488 logger.debug("-----------------------") 489 490 status, resp_headers = self._read_headers() 491 if status != 101: 492 self.close() 493 raise WebSocketException("Handshake Status %d" % status) 494 495 success = self._validate_header(resp_headers, key) 496 if not success: 497 self.close() 498 raise WebSocketException("Invalid WebSocket Header") 499 500 self.connected = True 501 502 def _validate_header(self, headers, key): 503 for k, v in _HEADERS_TO_CHECK.iteritems(): 504 r = headers.get(k, None) 505 if not r: 506 return False 507 r = r.lower() 508 if v != r: 509 return False 510 511 result = headers.get("sec-websocket-accept", None) 512 if not result: 513 return False 514 result = result.lower() 515 516 value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 517 hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower() 518 return hashed == result 519 520 def _read_headers(self): 521 status = None 522 headers = {} 523 if traceEnabled: 524 logger.debug("--- response header ---") 525 526 while True: 527 line = self._recv_line() 528 if line == "\r\n": 529 break 530 line = line.strip() 531 if traceEnabled: 532 logger.debug(line) 533 if not status: 534 status_info = line.split(" ", 2) 535 status = int(status_info[1]) 536 else: 537 kv = line.split(":", 1) 538 if len(kv) == 2: 539 key, value = kv 540 headers[key.lower()] = value.strip().lower() 541 else: 542 raise WebSocketException("Invalid header") 543 544 if traceEnabled: 545 logger.debug("-----------------------") 546 547 return status, headers 548 549 def send(self, payload, opcode=ABNF.OPCODE_TEXT): 550 """ 551 Send the data as string. 552 553 payload: Payload must be utf-8 string or unicoce, 554 if the opcode is OPCODE_TEXT. 555 Otherwise, it must be string(byte array) 556 557 opcode: operation code to send. Please see OPCODE_XXX. 558 """ 559 frame = ABNF.create_frame(payload, opcode) 560 if self.get_mask_key: 561 frame.get_mask_key = self.get_mask_key 562 data = frame.format() 563 length = len(data) 564 if traceEnabled: 565 logger.debug("send: " + repr(data)) 566 while data: 567 l = self._send(data) 568 data = data[l:] 569 return length 570 571 def send_binary(self, payload): 572 return self.send(payload, ABNF.OPCODE_BINARY) 573 574 def ping(self, payload=""): 575 """ 576 send ping data. 577 578 payload: data payload to send server. 579 """ 580 self.send(payload, ABNF.OPCODE_PING) 581 582 def pong(self, payload): 583 """ 584 send pong data. 585 586 payload: data payload to send server. 587 """ 588 self.send(payload, ABNF.OPCODE_PONG) 589 590 def recv(self): 591 """ 592 Receive string data(byte array) from the server. 593 594 return value: string(byte array) value. 595 """ 596 opcode, data = self.recv_data() 597 return data 598 599 def recv_data(self): 600 """ 601 Recieve data with operation code. 602 603 return value: tuple of operation code and string(byte array) value. 604 """ 605 while True: 606 frame = self.recv_frame() 607 if not frame: 608 # handle error: 609 # 'NoneType' object has no attribute 'opcode' 610 raise WebSocketException("Not a valid frame %s" % frame) 611 elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT): 612 if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data: 613 raise WebSocketException("Illegal frame") 614 if self._cont_data: 615 self._cont_data[1] += frame.data 616 else: 617 self._cont_data = [frame.opcode, frame.data] 618 619 if frame.fin: 620 data = self._cont_data 621 self._cont_data = None 622 return data 623 elif frame.opcode == ABNF.OPCODE_CLOSE: 624 self.send_close() 625 return (frame.opcode, None) 626 elif frame.opcode == ABNF.OPCODE_PING: 627 self.pong(frame.data) 628 629 def recv_frame(self): 630 """ 631 recieve data as frame from server. 632 633 return value: ABNF frame object. 634 """ 635 # Header 636 if self._frame_header is None: 637 self._frame_header = self._recv_strict(2) 638 b1 = ord(self._frame_header[0]) 639 fin = b1 >> 7 & 1 640 rsv1 = b1 >> 6 & 1 641 rsv2 = b1 >> 5 & 1 642 rsv3 = b1 >> 4 & 1 643 opcode = b1 & 0xf 644 b2 = ord(self._frame_header[1]) 645 has_mask = b2 >> 7 & 1 646 # Frame length 647 if self._frame_length is None: 648 length_bits = b2 & 0x7f 649 if length_bits == 0x7e: 650 length_data = self._recv_strict(2) 651 self._frame_length = struct.unpack("!H", length_data)[0] 652 elif length_bits == 0x7f: 653 length_data = self._recv_strict(8) 654 self._frame_length = struct.unpack("!Q", length_data)[0] 655 else: 656 self._frame_length = length_bits 657 # Mask 658 if self._frame_mask is None: 659 self._frame_mask = self._recv_strict(4) if has_mask else "" 660 # Payload 661 payload = self._recv_strict(self._frame_length) 662 if has_mask: 663 payload = ABNF.mask(self._frame_mask, payload) 664 # Reset for next frame 665 self._frame_header = None 666 self._frame_length = None 667 self._frame_mask = None 668 return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload) 669 670 671 def send_close(self, status=STATUS_NORMAL, reason=""): 672 """ 673 send close data to the server. 674 675 status: status code to send. see STATUS_XXX. 676 677 reason: the reason to close. This must be string. 678 """ 679 if status < 0 or status >= ABNF.LENGTH_16: 680 raise ValueError("code is invalid range") 681 self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) 682 683 def close(self, status=STATUS_NORMAL, reason=""): 684 """ 685 Close Websocket object 686 687 status: status code to send. see STATUS_XXX. 688 689 reason: the reason to close. This must be string. 690 """ 691 if self.connected: 692 if status < 0 or status >= ABNF.LENGTH_16: 693 raise ValueError("code is invalid range") 694 695 try: 696 self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) 697 timeout = self.sock.gettimeout() 698 self.sock.settimeout(3) 699 try: 700 frame = self.recv_frame() 701 if logger.isEnabledFor(logging.ERROR): 702 recv_status = struct.unpack("!H", frame.data)[0] 703 if recv_status != STATUS_NORMAL: 704 logger.error("close status: " + repr(recv_status)) 705 except: 706 pass 707 self.sock.settimeout(timeout) 708 self.sock.shutdown(socket.SHUT_RDWR) 709 except: 710 pass 711 self._closeInternal() 712 713 def _closeInternal(self): 714 self.connected = False 715 self.sock.close() 716 717 def _send(self, data): 718 try: 719 return self.sock.send(data) 720 except socket.timeout as e: 721 raise WebSocketTimeoutException(e.message) 722 except Exception as e: 723 if "timed out" in e.message: 724 raise WebSocketTimeoutException(e.message) 725 else: 726 raise e 727 728 def _recv(self, bufsize): 729 try: 730 bytes = self.sock.recv(bufsize) 731 except socket.timeout as e: 732 raise WebSocketTimeoutException(e.message) 733 except SSLError as e: 734 if e.message == "The read operation timed out": 735 raise WebSocketTimeoutException(e.message) 736 else: 737 raise 738 if not bytes: 739 raise WebSocketConnectionClosedException() 740 return bytes 741 742 743 def _recv_strict(self, bufsize): 744 shortage = bufsize - sum(len(x) for x in self._recv_buffer) 745 while shortage > 0: 746 bytes = self._recv(shortage) 747 self._recv_buffer.append(bytes) 748 shortage -= len(bytes) 749 unified = "".join(self._recv_buffer) 750 if shortage == 0: 751 self._recv_buffer = [] 752 return unified 753 else: 754 self._recv_buffer = [unified[bufsize:]] 755 return unified[:bufsize] 756 757 758 def _recv_line(self): 759 line = [] 760 while True: 761 c = self._recv(1) 762 line.append(c) 763 if c == "\n": 764 break 765 return "".join(line) 766 767 768class WebSocketApp(object): 769 """ 770 Higher level of APIs are provided. 771 The interface is like JavaScript WebSocket object. 772 """ 773 def __init__(self, url, header=[], 774 on_open=None, on_message=None, on_error=None, 775 on_close=None, keep_running=True, get_mask_key=None): 776 """ 777 url: websocket url. 778 header: custom header for websocket handshake. 779 on_open: callable object which is called at opening websocket. 780 this function has one argument. The arugment is this class object. 781 on_message: callbale object which is called when recieved data. 782 on_message has 2 arguments. 783 The 1st arugment is this class object. 784 The passing 2nd arugment is utf-8 string which we get from the server. 785 on_error: callable object which is called when we get error. 786 on_error has 2 arguments. 787 The 1st arugment is this class object. 788 The passing 2nd arugment is exception object. 789 on_close: callable object which is called when closed the connection. 790 this function has one argument. The arugment is this class object. 791 keep_running: a boolean flag indicating whether the app's main loop should 792 keep running, defaults to True 793 get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's 794 docstring for more information 795 """ 796 self.url = url 797 self.header = header 798 self.on_open = on_open 799 self.on_message = on_message 800 self.on_error = on_error 801 self.on_close = on_close 802 self.keep_running = keep_running 803 self.get_mask_key = get_mask_key 804 self.sock = None 805 806 def send(self, data, opcode=ABNF.OPCODE_TEXT): 807 """ 808 send message. 809 data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode. 810 opcode: operation code of data. default is OPCODE_TEXT. 811 """ 812 if self.sock.send(data, opcode) == 0: 813 raise WebSocketConnectionClosedException() 814 815 def close(self): 816 """ 817 close websocket connection. 818 """ 819 self.keep_running = False 820 self.sock.close() 821 822 def _send_ping(self, interval): 823 while True: 824 for i in range(interval): 825 time.sleep(1) 826 if not self.keep_running: 827 return 828 self.sock.ping() 829 830 def run_forever(self, sockopt=None, sslopt=None, ping_interval=0): 831 """ 832 run event loop for WebSocket framework. 833 This loop is infinite loop and is alive during websocket is available. 834 sockopt: values for socket.setsockopt. 835 sockopt must be tuple and each element is argument of sock.setscokopt. 836 sslopt: ssl socket optional dict. 837 ping_interval: automatically send "ping" command every specified period(second) 838 if set to 0, not send automatically. 839 """ 840 if sockopt is None: 841 sockopt = [] 842 if sslopt is None: 843 sslopt = {} 844 if self.sock: 845 raise WebSocketException("socket is already opened") 846 thread = None 847 848 try: 849 self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt) 850 self.sock.settimeout(default_timeout) 851 self.sock.connect(self.url, header=self.header) 852 self._callback(self.on_open) 853 854 if ping_interval: 855 thread = threading.Thread(target=self._send_ping, args=(ping_interval,)) 856 thread.setDaemon(True) 857 thread.start() 858 859 while self.keep_running: 860 data = self.sock.recv() 861 if data is None: 862 break 863 self._callback(self.on_message, data) 864 except Exception, e: 865 self._callback(self.on_error, e) 866 finally: 867 if thread: 868 self.keep_running = False 869 self.sock.close() 870 self._callback(self.on_close) 871 self.sock = None 872 873 def _callback(self, callback, *args): 874 if callback: 875 try: 876 callback(self, *args) 877 except Exception, e: 878 logger.error(e) 879 if logger.isEnabledFor(logging.DEBUG): 880 _, _, tb = sys.exc_info() 881 traceback.print_tb(tb) 882 883 884if __name__ == "__main__": 885 enableTrace(True) 886 ws = create_connection("ws://echo.websocket.org/") 887 print("Sending 'Hello, World'...") 888 ws.send("Hello, World") 889 print("Sent") 890 print("Receiving...") 891 result = ws.recv() 892 print("Received '%s'" % result) 893 ws.close() 894