telnet.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. """
  2. Scrapy Telnet Console extension
  3. See documentation in docs/topics/telnetconsole.rst
  4. """
  5. import pprint
  6. import logging
  7. import traceback
  8. import binascii
  9. import os
  10. from twisted.internet import protocol
  11. try:
  12. from twisted.conch import manhole, telnet
  13. from twisted.conch.insults import insults
  14. TWISTED_CONCH_AVAILABLE = True
  15. except (ImportError, SyntaxError):
  16. _TWISTED_CONCH_TRACEBACK = traceback.format_exc()
  17. TWISTED_CONCH_AVAILABLE = False
  18. from scrapy.exceptions import NotConfigured
  19. from scrapy import signals
  20. from scrapy.utils.trackref import print_live_refs
  21. from scrapy.utils.engine import print_engine_status
  22. from scrapy.utils.reactor import listen_tcp
  23. from scrapy.utils.decorators import defers
  24. try:
  25. import guppy
  26. hpy = guppy.hpy()
  27. except ImportError:
  28. hpy = None
  29. logger = logging.getLogger(__name__)
  30. # signal to update telnet variables
  31. # args: telnet_vars
  32. update_telnet_vars = object()
  33. class TelnetConsole(protocol.ServerFactory):
  34. def __init__(self, crawler):
  35. if not crawler.settings.getbool('TELNETCONSOLE_ENABLED'):
  36. raise NotConfigured
  37. if not TWISTED_CONCH_AVAILABLE:
  38. raise NotConfigured(
  39. 'TELNETCONSOLE_ENABLED setting is True but required twisted '
  40. 'modules failed to import:\n' + _TWISTED_CONCH_TRACEBACK)
  41. self.crawler = crawler
  42. self.noisy = False
  43. self.portrange = [int(x) for x in crawler.settings.getlist('TELNETCONSOLE_PORT')]
  44. self.host = crawler.settings['TELNETCONSOLE_HOST']
  45. self.username = crawler.settings['TELNETCONSOLE_USERNAME']
  46. self.password = crawler.settings['TELNETCONSOLE_PASSWORD']
  47. if not self.password:
  48. self.password = binascii.hexlify(os.urandom(8)).decode('utf8')
  49. logger.info('Telnet Password: %s', self.password)
  50. self.crawler.signals.connect(self.start_listening, signals.engine_started)
  51. self.crawler.signals.connect(self.stop_listening, signals.engine_stopped)
  52. @classmethod
  53. def from_crawler(cls, crawler):
  54. return cls(crawler)
  55. def start_listening(self):
  56. self.port = listen_tcp(self.portrange, self.host, self)
  57. h = self.port.getHost()
  58. logger.info("Telnet console listening on %(host)s:%(port)d",
  59. {'host': h.host, 'port': h.port},
  60. extra={'crawler': self.crawler})
  61. def stop_listening(self):
  62. self.port.stopListening()
  63. def protocol(self):
  64. class Portal:
  65. """An implementation of IPortal"""
  66. @defers
  67. def login(self_, credentials, mind, *interfaces):
  68. if not (credentials.username == self.username.encode('utf8') and
  69. credentials.checkPassword(self.password.encode('utf8'))):
  70. raise ValueError("Invalid credentials")
  71. protocol = telnet.TelnetBootstrapProtocol(
  72. insults.ServerProtocol,
  73. manhole.Manhole,
  74. self._get_telnet_vars()
  75. )
  76. return (interfaces[0], protocol, lambda: None)
  77. return telnet.TelnetTransport(
  78. telnet.AuthenticatingTelnetProtocol,
  79. Portal()
  80. )
  81. def _get_telnet_vars(self):
  82. # Note: if you add entries here also update topics/telnetconsole.rst
  83. telnet_vars = {
  84. 'engine': self.crawler.engine,
  85. 'spider': self.crawler.engine.spider,
  86. 'slot': self.crawler.engine.slot,
  87. 'crawler': self.crawler,
  88. 'extensions': self.crawler.extensions,
  89. 'stats': self.crawler.stats,
  90. 'settings': self.crawler.settings,
  91. 'est': lambda: print_engine_status(self.crawler.engine),
  92. 'p': pprint.pprint,
  93. 'prefs': print_live_refs,
  94. 'hpy': hpy,
  95. 'help': "This is Scrapy telnet console. For more info see: "
  96. "https://docs.scrapy.org/en/latest/topics/telnetconsole.html",
  97. }
  98. self.crawler.signals.send_catch_log(update_telnet_vars, telnet_vars=telnet_vars)
  99. return telnet_vars