_core.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. from __future__ import print_function
  2. """
  3. _core.py
  4. ====================================
  5. WebSocket Python client
  6. """
  7. """
  8. websocket - WebSocket client library for Python
  9. Copyright (C) 2010 Hiroki Ohtani(liris)
  10. This library is free software; you can redistribute it and/or
  11. modify it under the terms of the GNU Lesser General Public
  12. License as published by the Free Software Foundation; either
  13. version 2.1 of the License, or (at your option) any later version.
  14. This library is distributed in the hope that it will be useful,
  15. but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. Lesser General Public License for more details.
  18. You should have received a copy of the GNU Lesser General Public
  19. License along with this library; if not, write to the Free Software
  20. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  21. """
  22. import socket
  23. import struct
  24. import threading
  25. import time
  26. import six
  27. # websocket modules
  28. from ._abnf import *
  29. from ._exceptions import *
  30. from ._handshake import *
  31. from ._http import *
  32. from ._logging import *
  33. from ._socket import *
  34. from ._ssl_compat import *
  35. from ._utils import *
  36. __all__ = ['WebSocket', 'create_connection']
  37. class WebSocket(object):
  38. """
  39. Low level WebSocket interface.
  40. This class is based on the WebSocket protocol `draft-hixie-thewebsocketprotocol-76 <http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76>`_
  41. We can connect to the websocket server and send/receive data.
  42. The following example is an echo client.
  43. >>> import websocket
  44. >>> ws = websocket.WebSocket()
  45. >>> ws.connect("ws://echo.websocket.org")
  46. >>> ws.send("Hello, Server")
  47. >>> ws.recv()
  48. 'Hello, Server'
  49. >>> ws.close()
  50. Parameters
  51. ----------
  52. get_mask_key: func
  53. a callable to produce new mask keys, see the set_mask_key
  54. function's docstring for more details
  55. sockopt: tuple
  56. values for socket.setsockopt.
  57. sockopt must be tuple and each element is argument of sock.setsockopt.
  58. sslopt: dict
  59. optional dict object for ssl socket option.
  60. fire_cont_frame: bool
  61. fire recv event for each cont frame. default is False
  62. enable_multithread: bool
  63. if set to True, lock send method.
  64. skip_utf8_validation: bool
  65. skip utf8 validation.
  66. """
  67. def __init__(self, get_mask_key=None, sockopt=None, sslopt=None,
  68. fire_cont_frame=False, enable_multithread=False,
  69. skip_utf8_validation=False, **_):
  70. """
  71. Initialize WebSocket object.
  72. Parameters
  73. ----------
  74. sslopt: specify ssl certification verification options
  75. """
  76. self.sock_opt = sock_opt(sockopt, sslopt)
  77. self.handshake_response = None
  78. self.sock = None
  79. self.connected = False
  80. self.get_mask_key = get_mask_key
  81. # These buffer over the build-up of a single frame.
  82. self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation)
  83. self.cont_frame = continuous_frame(
  84. fire_cont_frame, skip_utf8_validation)
  85. if enable_multithread:
  86. self.lock = threading.Lock()
  87. self.readlock = threading.Lock()
  88. else:
  89. self.lock = NoLock()
  90. self.readlock = NoLock()
  91. def __iter__(self):
  92. """
  93. Allow iteration over websocket, implying sequential `recv` executions.
  94. """
  95. while True:
  96. yield self.recv()
  97. def __next__(self):
  98. return self.recv()
  99. def next(self):
  100. return self.__next__()
  101. def fileno(self):
  102. return self.sock.fileno()
  103. def set_mask_key(self, func):
  104. """
  105. Set function to create mask key. You can customize mask key generator.
  106. Mainly, this is for testing purpose.
  107. Parameters
  108. ----------
  109. func: func
  110. callable object. the func takes 1 argument as integer.
  111. The argument means length of mask key.
  112. This func must return string(byte array),
  113. which length is argument specified.
  114. """
  115. self.get_mask_key = func
  116. def gettimeout(self):
  117. """
  118. Get the websocket timeout (in seconds) as an int or float
  119. Returns
  120. ----------
  121. timeout: int or float
  122. returns timeout value (in seconds). This value could be either float/integer.
  123. """
  124. return self.sock_opt.timeout
  125. def settimeout(self, timeout):
  126. """
  127. Set the timeout to the websocket.
  128. Parameters
  129. ----------
  130. timeout: int or float
  131. timeout time (in seconds). This value could be either float/integer.
  132. """
  133. self.sock_opt.timeout = timeout
  134. if self.sock:
  135. self.sock.settimeout(timeout)
  136. timeout = property(gettimeout, settimeout)
  137. def getsubprotocol(self):
  138. """
  139. Get subprotocol
  140. """
  141. if self.handshake_response:
  142. return self.handshake_response.subprotocol
  143. else:
  144. return None
  145. subprotocol = property(getsubprotocol)
  146. def getstatus(self):
  147. """
  148. Get handshake status
  149. """
  150. if self.handshake_response:
  151. return self.handshake_response.status
  152. else:
  153. return None
  154. status = property(getstatus)
  155. def getheaders(self):
  156. """
  157. Get handshake response header
  158. """
  159. if self.handshake_response:
  160. return self.handshake_response.headers
  161. else:
  162. return None
  163. def is_ssl(self):
  164. return isinstance(self.sock, ssl.SSLSocket)
  165. headers = property(getheaders)
  166. def connect(self, url, **options):
  167. """
  168. Connect to url. url is websocket url scheme.
  169. ie. ws://host:port/resource
  170. You can customize using 'options'.
  171. If you set "header" list object, you can set your own custom header.
  172. >>> ws = WebSocket()
  173. >>> ws.connect("ws://echo.websocket.org/",
  174. ... header=["User-Agent: MyProgram",
  175. ... "x-custom: header"])
  176. timeout: <type>
  177. socket timeout time. This value is an integer or float.
  178. if you set None for this value, it means "use default_timeout value"
  179. Parameters
  180. ----------
  181. options:
  182. - header: list or dict
  183. custom http header list or dict.
  184. - cookie: str
  185. cookie value.
  186. - origin: str
  187. custom origin url.
  188. - suppress_origin: bool
  189. suppress outputting origin header.
  190. - host: str
  191. custom host header string.
  192. - http_proxy_host: <type>
  193. http proxy host name.
  194. - http_proxy_port: <type>
  195. http proxy port. If not set, set to 80.
  196. - http_no_proxy: <type>
  197. host names, which doesn't use proxy.
  198. - http_proxy_auth: <type>
  199. http proxy auth information. tuple of username and password. default is None
  200. - redirect_limit: <type>
  201. number of redirects to follow.
  202. - subprotocols: <type>
  203. array of available sub protocols. default is None.
  204. - socket: <type>
  205. pre-initialized stream socket.
  206. """
  207. self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout)
  208. self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options),
  209. options.pop('socket', None))
  210. try:
  211. self.handshake_response = handshake(self.sock, *addrs, **options)
  212. for attempt in range(options.pop('redirect_limit', 3)):
  213. if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES:
  214. url = self.handshake_response.headers['location']
  215. self.sock.close()
  216. self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options),
  217. options.pop('socket', None))
  218. self.handshake_response = handshake(self.sock, *addrs, **options)
  219. self.connected = True
  220. except:
  221. if self.sock:
  222. self.sock.close()
  223. self.sock = None
  224. raise
  225. def send(self, payload, opcode=ABNF.OPCODE_TEXT):
  226. """
  227. Send the data as string.
  228. Parameters
  229. ----------
  230. payload: <type>
  231. Payload must be utf-8 string or unicode,
  232. if the opcode is OPCODE_TEXT.
  233. Otherwise, it must be string(byte array)
  234. opcode: <type>
  235. operation code to send. Please see OPCODE_XXX.
  236. """
  237. frame = ABNF.create_frame(payload, opcode)
  238. return self.send_frame(frame)
  239. def send_frame(self, frame):
  240. """
  241. Send the data frame.
  242. >>> ws = create_connection("ws://echo.websocket.org/")
  243. >>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT)
  244. >>> ws.send_frame(frame)
  245. >>> cont_frame = ABNF.create_frame("My name is ", ABNF.OPCODE_CONT, 0)
  246. >>> ws.send_frame(frame)
  247. >>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1)
  248. >>> ws.send_frame(frame)
  249. Parameters
  250. ----------
  251. frame: <type>
  252. frame data created by ABNF.create_frame
  253. """
  254. if self.get_mask_key:
  255. frame.get_mask_key = self.get_mask_key
  256. data = frame.format()
  257. length = len(data)
  258. if (isEnabledForTrace()):
  259. trace("send: " + repr(data))
  260. with self.lock:
  261. while data:
  262. l = self._send(data)
  263. data = data[l:]
  264. return length
  265. def send_binary(self, payload):
  266. return self.send(payload, ABNF.OPCODE_BINARY)
  267. def ping(self, payload=""):
  268. """
  269. Send ping data.
  270. Parameters
  271. ----------
  272. payload: <type>
  273. data payload to send server.
  274. """
  275. if isinstance(payload, six.text_type):
  276. payload = payload.encode("utf-8")
  277. self.send(payload, ABNF.OPCODE_PING)
  278. def pong(self, payload=""):
  279. """
  280. Send pong data.
  281. Parameters
  282. ----------
  283. payload: <type>
  284. data payload to send server.
  285. """
  286. if isinstance(payload, six.text_type):
  287. payload = payload.encode("utf-8")
  288. self.send(payload, ABNF.OPCODE_PONG)
  289. def recv(self):
  290. """
  291. Receive string data(byte array) from the server.
  292. Returns
  293. ----------
  294. data: string (byte array) value.
  295. """
  296. with self.readlock:
  297. opcode, data = self.recv_data()
  298. if six.PY3 and opcode == ABNF.OPCODE_TEXT:
  299. return data.decode("utf-8")
  300. elif opcode == ABNF.OPCODE_TEXT or opcode == ABNF.OPCODE_BINARY:
  301. return data
  302. else:
  303. return ''
  304. def recv_data(self, control_frame=False):
  305. """
  306. Receive data with operation code.
  307. Parameters
  308. ----------
  309. control_frame: bool
  310. a boolean flag indicating whether to return control frame
  311. data, defaults to False
  312. Returns
  313. -------
  314. opcode, frame.data: tuple
  315. tuple of operation code and string(byte array) value.
  316. """
  317. opcode, frame = self.recv_data_frame(control_frame)
  318. return opcode, frame.data
  319. def recv_data_frame(self, control_frame=False):
  320. """
  321. Receive data with operation code.
  322. Parameters
  323. ----------
  324. control_frame: bool
  325. a boolean flag indicating whether to return control frame
  326. data, defaults to False
  327. Returns
  328. -------
  329. frame.opcode, frame: tuple
  330. tuple of operation code and string(byte array) value.
  331. """
  332. while True:
  333. frame = self.recv_frame()
  334. if not frame:
  335. # handle error:
  336. # 'NoneType' object has no attribute 'opcode'
  337. raise WebSocketProtocolException(
  338. "Not a valid frame %s" % frame)
  339. elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
  340. self.cont_frame.validate(frame)
  341. self.cont_frame.add(frame)
  342. if self.cont_frame.is_fire(frame):
  343. return self.cont_frame.extract(frame)
  344. elif frame.opcode == ABNF.OPCODE_CLOSE:
  345. self.send_close()
  346. return frame.opcode, frame
  347. elif frame.opcode == ABNF.OPCODE_PING:
  348. if len(frame.data) < 126:
  349. self.pong(frame.data)
  350. else:
  351. raise WebSocketProtocolException(
  352. "Ping message is too long")
  353. if control_frame:
  354. return frame.opcode, frame
  355. elif frame.opcode == ABNF.OPCODE_PONG:
  356. if control_frame:
  357. return frame.opcode, frame
  358. def recv_frame(self):
  359. """
  360. Receive data as frame from server.
  361. Returns
  362. -------
  363. self.frame_buffer.recv_frame(): ABNF frame object
  364. """
  365. return self.frame_buffer.recv_frame()
  366. def send_close(self, status=STATUS_NORMAL, reason=six.b("")):
  367. """
  368. Send close data to the server.
  369. Parameters
  370. ----------
  371. status: <type>
  372. status code to send. see STATUS_XXX.
  373. reason: str or bytes
  374. the reason to close. This must be string or bytes.
  375. """
  376. if status < 0 or status >= ABNF.LENGTH_16:
  377. raise ValueError("code is invalid range")
  378. self.connected = False
  379. self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
  380. def close(self, status=STATUS_NORMAL, reason=six.b(""), timeout=3):
  381. """
  382. Close Websocket object
  383. Parameters
  384. ----------
  385. status: <type>
  386. status code to send. see STATUS_XXX.
  387. reason: <type>
  388. the reason to close. This must be string.
  389. timeout: int or float
  390. timeout until receive a close frame.
  391. If None, it will wait forever until receive a close frame.
  392. """
  393. if self.connected:
  394. if status < 0 or status >= ABNF.LENGTH_16:
  395. raise ValueError("code is invalid range")
  396. try:
  397. self.connected = False
  398. self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
  399. sock_timeout = self.sock.gettimeout()
  400. self.sock.settimeout(timeout)
  401. start_time = time.time()
  402. while timeout is None or time.time() - start_time < timeout:
  403. try:
  404. frame = self.recv_frame()
  405. if frame.opcode != ABNF.OPCODE_CLOSE:
  406. continue
  407. if isEnabledForError():
  408. recv_status = struct.unpack("!H", frame.data[0:2])[0]
  409. if recv_status >= 3000 and recv_status <= 4999:
  410. debug("close status: " + repr(recv_status))
  411. elif recv_status != STATUS_NORMAL:
  412. error("close status: " + repr(recv_status))
  413. break
  414. except:
  415. break
  416. self.sock.settimeout(sock_timeout)
  417. self.sock.shutdown(socket.SHUT_RDWR)
  418. except:
  419. pass
  420. self.shutdown()
  421. def abort(self):
  422. """
  423. Low-level asynchronous abort, wakes up other threads that are waiting in recv_*
  424. """
  425. if self.connected:
  426. self.sock.shutdown(socket.SHUT_RDWR)
  427. def shutdown(self):
  428. """
  429. close socket, immediately.
  430. """
  431. if self.sock:
  432. self.sock.close()
  433. self.sock = None
  434. self.connected = False
  435. def _send(self, data):
  436. return send(self.sock, data)
  437. def _recv(self, bufsize):
  438. try:
  439. return recv(self.sock, bufsize)
  440. except WebSocketConnectionClosedException:
  441. if self.sock:
  442. self.sock.close()
  443. self.sock = None
  444. self.connected = False
  445. raise
  446. def create_connection(url, timeout=None, class_=WebSocket, **options):
  447. """
  448. Connect to url and return websocket object.
  449. Connect to url and return the WebSocket object.
  450. Passing optional timeout parameter will set the timeout on the socket.
  451. If no timeout is supplied,
  452. the global default timeout setting returned by getdefaulttimeout() is used.
  453. You can customize using 'options'.
  454. If you set "header" list object, you can set your own custom header.
  455. >>> conn = create_connection("ws://echo.websocket.org/",
  456. ... header=["User-Agent: MyProgram",
  457. ... "x-custom: header"])
  458. Parameters
  459. ----------
  460. timeout: int or float
  461. socket timeout time. This value could be either float/integer.
  462. if you set None for this value,
  463. it means "use default_timeout value"
  464. class_: <type>
  465. class to instantiate when creating the connection. It has to implement
  466. settimeout and connect. It's __init__ should be compatible with
  467. WebSocket.__init__, i.e. accept all of it's kwargs.
  468. options: <type>
  469. - header: list or dict
  470. custom http header list or dict.
  471. - cookie: str
  472. cookie value.
  473. - origin: str
  474. custom origin url.
  475. - suppress_origin: bool
  476. suppress outputting origin header.
  477. - host: <type>
  478. custom host header string.
  479. - http_proxy_host: <type>
  480. http proxy host name.
  481. - http_proxy_port: <type>
  482. http proxy port. If not set, set to 80.
  483. - http_no_proxy: <type>
  484. host names, which doesn't use proxy.
  485. - http_proxy_auth: <type>
  486. http proxy auth information. tuple of username and password. default is None
  487. - enable_multithread: bool
  488. enable lock for multithread.
  489. - redirect_limit: <type>
  490. number of redirects to follow.
  491. - sockopt: <type>
  492. socket options
  493. - sslopt: <type>
  494. ssl option
  495. - subprotocols: <type>
  496. array of available sub protocols. default is None.
  497. - skip_utf8_validation: bool
  498. skip utf8 validation.
  499. - socket: <type>
  500. pre-initialized stream socket.
  501. """
  502. sockopt = options.pop("sockopt", [])
  503. sslopt = options.pop("sslopt", {})
  504. fire_cont_frame = options.pop("fire_cont_frame", False)
  505. enable_multithread = options.pop("enable_multithread", False)
  506. skip_utf8_validation = options.pop("skip_utf8_validation", False)
  507. websock = class_(sockopt=sockopt, sslopt=sslopt,
  508. fire_cont_frame=fire_cont_frame,
  509. enable_multithread=enable_multithread,
  510. skip_utf8_validation=skip_utf8_validation, **options)
  511. websock.settimeout(timeout if timeout is not None else getdefaulttimeout())
  512. websock.connect(url, **options)
  513. return websock