Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(89)

Side by Side Diff: tools/chrome_remote_control/third_party/websocket-client/websocket.py

Issue 11361165: [chrome_remote_control] Rename chrome_remote_control to telemetry. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698