listing27-2.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from xmlrpc.client import ServerProxy, Fault
  2. from os.path import join, abspath, isfile
  3. from xmlrpc.server import SimpleXMLRPCServer
  4. from urllib.parse import urlparse
  5. import sys
  6. SimpleXMLRPCServer.allow_reuse_address = 1
  7. MAX_HISTORY_LENGTH = 6
  8. UNHANDLED = 100
  9. ACCESS_DENIED = 200
  10. class UnhandledQuery(Fault):
  11. """
  12. An exception that represents an unhandled query.
  13. """
  14. def __init__(self, message="Couldn't handle the query"):
  15. super().__init__(UNHANDLED, message)
  16. class AccessDenied(Fault):
  17. """
  18. An exception that is raised if a user tries to access a
  19. resource for which he or she is not authorized.
  20. """
  21. def __init__(self, message="Access denied"):
  22. super().__init__(ACCESS_DENIED, message)
  23. def inside(dir, name):
  24. """
  25. Checks whether a given file name lies within a given directory.
  26. """
  27. dir = abspath(dir)
  28. name = abspath(name)
  29. return name.startswith(join(dir, ''))
  30. def get_port(url):
  31. """
  32. Extracts the port number from a URL.
  33. """
  34. name = urlparse(url)[1]
  35. parts = name.split(':')
  36. return int(parts[-1])
  37. class Node:
  38. """
  39. A node in a peer-to-peer network.
  40. """
  41. def __init__(self, url, dirname, secret):
  42. self.url = url
  43. self.dirname = dirname
  44. self.secret = secret
  45. self.known = set()
  46. def query(self, query, history=[]):
  47. """
  48. Performs a query for a file, possibly asking other known Nodes for
  49. help. Returns the file as a string.
  50. """
  51. try:
  52. return self._handle(query)
  53. except UnhandledQuery:
  54. history = history + [self.url]
  55. if len(history) >= MAX_HISTORY_LENGTH: raise
  56. return self._broadcast(query, history)
  57. def hello(self, other):
  58. """
  59. Used to introduce the Node to other Nodes.
  60. """
  61. self.known.add(other)
  62. return 0
  63. def fetch(self, query, secret):
  64. """
  65. Used to make the Node find a file and download it.
  66. """
  67. if secret != self.secret: raise AccessDenied
  68. result = self.query(query)
  69. f = open(join(self.dirname, query), 'w')
  70. f.write(result)
  71. f.close()
  72. return 0
  73. def _start(self):
  74. """
  75. Used internally to start the XML-RPC server.
  76. """
  77. s = SimpleXMLRPCServer(("", get_port(self.url)), logRequests=False)
  78. s.register_instance(self)
  79. s.serve_forever()
  80. def _handle(self, query):
  81. """
  82. Used internally to handle queries.
  83. """
  84. dir = self.dirname
  85. name = join(dir, query)
  86. if not isfile(name): raise UnhandledQuery
  87. if not inside(dir, name): raise AccessDenied
  88. return open(name).read()
  89. def _broadcast(self, query, history):
  90. """
  91. Used internally to broadcast a query to all known Nodes.
  92. """
  93. for other in self.known.copy():
  94. if other in history: continue
  95. try:
  96. s = ServerProxy(other)
  97. return s.query(query, history)
  98. except Fault as f:
  99. if f.faultCode == UNHANDLED: pass
  100. else: self.known.remove(other)
  101. except:
  102. self.known.remove(other)
  103. raise UnhandledQuery
  104. def main():
  105. url, directory, secret = sys.argv[1:]
  106. n = Node(url, directory, secret)
  107. n._start()
  108. if __name__ == '__main__': main()