PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/tv/lib/net.py

https://github.com/omgwtftbh/miro
Python | 661 lines | 583 code | 7 blank | 71 comment | 8 complexity | 323e67f38159e24e7d2c80ec7d6f67ce MD5 | raw file
Possible License(s): GPL-2.0, MIT
  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011
  3. # Participatory Culture Foundation
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  18. #
  19. # In addition, as a special exception, the copyright holders give
  20. # permission to link the code of portions of this program with the OpenSSL
  21. # library.
  22. #
  23. # You must obey the GNU General Public License in all respects for all of
  24. # the code used other than OpenSSL. If you modify file(s) with this
  25. # exception, you may extend this exception to your version of the file(s),
  26. # but you are not obligated to do so. If you do not wish to do so, delete
  27. # this exception statement from your version. If you delete this exception
  28. # statement from all source files in the program, then also delete it here.
  29. """net.py -- Low-level Networking Code
  30. The main class here is ConnectionHandler, which is a state-based socket
  31. handling class. It's used for communication with the downloader daemon via a
  32. socket.
  33. This class also defines the base Exception classes used by httpclient.
  34. """
  35. import errno
  36. import logging
  37. import socket
  38. from miro import app
  39. from miro import eventloop
  40. from miro import util
  41. from miro import prefs
  42. from miro import signals
  43. from miro import trapcall
  44. from miro.clock import clock
  45. from miro.gtcache import gettext as _
  46. SOCKET_READ_TIMEOUT = 60
  47. SOCKET_INITIAL_READ_TIMEOUT = 30
  48. SOCKET_CONNECT_TIMEOUT = 15
  49. # socket.ssl is deprecated as of Python 2.6, so we use socket_ssl for
  50. # pre Python 2.6 and ssl.wrap_socket for Python 2.6 and later.
  51. try:
  52. import ssl
  53. ssl.wrap_socket
  54. def convert_to_ssl(sock):
  55. return ssl.wrap_socket(sock)
  56. except (ImportError, AttributeError):
  57. def convert_to_ssl(sock):
  58. return socket.ssl(sock)
  59. class NetworkError(StandardError):
  60. """Base class for all errors that will be passed to errbacks from get_url
  61. and friends. NetworkErrors can be display in 2 ways:
  62. getFriendlyDescription() -- short, newbie friendly description
  63. getLongDescription() -- detailed description
  64. """
  65. def __init__(self, shortDescription, longDescription=None):
  66. if longDescription is None:
  67. longDescription = shortDescription
  68. self.friendlyDescription = _("Error: %(msg)s",
  69. {"msg": shortDescription})
  70. self.longDescription = longDescription
  71. def getFriendlyDescription(self):
  72. return self.friendlyDescription
  73. def getLongDescription(self):
  74. return self.longDescription
  75. def __str__(self):
  76. return "%s: %s -- %s" % (self.__class__,
  77. util.stringify(self.getFriendlyDescription()),
  78. util.stringify(self.getLongDescription()))
  79. class ConnectionError(NetworkError):
  80. def __init__(self, errorMessage):
  81. self.friendlyDescription = _("Can't connect")
  82. self.longDescription = _("Connection Error: %(msg)s",
  83. {"msg": util.unicodify(errorMessage)})
  84. class SSLConnectionError(ConnectionError):
  85. def __init__(self):
  86. self.friendlyDescription = _("Can't connect")
  87. self.longDescription = _("SSL connection error")
  88. class ConnectionTimeout(NetworkError):
  89. def __init__(self, host):
  90. NetworkError.__init__(self, _('Timeout'),
  91. _('Connection to %(host)s timed out', {"host": host}))
  92. def trap_call(object, function, *args, **kwargs):
  93. """Convenience function do a trapcall.trap_call, where when is
  94. 'While talking to the network'
  95. """
  96. return trapcall.time_trap_call("Calling %s on %s" % (function, object),
  97. function, *args, **kwargs)
  98. class NetworkBuffer(object):
  99. """Responsible for storing incomming network data and doing some basic
  100. parsing of it. I think this is about as fast as we can do things in pure
  101. python, someday we may want to make it C...
  102. """
  103. def __init__(self):
  104. self.chunks = []
  105. self.length = 0
  106. def addData(self, data):
  107. self.chunks.append(data)
  108. self.length += len(data)
  109. def _mergeChunks(self):
  110. self.chunks = [''.join(self.chunks)]
  111. def has_data(self):
  112. return self.length > 0
  113. def discard_data(self):
  114. self.chunks = []
  115. self.length = 0
  116. def read(self, size=None):
  117. """Read at most size bytes from the data that has been added to the
  118. buffer. """
  119. self._mergeChunks()
  120. if size is not None:
  121. rv = self.chunks[0][:size]
  122. self.chunks[0] = self.chunks[0][len(rv):]
  123. else:
  124. rv = self.chunks[0]
  125. self.chunks = []
  126. self.length -= len(rv)
  127. return rv
  128. def readline(self):
  129. """Like a file readline, with several difference:
  130. * If there isn't a full line ready to be read we return None.
  131. * Doesn't include the trailing line separator.
  132. * Both "\r\n" and "\n" act as a line ender
  133. """
  134. self._mergeChunks()
  135. split = self.chunks[0].split("\n", 1)
  136. if len(split) == 2:
  137. self.chunks[0] = split[1]
  138. self.length = len(self.chunks[0])
  139. if split[0].endswith("\r"):
  140. return split[0][:-1]
  141. else:
  142. return split[0]
  143. else:
  144. return None
  145. def unread(self, data):
  146. """Put back read data. This make is like the data was never read at
  147. all.
  148. """
  149. self.chunks.insert(0, data)
  150. self.length += len(data)
  151. def getValue(self):
  152. self._mergeChunks()
  153. return self.chunks[0]
  154. class _Packet(object):
  155. """A packet of data for the AsyncSocket class
  156. """
  157. def __init__(self, data, callback=None):
  158. self.data = data
  159. self.callback = callback
  160. class AsyncSocket(object):
  161. """Socket class that uses the eventloop module.
  162. """
  163. MEMORY_ERROR_LIMIT = 5
  164. def __init__(self, closeCallback=None):
  165. """Create an AsyncSocket. If closeCallback is given, it will be
  166. called if we detect that the socket has been closed durring a
  167. read/write operation. The arguments will be the AsyncSocket object
  168. and either socket.SHUT_RD or socket.SHUT_WR.
  169. """
  170. self.toSend = []
  171. self.to_send_length = 0
  172. self.readSize = 4096
  173. self.socket = None
  174. self.readCallback = None
  175. self.closeCallback = closeCallback
  176. self.readTimeout = None
  177. self.timedOut = False
  178. self.connectionErrback = None
  179. self.disable_read_timeout = False
  180. self.readSomeData = False
  181. self.name = ""
  182. self.lastClock = None
  183. self.memoryErrors = 0
  184. def __str__(self):
  185. if self.name:
  186. return "%s: %s" % (type(self).__name__, self.name)
  187. else:
  188. return "Unknown %s" % (type(self).__name__,)
  189. # The complication in the timeout code is because creating and
  190. # cancelling a timeout costs some memory (timeout is in memory
  191. # until it goes off, even if cancelled.)
  192. def startReadTimeout(self):
  193. if self.disable_read_timeout:
  194. return
  195. self.lastClock = clock()
  196. if self.readTimeout is not None:
  197. return
  198. self.readTimeout = eventloop.add_timeout(SOCKET_INITIAL_READ_TIMEOUT,
  199. self.onReadTimeout,
  200. "AsyncSocket.onReadTimeout")
  201. def stopReadTimeout(self):
  202. if self.readTimeout is not None:
  203. self.readTimeout.cancel()
  204. self.readTimeout = None
  205. def _pick_address(self, addresses):
  206. """Pick the best entry to use from a list of addresses
  207. :param addresses: list of address tuples returned by getaddrinfo()
  208. :returns: one of the tuples, or None if no address could be found
  209. """
  210. if not app.config.get(prefs.DISABLE_IPV6) and util.use_ipv6():
  211. # prefer ipv6 if possible
  212. for entry in addresses:
  213. if entry[0] == socket.AF_INET6:
  214. return entry
  215. # fall back on ipv4
  216. for entry in addresses:
  217. if entry[0] == socket.AF_INET:
  218. return entry
  219. return None
  220. def open_connection(self, host, port, callback, errback,
  221. disable_read_timeout=None):
  222. """Open a connection. On success, callback will be called with this
  223. object.
  224. """
  225. if disable_read_timeout is not None:
  226. self.disable_read_timeout = disable_read_timeout
  227. self.name = "Outgoing %s:%s" % (host, port)
  228. self.connectionErrback = errback
  229. def handleGetAddrInfoException(e):
  230. if self.connectionErrback is None:
  231. # called connectionErrback while we were waiting for
  232. # getaddrinfo
  233. return
  234. trap_call(self, errback,
  235. ConnectionError(e[1] + " (host: %s)" % host))
  236. def createSocketHandle(family):
  237. try:
  238. self.socket = socket.socket(family, socket.SOCK_STREAM)
  239. except socket.error, e:
  240. trap_call(self, errback, ConnectionError(e[1]))
  241. return
  242. self.socket.setblocking(0)
  243. return self.socket
  244. def onAddressLookup(addresses):
  245. if self.connectionErrback is None:
  246. # called connectionErrback while we were waiting for
  247. # getaddrinfo
  248. return
  249. entry = self._pick_address(addresses)
  250. if entry is None:
  251. # FIXME - wtf kind of user message is this? it's too
  252. # technical and there's no way a user would know what
  253. # to do about it.
  254. msg = _("Couldn't find address family to use")
  255. trap_call(self, errback, ConnectionError(msg))
  256. return
  257. try:
  258. self.socket = socket.socket(entry[0], socket.SOCK_STREAM)
  259. except socket.error, e:
  260. trap_call(self, errback, ConnectionError(e[1]))
  261. return
  262. self.socket.setblocking(0)
  263. try:
  264. rv = self.socket.connect_ex(entry[4])
  265. except socket.gaierror:
  266. trap_call(self, errback, ConnectionError('gaierror'))
  267. return
  268. if rv in (0, errno.EINPROGRESS, errno.EWOULDBLOCK):
  269. eventloop.add_write_callback(self.socket, onWriteReady)
  270. self.socketConnectTimeout = eventloop.add_timeout(
  271. SOCKET_CONNECT_TIMEOUT, onWriteTimeout,
  272. "socket connect timeout")
  273. else:
  274. fullmsg = "Connection failed"
  275. trap_call(self, errback, ConnectionError(fullmsg))
  276. def onWriteReady():
  277. eventloop.remove_write_callback(self.socket)
  278. self.socketConnectTimeout.cancel()
  279. rv = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
  280. if rv == 0:
  281. trap_call(self, callback, self)
  282. else:
  283. msg = errno.errorcode.get(rv, _('Unknown Error code'))
  284. trap_call(self, errback, ConnectionError(msg))
  285. self.connectionErrback = None
  286. def onWriteTimeout():
  287. eventloop.remove_write_callback(self.socket)
  288. trap_call(self, errback, ConnectionTimeout(host))
  289. self.connectionErrback = None
  290. eventloop.call_in_thread(onAddressLookup, handleGetAddrInfoException,
  291. socket.getaddrinfo,
  292. "getAddrInfo - %s:%s" % (host, port),
  293. host, port)
  294. def accept_connection(self, family, host, port, callback, errback):
  295. def finishAccept():
  296. eventloop.remove_read_callback(self.socket)
  297. (self.socket, addr) = self.socket.accept()
  298. trap_call(self, callback, self)
  299. self.connectionErrback = None
  300. self.name = "Incoming %s:%s" % (host, port)
  301. self.connectionErrback = errback
  302. try:
  303. self.socket = socket.socket(family, socket.SOCK_STREAM)
  304. self.socket.bind((host, port))
  305. except socket.error, e:
  306. trap_call(self, errback, ConnectionError(e[1]))
  307. return
  308. if self.socket.family == socket.AF_INET:
  309. (self.addr, self.port) = self.socket.getsockname()
  310. elif self.socket.family == socket.AF_INET6:
  311. (self.addr, self.port, self.flowinfo,
  312. self.scopeid) = self.socket.getsockname()
  313. else:
  314. raise ValueError("Unknown socket family: %s", self.socket.family)
  315. self.socket.listen(63)
  316. eventloop.add_read_callback(self.socket, finishAccept)
  317. def close_connection(self):
  318. if self.isOpen():
  319. eventloop.stop_handling_socket(self.socket)
  320. self.stopReadTimeout()
  321. self.socket.close()
  322. self.socket = None
  323. if self.connectionErrback is not None:
  324. error = NetworkError(_("Connection closed"))
  325. trap_call(self, self.connectionErrback, error)
  326. self.connectionErrback = None
  327. def isOpen(self):
  328. return self.socket is not None
  329. def send_data(self, data, callback=None):
  330. """Send data out to the socket when it becomes ready.
  331. NOTE: currently we have no way of detecting when the data gets sent
  332. out, or if errors happen.
  333. """
  334. if not self.isOpen():
  335. raise ValueError("Socket not connected")
  336. self.toSend.append(_Packet(data, callback))
  337. self.to_send_length += len(data)
  338. eventloop.add_write_callback(self.socket, self.onWriteReady)
  339. def startReading(self, readCallback):
  340. """Start reading from the socket. When data becomes available it will
  341. be passed to readCallback. If there is already a read callback, it
  342. will be replaced.
  343. """
  344. if not self.isOpen():
  345. raise ValueError("Socket not connected")
  346. self.readCallback = readCallback
  347. eventloop.add_read_callback(self.socket, self.onReadReady)
  348. self.startReadTimeout()
  349. def stopReading(self):
  350. """Stop reading from the socket."""
  351. if not self.isOpen():
  352. raise ValueError("Socket not connected")
  353. self.readCallback = None
  354. eventloop.remove_read_callback(self.socket)
  355. self.stopReadTimeout()
  356. def onReadTimeout(self):
  357. if self.readSomeData:
  358. timeout = SOCKET_READ_TIMEOUT
  359. else:
  360. timeout = SOCKET_INITIAL_READ_TIMEOUT
  361. if clock() < self.lastClock + timeout:
  362. self.readTimeout = eventloop.add_timeout(
  363. self.lastClock + timeout - clock(), self.onReadTimeout,
  364. "AsyncSocket.onReadTimeout")
  365. else:
  366. self.readTimeout = None
  367. self.timedOut = True
  368. self.handleEarlyClose('read')
  369. def handleSocketError(self, code, msg, operation):
  370. if code in (errno.EWOULDBLOCK, errno.EINTR):
  371. return
  372. if operation == "write":
  373. expectedErrors = (errno.EPIPE, errno.ECONNRESET)
  374. else:
  375. expectedErrors = (errno.ECONNREFUSED, errno.ECONNRESET)
  376. if code not in expectedErrors:
  377. logging.warning("WARNING, got unexpected error during %s",
  378. operation)
  379. logging.warning("%s: %s", errno.errorcode.get(code), msg)
  380. self.handleEarlyClose(operation)
  381. def onWriteReady(self):
  382. try:
  383. if len(self.toSend) > 0:
  384. sent = self.socket.send(self.toSend[0].data)
  385. else:
  386. sent = 0
  387. except socket.error, (code, msg):
  388. self.handleSocketError(code, msg, "write")
  389. else:
  390. self.handleSentData(sent)
  391. def handleSentData(self, sent):
  392. if len(self.toSend) > 0:
  393. self.toSend[0].data = self.toSend[0].data[sent:]
  394. if len(self.toSend[0].data) == 0:
  395. if self.toSend[0].callback:
  396. self.toSend[0].callback()
  397. self.toSend = self.toSend[1:]
  398. self.to_send_length -= sent
  399. if len(self.toSend) == 0:
  400. eventloop.remove_write_callback(self.socket)
  401. def onReadReady(self):
  402. try:
  403. data = self.socket.recv(self.readSize)
  404. except socket.error, (code, msg):
  405. self.handleSocketError(code, msg, "read")
  406. except MemoryError:
  407. # This happens because of a windows bug in the socket code (see
  408. # #4373). Let's hope that things clear themselves up next time we
  409. # read.
  410. self.memoryErrors += 1
  411. if self.memoryErrors > self.MEMORY_ERROR_LIMIT:
  412. logging.error("ERROR: Too many MemoryErrors on %s", self)
  413. self.handleEarlyClose('read')
  414. else:
  415. logging.warning(
  416. "WARNING: Memory error while reading from %s", self)
  417. else:
  418. self.memoryErrors = 0
  419. self.handleReadData(data)
  420. def handleReadData(self, data):
  421. self.startReadTimeout()
  422. if data == '':
  423. if self.closeCallback:
  424. trap_call(self, self.closeCallback, self, socket.SHUT_RD)
  425. else:
  426. self.readSomeData = True
  427. trap_call(self, self.readCallback, data)
  428. def handleEarlyClose(self, operation):
  429. self.close_connection()
  430. if self.closeCallback:
  431. if operation == 'read':
  432. typ = socket.SHUT_RD
  433. else:
  434. typ = socket.SHUT_WR
  435. trap_call(self, self.closeCallback, self, typ)
  436. class AsyncSSLStream(AsyncSocket):
  437. def __init__(self, closeCallback=None):
  438. super(AsyncSSLStream, self).__init__(closeCallback)
  439. self.interruptedOperation = None
  440. def open_connection(self, host, port, callback, errback,
  441. disable_read_timeout=None):
  442. def onSocketOpen(self):
  443. self.socket.setblocking(1)
  444. eventloop.call_in_thread(onSSLOpen, handleSSLError, convert_to_ssl,
  445. "AsyncSSL onSocketOpen()",
  446. self.socket)
  447. def onSSLOpen(ssl):
  448. if self.socket is None:
  449. # the connection was closed while we were calling
  450. # convert_to_ssl
  451. return
  452. self.socket.setblocking(0)
  453. self.ssl = ssl
  454. # finally we can call the actuall callback
  455. callback(self)
  456. def handleSSLError(error):
  457. logging.error("handleSSLError: %r", error)
  458. errback(SSLConnectionError())
  459. super(AsyncSSLStream, self).open_connection(host, port, onSocketOpen,
  460. errback, disable_read_timeout)
  461. def resumeNormalCallbacks(self):
  462. if self.readCallback is not None:
  463. eventloop.add_read_callback(self.socket, self.onReadReady)
  464. if len(self.toSend) != 0:
  465. eventloop.add_write_callback(self.socket, self.onWriteReady)
  466. def handleSocketError(self, code, msg, operation):
  467. if code in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
  468. if self.interruptedOperation is None:
  469. self.interruptedOperation = operation
  470. elif self.interruptedOperation != operation:
  471. signals.system.failed("When talking to the network",
  472. details="socket error for the wrong SSL operation")
  473. self.close_connection()
  474. return
  475. eventloop.stop_handling_socket(self.socket)
  476. if code == socket.SSL_ERROR_WANT_READ:
  477. eventloop.add_read_callback(self.socket, self.onReadReady)
  478. else:
  479. eventloop.add_write_callback(self.socket, self.onWriteReady)
  480. elif code in (socket.SSL_ERROR_ZERO_RETURN, socket.SSL_ERROR_SSL,
  481. socket.SSL_ERROR_SYSCALL, socket.SSL_ERROR_EOF):
  482. self.handleEarlyClose(operation)
  483. else:
  484. super(AsyncSSLStream, self).handleSocketError(code, msg,
  485. operation)
  486. def onWriteReady(self):
  487. if self.interruptedOperation == 'read':
  488. return self.onReadReady()
  489. try:
  490. if len(self.toSend) > 0:
  491. sent = self.ssl.write(self.toSend[0].data)
  492. else:
  493. sent = 0
  494. except socket.error, (code, msg):
  495. self.handleSocketError(code, msg, "write")
  496. else:
  497. if self.interruptedOperation == 'write':
  498. self.resumeNormalCallbacks()
  499. self.interruptedOperation = None
  500. self.handleSentData(sent)
  501. def onReadReady(self):
  502. if self.interruptedOperation == 'write':
  503. return self.onWriteReady()
  504. try:
  505. data = self.ssl.read(self.readSize)
  506. except socket.error, (code, msg):
  507. self.handleSocketError(code, msg, "read")
  508. else:
  509. if self.interruptedOperation == 'read':
  510. self.resumeNormalCallbacks()
  511. self.interruptedOperation = None
  512. self.handleReadData(data)
  513. class ConnectionHandler(object):
  514. """Base class to handle asynchronous network streams. It implements a
  515. simple state machine to deal with incomming data.
  516. Sending data: Use the send_data() method.
  517. Reading Data: Add entries to the state dictionary, which maps strings to
  518. methods. The state methods will be called when there is data available,
  519. which can be read from the buffer variable. The states dictionary can
  520. contain a None value, to signal that the handler isn't interested in
  521. reading at that point. Use change_state() to switch states.
  522. Subclasses should override the handle_close() method to handle the
  523. socket closing.
  524. """
  525. stream_factory = AsyncSocket
  526. def __init__(self):
  527. self.buffer = NetworkBuffer()
  528. self.states = {'initializing': None, 'closed': None}
  529. self.stream = self.stream_factory(closeCallback=self.closeCallback)
  530. self.change_state('initializing')
  531. self.name = ""
  532. def __str__(self):
  533. return "%s -- %s" % (self.__class__, self.state)
  534. def open_connection(self, host, port, callback, errback,
  535. disable_read_timeout=None):
  536. self.name = "Outgoing %s:%s" % (host, port)
  537. self.host = host
  538. self.port = port
  539. def callbackIntercept(asyncSocket):
  540. if callback:
  541. trap_call(self, callback, self)
  542. self.stream.open_connection(host, port, callbackIntercept, errback,
  543. disable_read_timeout)
  544. def close_connection(self):
  545. if self.stream.isOpen():
  546. self.stream.close_connection()
  547. self.change_state('closed')
  548. self.buffer.discard_data()
  549. def send_data(self, data, callback=None):
  550. self.stream.send_data(data, callback)
  551. def change_state(self, newState):
  552. self.readHandler = self.states[newState]
  553. self.state = newState
  554. self.updateReadCallback()
  555. def updateReadCallback(self):
  556. if self.readHandler is not None:
  557. self.stream.startReading(self.handleData)
  558. elif self.stream.isOpen():
  559. try:
  560. self.stream.stopReading()
  561. except KeyError:
  562. pass
  563. def handleData(self, data):
  564. self.buffer.addData(data)
  565. lastState = self.state
  566. self.readHandler()
  567. # If we switch states, continue processing the buffer. There may be
  568. # extra data that the last read handler didn't read in
  569. while self.readHandler is not None and lastState != self.state:
  570. lastState = self.state
  571. self.readHandler()
  572. def closeCallback(self, stream, typ):
  573. self.handle_close(typ)
  574. def handle_close(self, typ):
  575. """Handle our stream becoming closed. Type is either socket.SHUT_RD,
  576. or socket.SHUT_WR.
  577. """
  578. raise NotImplementedError()