aboutsummaryrefslogtreecommitdiff
path: root/backend/server.py
blob: ded74d62872b38e0d354a6a6c5c5a6c35a64f8b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/usr/bin/python3

import sys, re, sqlite3, json
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.parse

hostname = "localhost"
port = 8000
dbFile = "data/data.db"
tolnodeReqDepth = 1
	# For a /node?name=name1 request, respond with name1's node, and descendent nodes in a subtree to some depth > 0

usageInfo =  f"usage: {sys.argv[0]}\n"
usageInfo += "Starts a server that listens for GET requests to http://" + hostname + ":" + str(port) + ".\n"
usageInfo += "Responds to path+query /data/type1?name=name1 with JSON data.\n"
usageInfo += "\n"
usageInfo += "If type1 is 'node': \n"
usageInfo += "    Responds with a map from names to node objects, representing\n"
usageInfo += "    nodes name1, and child nodes up to depth " + str(tolnodeReqDepth) + ".\n"
usageInfo += "If type1 is 'children': Like 'node', but excludes node name1.\n"
usageInfo += "If type1 is 'chain': Like 'node', but gets nodes from name1 up to the root, and their direct children.\n"
usageInfo += "If type1 is 'search': Responds with a tolnode name that has alt-name name1, or null.\n"

dbCon = sqlite3.connect(dbFile)
def lookupNode(name):
	cur = dbCon.cursor()
	cur.execute("SELECT name, data FROM nodes WHERE name = ?", (name,))
	row = cur.fetchone()
	return row[1] if row != None else None
def lookupName(name):
	cur = dbCon.cursor()
	cur.execute("SELECT name, alt_name FROM names WHERE alt_name = ?", (name,))
	row = cur.fetchone()
	return json.dumps(row[0]) if row != None else None

class DbServer(BaseHTTPRequestHandler):
	def do_GET(self):
		# Parse URL
		urlParts = urllib.parse.urlparse(self.path)
		path = urllib.parse.unquote(urlParts.path)
		queryDict = urllib.parse.parse_qs(urlParts.query)
		# Check first element of path
		match = re.match(r"/([^/]+)/(.+)", path)
		if match != None and match.group(1) == "data" and "name" in queryDict:
			reqType = match.group(2)
			name = queryDict["name"][0]
			print(name)
			# Check query string
			if reqType == "node":
				nodeJson = lookupNode(name)
				if nodeJson != None:
					results = []
					getResultsUntilDepth(name, nodeJson, tolnodeReqDepth, results)
					self.respondJson(nodeResultsToJSON(results))
					return
			elif reqType == "children":
				nodeJson = lookupNode(name)
				if nodeJson != None:
					obj = json.loads(nodeJson)
					results = []
					for childName in obj["children"]:
						nodeJson = lookupNode(childName)
						if nodeJson != None:
							getResultsUntilDepth(childName, nodeJson, tolnodeReqDepth, results)
					self.respondJson(nodeResultsToJSON(results))
					return
			elif reqType == "chain":
				results = []
				ranOnce = False
				while True:
					jsonResult = lookupNode(name)
					if jsonResult == None:
						if ranOnce:
							print("ERROR: Parent-chain node {} not found".format(name), file=sys.stderr)
						break
					results.append([name, jsonResult])
					obj = json.loads(jsonResult)
					# Add children
					if not ranOnce:
						ranOnce = True
					else:
						internalFail = False
						for childName in obj["children"]:
							jsonResult = lookupNode(childName)
							if jsonResult == None:
								print("ERROR: Parent-chain-child node {} not found".format(name), file=sys.stderr)
								internalFail = True
								break
							results.append([childName, jsonResult])
						if internalFail:
							break
					# Check if root
					if obj["parent"] == None:
						self.respondJson(nodeResultsToJSON(results))
						return
					else:
						name = obj["parent"]
			elif reqType == "search":
				nameJson = lookupName(name)
				if nameJson != None:
					self.respondJson(nameJson)
					return
		self.send_response(404)
		self.end_headers()
		self.end_headers()
	def respondJson(self, jsonStr):
		self.send_response(200)
		self.send_header("Content-type", "application/json")
		self.end_headers()
		self.wfile.write(jsonStr.encode("utf-8"))
def getResultsUntilDepth(name, nodeJson, depth, results):
	"""Given a node [name, nodeJson] pair, adds child node pairs to 'results', up until 'depth'"""
	results.append([name, nodeJson])
	if depth > 0:
		obj = json.loads(nodeJson)
		for childName in obj["children"]:
			childJson = lookupNode(childName)
			if childJson != None:
				getResultsUntilDepth(childName, childJson, depth-1, results)
def nodeResultsToJSON(results):
	"""Given a list of [name, nodeJson] pairs, returns a representative JSON string"""
	jsonStr = "{"
	for i in range(len(results)):
		jsonStr += json.dumps(results[i][0]) + ":" + results[i][1]
		if i < len(results) - 1:
			jsonStr += ","
	jsonStr += "}"
	return jsonStr

server = HTTPServer((hostname, port), DbServer)
print("Server started at http://{}:{}".format(hostname, port))
try:
	server.serve_forever()
except KeyboardInterrupt:
	pass
server.server_close()
dbCon.close()
print("Server stopped")