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