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
27 from ssl
import SSLError
30 # dummy class of SSLError for ssl none-support environment.
31 class SSLError(Exception):
36 from urlparse
import urlparse
50 websocket python client.
51 =========================
53 This version support only hybi-13.
54 Please see http://tools.ietf.org/html/rfc6455 for protocol.
58 # websocket supported version.
61 # closing frame status codes.
63 STATUS_GOING_AWAY
= 1001
64 STATUS_PROTOCOL_ERROR
= 1002
65 STATUS_UNSUPPORTED_DATA_TYPE
= 1003
66 STATUS_STATUS_NOT_AVAILABLE
= 1005
67 STATUS_ABNORMAL_CLOSED
= 1006
68 STATUS_INVALID_PAYLOAD
= 1007
69 STATUS_POLICY_VIOLATION
= 1008
70 STATUS_MESSAGE_TOO_BIG
= 1009
71 STATUS_INVALID_EXTENSION
= 1010
72 STATUS_UNEXPECTED_CONDITION
= 1011
73 STATUS_TLS_HANDSHAKE_ERROR
= 1015
75 logger
= logging
.getLogger()
78 class WebSocketException(Exception):
80 websocket exeception class.
85 class WebSocketConnectionClosedException(WebSocketException
):
87 If remote host closed the connection or some network error happened,
88 this exception will be raised.
92 class WebSocketTimeoutException(WebSocketException
):
94 WebSocketTimeoutException will be raised at socket timeout during read/write data.
98 default_timeout
= None
102 def enableTrace(tracable
):
104 turn on/off the tracability.
106 tracable: boolean value. if set True, tracability is enabled.
109 traceEnabled
= tracable
111 if not logger
.handlers
:
112 logger
.addHandler(logging
.StreamHandler())
113 logger
.setLevel(logging
.DEBUG
)
116 def setdefaulttimeout(timeout
):
118 Set the global timeout setting to connect.
120 timeout: default socket timeout time. This value is second.
122 global default_timeout
123 default_timeout
= timeout
126 def getdefaulttimeout():
128 Return the global timeout setting(second) to connect.
130 return default_timeout
135 parse url and the result is tuple of
136 (hostname, port, resource path and the flag of secure mode)
141 raise ValueError("url is invalid")
143 scheme
, url
= url
.split(":", 1)
145 parsed
= urlparse(url
, scheme
="http")
147 hostname
= parsed
.hostname
149 raise ValueError("hostname is invalid")
158 elif scheme
== "wss":
163 raise ValueError("scheme %s is invalid" % scheme
)
166 resource
= parsed
.path
171 resource
+= "?" + parsed
.query
173 return (hostname
, port
, resource
, is_secure
)
176 def create_connection(url
, timeout
=None, **options
):
178 connect to url and return websocket object.
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.
186 >>> conn = create_connection("ws://echo.websocket.org/",
187 ... header=["User-Agent: MyProgram",
188 ... "x-custom: header"])
191 timeout: socket timeout time. This value is integer.
192 if you set None for this value, it means "use default_timeout value"
194 options: current support option is only "header".
195 if you set header as dict value, the custom HTTP headers are added.
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
)
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
208 # ref. Websocket gets an update, and it breaks stuff.
209 # http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
212 def _create_sec_websocket_key():
214 return base64
.encodestring(uid
.bytes
).strip()
217 _HEADERS_TO_CHECK
= {
218 "upgrade": "websocket",
219 "connection": "upgrade",
226 see http://tools.ietf.org/html/rfc5234
227 and http://tools.ietf.org/html/rfc6455#section-5.2
230 # operation code values.
238 # available operation code value tuple
239 OPCODES
= (OPCODE_CONT
, OPCODE_TEXT
, OPCODE_BINARY
, OPCODE_CLOSE
,
240 OPCODE_PING
, OPCODE_PONG
)
242 # opcode human readable string
246 OPCODE_BINARY
: "binary",
247 OPCODE_CLOSE
: "close",
252 # data length threashold.
257 def __init__(self
, fin
=0, rsv1
=0, rsv2
=0, rsv3
=0,
258 opcode
=OPCODE_TEXT
, mask
=1, data
=""):
260 Constructor for ABNF.
261 please check RFC for arguments.
270 self
.get_mask_key
= os
.urandom
273 return "fin=" + str(self
.fin
) \
274 + " opcode=" + str(self
.opcode
) \
275 + " data=" + str(self
.data
)
278 def create_frame(data
, opcode
):
280 create frame to send text, binary and other data.
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.
286 opcode: operation code. please see OPCODE_XXX.
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
)
295 format this object to string(byte array) to send data to server.
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")
305 frame_header
= chr(self
.fin
<< 7
306 | self
.rsv1
<< 6 | self
.rsv2
<< 5 | self
.rsv3
<< 4
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
)
314 frame_header
+= chr(self
.mask
<< 7 |
0x7f)
315 frame_header
+= struct
.pack("!Q", length
)
318 return frame_header
+ self
.data
320 mask_key
= self
.get_mask_key(4)
321 return frame_header
+ self
._get
_masked
(mask_key
)
323 def _get_masked(self
, mask_key
):
324 s
= ABNF
.mask(mask_key
, self
.data
)
325 return mask_key
+ "".join(s
)
328 def mask(mask_key
, data
):
330 mask or unmask data. Just do xor for each byte
332 mask_key: 4 byte string(byte).
334 data: data to mask/unmask.
336 _m
= array
.array("B", mask_key
)
337 _d
= array
.array("B", data
)
338 for i
in xrange(len(_d
)):
343 class WebSocket(object):
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
350 We can connect to the websocket server and send/recieve data.
351 The following example is a echo client.
354 >>> ws = websocket.WebSocket()
355 >>> ws.connect("ws://echo.websocket.org")
356 >>> ws.send("Hello, Server")
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.
368 def __init__(self
, get_mask_key
=None, sockopt
=None, sslopt
=None):
370 Initalize WebSocket object.
376 self
.connected
= False
377 self
.sock
= socket
.socket()
379 self
.sock
.setsockopt(*opts
)
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
392 return self
.sock
.fileno()
394 def set_mask_key(self
, func
):
396 set function to create musk key. You can custumize mask key generator.
397 Mainly, this is for testing purpose.
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.
404 self
.get_mask_key
= func
406 def gettimeout(self
):
408 Get the websocket timeout(second).
410 return self
.sock
.gettimeout()
412 def settimeout(self
, timeout
):
414 Set the timeout to the websocket.
416 timeout: timeout time(second).
418 self
.sock
.settimeout(timeout
)
420 timeout
= property(gettimeout
, settimeout
)
422 def connect(self
, url
, **options
):
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.
429 >>> ws.connect("ws://echo.websocket.org/",
430 ... header={"User-Agent: MyProgram",
431 ... "x-custom: header"})
433 timeout: socket timeout time. This value is integer.
434 if you set None for this value,
435 it means "use default_timeout value"
437 options: current support option is only "header".
438 if you set header as dict value,
439 the custom HTTP headers are added.
442 hostname
, port
, resource
, is_secure
= _parse_url(url
)
443 # TODO: we need to support proxy
444 self
.sock
.connect((hostname
, port
))
447 if self
.sslopt
is None:
451 self
.sock
= ssl
.wrap_socket(self
.sock
, **sslopt
)
453 raise WebSocketException("SSL not available.")
455 self
._handshake
(hostname
, port
, resource
, **options
)
457 def _handshake(self
, host
, port
, resource
, **options
):
460 headers
.append("GET %s HTTP/1.1" % resource
)
461 headers
.append("Upgrade: websocket")
462 headers
.append("Connection: Upgrade")
466 hostport
= "%s:%d" % (host
, port
)
467 headers
.append("Host: %s" % hostport
)
469 if "origin" in options
:
470 headers
.append("Origin: %s" % options
["origin"])
472 headers
.append("Origin: http://%s" % hostport
)
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"])
483 header_str
= "\r\n".join(headers
)
484 self
._send
(header_str
)
486 logger
.debug("--- request header ---")
487 logger
.debug(header_str
)
488 logger
.debug("-----------------------")
490 status
, resp_headers
= self
._read
_headers
()
493 raise WebSocketException("Handshake Status %d" % status
)
495 success
= self
._validate
_header
(resp_headers
, key
)
498 raise WebSocketException("Invalid WebSocket Header")
500 self
.connected
= True
502 def _validate_header(self
, headers
, key
):
503 for k
, v
in _HEADERS_TO_CHECK
.iteritems():
504 r
= headers
.get(k
, None)
511 result
= headers
.get("sec-websocket-accept", None)
514 result
= result
.lower()
516 value
= key
+ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
517 hashed
= base64
.encodestring(hashlib
.sha1(value
).digest()).strip().lower()
518 return hashed
== result
520 def _read_headers(self
):
524 logger
.debug("--- response header ---")
527 line
= self
._recv
_line
()
534 status_info
= line
.split(" ", 2)
535 status
= int(status_info
[1])
537 kv
= line
.split(":", 1)
540 headers
[key
.lower()] = value
.strip().lower()
542 raise WebSocketException("Invalid header")
545 logger
.debug("-----------------------")
547 return status
, headers
549 def send(self
, payload
, opcode
=ABNF
.OPCODE_TEXT
):
551 Send the data as string.
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)
557 opcode: operation code to send. Please see OPCODE_XXX.
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()
565 logger
.debug("send: " + repr(data
))
571 def send_binary(self
, payload
):
572 return self
.send(payload
, ABNF
.OPCODE_BINARY
)
574 def ping(self
, payload
=""):
578 payload: data payload to send server.
580 self
.send(payload
, ABNF
.OPCODE_PING
)
582 def pong(self
, payload
):
586 payload: data payload to send server.
588 self
.send(payload
, ABNF
.OPCODE_PONG
)
592 Receive string data(byte array) from the server.
594 return value: string(byte array) value.
596 opcode
, data
= self
.recv_data()
601 Recieve data with operation code.
603 return value: tuple of operation code and string(byte array) value.
606 frame
= self
.recv_frame()
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")
615 self
._cont
_data
[1] += frame
.data
617 self
._cont
_data
= [frame
.opcode
, frame
.data
]
620 data
= self
._cont
_data
621 self
._cont
_data
= None
623 elif frame
.opcode
== ABNF
.OPCODE_CLOSE
:
625 return (frame
.opcode
, None)
626 elif frame
.opcode
== ABNF
.OPCODE_PING
:
627 self
.pong(frame
.data
)
629 def recv_frame(self
):
631 recieve data as frame from server.
633 return value: ABNF frame object.
636 if self
._frame
_header
is None:
637 self
._frame
_header
= self
._recv
_strict
(2)
638 b1
= ord(self
._frame
_header
[0])
644 b2
= ord(self
._frame
_header
[1])
645 has_mask
= b2
>> 7 & 1
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]
656 self
._frame
_length
= length_bits
658 if self
._frame
_mask
is None:
659 self
._frame
_mask
= self
._recv
_strict
(4) if has_mask
else ""
661 payload
= self
._recv
_strict
(self
._frame
_length
)
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
)
671 def send_close(self
, status
=STATUS_NORMAL
, reason
=""):
673 send close data to the server.
675 status: status code to send. see STATUS_XXX.
677 reason: the reason to close. This must be string.
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
)
683 def close(self
, status
=STATUS_NORMAL
, reason
=""):
685 Close Websocket object
687 status: status code to send. see STATUS_XXX.
689 reason: the reason to close. This must be string.
692 if status
< 0 or status
>= ABNF
.LENGTH_16
:
693 raise ValueError("code is invalid range")
696 self
.send(struct
.pack('!H', status
) + reason
, ABNF
.OPCODE_CLOSE
)
697 timeout
= self
.sock
.gettimeout()
698 self
.sock
.settimeout(3)
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
))
707 self
.sock
.settimeout(timeout
)
708 self
.sock
.shutdown(socket
.SHUT_RDWR
)
711 self
._closeInternal
()
713 def _closeInternal(self
):
714 self
.connected
= False
717 def _send(self
, data
):
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
)
728 def _recv(self
, bufsize
):
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
)
739 raise WebSocketConnectionClosedException()
743 def _recv_strict(self
, bufsize
):
744 shortage
= bufsize
- sum(len(x
) for x
in self
._recv
_buffer
)
746 bytes
= self
._recv
(shortage
)
747 self
._recv
_buffer
.append(bytes
)
748 shortage
-= len(bytes
)
749 unified
= "".join(self
._recv
_buffer
)
751 self
._recv
_buffer
= []
754 self
._recv
_buffer
= [unified
[bufsize
:]]
755 return unified
[:bufsize
]
758 def _recv_line(self
):
768 class WebSocketApp(object):
770 Higher level of APIs are provided.
771 The interface is like JavaScript WebSocket object.
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):
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
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
806 def send(self
, data
, opcode
=ABNF
.OPCODE_TEXT
):
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.
812 if self
.sock
.send(data
, opcode
) == 0:
813 raise WebSocketConnectionClosedException()
817 close websocket connection.
819 self
.keep_running
= False
822 def _send_ping(self
, interval
):
824 for i
in range(interval
):
826 if not self
.keep_running
:
830 def run_forever(self
, sockopt
=None, sslopt
=None, ping_interval
=0):
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.
845 raise WebSocketException("socket is already opened")
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
)
855 thread
= threading
.Thread(target
=self
._send
_ping
, args
=(ping_interval
,))
856 thread
.setDaemon(True)
859 while self
.keep_running
:
860 data
= self
.sock
.recv()
863 self
._callback
(self
.on_message
, data
)
865 self
._callback
(self
.on_error
, e
)
868 self
.keep_running
= False
870 self
._callback
(self
.on_close
)
873 def _callback(self
, callback
, *args
):
876 callback(self
, *args
)
879 if logger
.isEnabledFor(logging
.DEBUG
):
880 _
, _
, tb
= sys
.exc_info()
881 traceback
.print_tb(tb
)
884 if __name__
== "__main__":
886 ws
= create_connection("ws://echo.websocket.org/")
887 print("Sending 'Hello, World'...")
888 ws
.send("Hello, World")
890 print("Receiving...")
892 print("Received '%s'" % result
)