tracker.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. # encoding-utf8
  2. '''Tracker communication.'''
  3. import functools
  4. import json
  5. import logging
  6. import requests
  7. import socket
  8. from terroroftinytown import six
  9. from terroroftinytown.client import VERSION
  10. from terroroftinytown.util.jsonutil import NativeStringJSONEncoder
  11. DEFAULT_USER_AGENT = 'Terroroftinytown/{0} (standalone library)'.format(VERSION)
  12. _logger = logging.getLogger(__name__)
  13. class TrackerError(Exception):
  14. pass
  15. def reraise_with_tracker_error(func):
  16. @functools.wraps(func)
  17. def wrapper(*args, **kwargs):
  18. try:
  19. return func(*args, **kwargs)
  20. except requests.RequestException as error:
  21. six.raise_from(TrackerError(str(error)), error)
  22. return wrapper
  23. class TrackerClient(object):
  24. def __init__(self, host, username, version=None, bind_address=None,
  25. user_agent=DEFAULT_USER_AGENT, scheme='http'):
  26. self.host = host
  27. self.username = username
  28. self.client_version = version
  29. if bind_address:
  30. self.bind_address(bind_address)
  31. self.user_agent = user_agent
  32. self.scheme = scheme
  33. @reraise_with_tracker_error
  34. def get_item(self):
  35. _logger.info('Contacting tracker.')
  36. response = requests.post(
  37. '{scheme}://{host}/api/get'.format(
  38. host=self.host,
  39. scheme=self.scheme),
  40. data={
  41. 'username': self.username,
  42. 'version': VERSION,
  43. 'client_version': self.client_version,
  44. },
  45. headers={
  46. 'User-Agent': self.user_agent
  47. },
  48. timeout=60,
  49. )
  50. response.raise_for_status()
  51. item = response.json()
  52. return item
  53. @reraise_with_tracker_error
  54. def upload_item(self, claim_id, tamper_key, results):
  55. _logger.info('Uploading to tracker.')
  56. response = requests.post(
  57. '{scheme}://{host}/api/done'.format(
  58. host=self.host,
  59. scheme=self.scheme),
  60. data={
  61. 'claim_id': claim_id,
  62. 'tamper_key': tamper_key,
  63. 'results': json.dumps(results, cls=NativeStringJSONEncoder),
  64. },
  65. timeout=60,
  66. )
  67. response.raise_for_status()
  68. @reraise_with_tracker_error
  69. def report_error(self, claim_id, tamper_key, message):
  70. _logger.info('Sending error report to tracker.')
  71. response = requests.post(
  72. '{scheme}://{host}/api/error'.format(
  73. host=self.host,
  74. scheme=self.scheme),
  75. data={
  76. 'claim_id': claim_id,
  77. 'tamper_key': tamper_key,
  78. 'message': message,
  79. },
  80. timeout=60,
  81. )
  82. response.raise_for_status()
  83. def bind_address(self, source_host):
  84. '''Set **all, global** socket connections to be outbound from this address'''
  85. # https://stackoverflow.com/questions/1150332/source-interface-with-python-and-urllib2
  86. real_socket_socket = socket.socket
  87. def bound_socket(*a, **k):
  88. sock = real_socket_socket(*a, **k)
  89. sock.bind((source_host, 0))
  90. return sock
  91. socket.socket = bound_socket
  92. # # https://stackoverflow.com/questions/12585317/requests-bind-to-an-ip
  93. # real_create_conn = socket.create_connection
  94. #
  95. # def set_src_addr(*args):
  96. # address, timeout = args[0], args[1]
  97. # source_address = (source_host, 0)
  98. # return real_create_conn(address, timeout, source_address)
  99. #
  100. # socket.create_connection = set_src_addr