listing27-1.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. from xmlrpc.client import ServerProxy
  2. from os.path import join, isfile
  3. from xmlrpc.server import SimpleXMLRPCServer
  4. from urllib.parse import urlparse
  5. import sys
  6. MAX_HISTORY_LENGTH = 6
  7. OK = 1
  8. FAIL = 2
  9. EMPTY = ''
  10. def get_port(url):
  11. 'Extracts the port from a URL'
  12. name = urlparse(url)[1]
  13. parts = name.split(':')
  14. return int(parts[-1])
  15. class Node:
  16. """
  17. A node in a peer-to-peer network.
  18. """
  19. def __init__(self, url, dirname, secret):
  20. self.url = url
  21. self.dirname = dirname
  22. self.secret = secret
  23. self.known = set()
  24. def query(self, query, history=[]):
  25. """
  26. Performs a query for a file, possibly asking other known Nodes for
  27. help. Returns the file as a string.
  28. """
  29. code, data = self._handle(query)
  30. if code == OK:
  31. return code, data
  32. else:
  33. history = history + [self.url]
  34. if len(history) >= MAX_HISTORY_LENGTH:
  35. return FAIL, EMPTY
  36. return self._broadcast(query, history)
  37. def hello(self, other):
  38. """
  39. Used to introduce the Node to other Nodes.
  40. """
  41. self.known.add(other)
  42. return OK
  43. def fetch(self, query, secret):
  44. """
  45. Used to make the Node find a file and download it.
  46. """
  47. if secret != self.secret: return FAIL
  48. code, data = self.query(query)
  49. if code == OK:
  50. f = open(join(self.dirname, query), 'w')
  51. f.write(data)
  52. f.close()
  53. return OK
  54. else:
  55. return FAIL
  56. def _start(self):
  57. """
  58. Used internally to start the XML-RPC server.
  59. """
  60. s = SimpleXMLRPCServer(("", get_port(self.url)), logRequests=False)
  61. s.register_instance(self)
  62. s.serve_forever()
  63. def _handle(self, query):
  64. """
  65. Used internally to handle queries.
  66. """
  67. dir = self.dirname
  68. name = join(dir, query)
  69. if not isfile(name): return FAIL, EMPTY
  70. return OK, open(name).read()
  71. def _broadcast(self, query, history):
  72. """
  73. Used internally to broadcast a query to all known Nodes.
  74. """
  75. for other in self.known.copy():
  76. if other in history: continue
  77. try:
  78. s = ServerProxy(other)
  79. code, data = s.query(query, history)
  80. if code == OK:
  81. return code, data
  82. except:
  83. self.known.remove(other)
  84. return FAIL, EMPTY
  85. def main():
  86. url, directory, secret = sys.argv[1:]
  87. n = Node(url, directory, secret)
  88. n._start()
  89. if __name__ == '__main__': main()