server.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. from __future__ import print_function
  4. from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
  5. from storage import Storage
  6. import json
  7. import logging
  8. import re
  9. from SocketServer import ThreadingMixIn
  10. class BatcaveHttpRequestHandler(BaseHTTPRequestHandler):
  11. def __init__(self, request, client_address, server):
  12. self.logger = logging.getLogger('API')
  13. BaseHTTPRequestHandler.__init__(self, request, client_address, server)
  14. def parse_url_pathquery(self):
  15. url = re.match(r'^\/(?P<path>.+?)(\?(?P<query>.+))?$', self.path)
  16. if url is None:
  17. logging.warn('Failed to parse URL \'' + str(self.path) + '\'.')
  18. return ( None, None )
  19. path = url.group('path')
  20. query = {}
  21. if not url.group('query') is None:
  22. for m in re.finditer(r'(?P<key>.+?)=(?P<value>.+?)(&|$)', url.group('query')):
  23. query[m.group('key')] = m.group('value')
  24. return ( path, query )
  25. def do_GET(self):
  26. path, query = self.parse_url_pathquery()
  27. if path == '':
  28. self.respond_index(query)
  29. return
  30. if path == 'list':
  31. self.respond_list(query)
  32. return
  33. m = re.match(r'node/([a-f0-9]{12})(?P<cmd>\.json|/[a-zA-Z0-9_\-]+)$', path)
  34. if m != None:
  35. cmd = m.group('cmd')
  36. if cmd == '.json':
  37. self.respond_node(m.group(1))
  38. else:
  39. self.respond_nodedetail(m.group(1), cmd[1:])
  40. return
  41. self.send_error(404, 'The URL \'{0}\' was not found here.'.format(path))
  42. def send_nocache_headers(self):
  43. self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
  44. self.send_header('Pragma', 'no-cache')
  45. self.send_header('Expires', '0')
  46. def send_headers(self, content_type='text/html; charset=utf-8', nocache=True):
  47. self.send_response(200)
  48. self.send_header('Content-Type', content_type)
  49. if nocache: self.send_nocache_headers()
  50. self.end_headers()
  51. def respond_index(self, query):
  52. storagy = self.server.storage
  53. self.send_headers()
  54. self.wfile.write('<!DOCTYPE html><html><head><title>BATCAVE</title></head>\n')
  55. self.wfile.write('<body>\n')
  56. self.wfile.write('<H1 title="Batman/Alfred Transmission Collection, Aggregation & Value Engine">BATCAVE</H1>\n')
  57. self.wfile.write('<p>Dies ist ein interner Hintergrund-Dienst. Er wird nur von anderen Diensten\n')
  58. self.wfile.write('angesprochen und sollte aus einer Mehrzahl von Gr&uuml;nden nicht &ouml;ffentlich\n')
  59. self.wfile.write('zug&auml;nglich sein.</p>\n')
  60. self.wfile.write('<H2>Status</H2>\n')
  61. self.wfile.write('Daten: <span id="datacount" class="value">')
  62. self.wfile.write(len(storage.data))
  63. self.wfile.write('</span>\n')
  64. self.wfile.write('<H2>API</H2>\n')
  65. self.wfile.write('<p>Grundsätzlich ist das Antwort-Format JSON und alle Daten sind Live-Daten (kein Cache) die ggf. etwas Bearbeitungs-Zeit erfordern.</p>')
  66. self.wfile.write('<dl>\n')
  67. self.wfile.write('<dt><a href="/nodes.json">nodes.json</a></dt><dd>zur Verwendung mit ffmap (MACs anonymisiert)</dd>\n')
  68. self.wfile.write('<dt><a href="/node/ff00ff00ff00.json">/node/&lt;id&gt;.json</a></dt><dd><u>alle</u> vorhandenen Information zu der gewünschten Node</dd>\n')
  69. self.wfile.write('</dl>\n')
  70. self.wfile.write('</body></html>')
  71. def respond_list(self, query):
  72. storage = self.server.storage
  73. self.send_headers()
  74. self.wfile.write('<!DOCTYPE html><html><head><title>BATCAVE</title></head>\n')
  75. self.wfile.write('<body>\n')
  76. self.wfile.write('<H1>BATCAVE - LIST</H1>\n')
  77. self.wfile.write('<table>\n')
  78. self.wfile.write('<thead><tr><th>ID</th><th>Name</th></tr></thead>\n')
  79. self.wfile.write('<tbody>\n')
  80. data = storage.data
  81. if 'sort' in query:
  82. if query['sort'] == 'name':
  83. sorteddata = sorted(data, key=lambda x: data[x]['hostname'].lower())
  84. data = sorteddata
  85. elif query['sort'] == 'id':
  86. sorteddata = sorted(data)
  87. data = sorteddata
  88. for nodeid in data:
  89. nodename = storage.data[nodeid]['hostname'] if 'hostname' in storage.data[nodeid] else '&lt;?&gt;'
  90. self.wfile.write('<tr><td><a href="/node/' + nodeid + '.json">' + nodeid + '</a></td><td>' + nodename + '</td></tr>')
  91. self.wfile.write('</tbody>\n')
  92. self.wfile.write('</table>\n')
  93. def respond_node(self, nodeid):
  94. storage = self.server.storage
  95. if nodeid == 'ff00ff00ff00':
  96. self.send_headers('text/json')
  97. self.wfile.write(json.dumps({
  98. 'name': 'API-Example',
  99. 'nodeid': nodeid,
  100. 'META': 'Dies ist ein minimaler Beispiel-Datensatz. Herzlichen Glückwunsch, du hast das Prinzip der API kapiert.',
  101. }))
  102. return
  103. if not nodeid in storage.data:
  104. self.send_error(404, 'No node with id \'' + nodeid + '\' present.')
  105. return
  106. self.send_headers('text/json')
  107. self.wfile.write(json.dumps(storage.data[nodeid]))
  108. def respond_nodedetail(self, nodeid, field):
  109. storage = self.server.storage
  110. if not nodeid in storage.data:
  111. self.send_error(404, 'No node with id \'' + nodeid + '\' present.')
  112. return
  113. if not field in storage.data[nodeid]:
  114. self.send_error(404, 'The node \'' + nodeid + '\' does not have a field named \'' + str(field) + '\'.')
  115. return
  116. value = storage.data[nodeid][field]
  117. self.send_headers('text/plain' if isinstance(value, basestring) else 'text/json')
  118. self.wfile.write(value if isinstance(value, basestring) else json.dumps(value))
  119. class ApiServer(ThreadingMixIn, HTTPServer):
  120. def __init__(self, endpoint, storage):
  121. HTTPServer.__init__(self, endpoint, BatcaveHttpRequestHandler)
  122. self.storage = storage
  123. def __str__(self):
  124. return 'ApiServer on {0}'.format(self.server_address)
  125. if __name__ == '__main__':
  126. dummystorage = Storage()
  127. server = ApiServer(('0.0.0.0', 8888), dummystorage)
  128. print("Server:", str(server))
  129. server.serve_forever()