-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
161 lines (132 loc) · 5.33 KB
/
server.py
File metadata and controls
161 lines (132 loc) · 5.33 KB
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
from http.server import BaseHTTPRequestHandler, HTTPServer
import logging
import logging.config
import os
import json
import math
import traceback
class PoolDataServer(BaseHTTPRequestHandler):
# pool_data is considered as global variable store the pool_values
# Dictionary is selected as store format for quick access O(1) by pool_id on /query
pool_data = {}
def get_percentile(self, values, percentile):
"""
Caculate percentile value
"""
k = (len(values)-1) * (percentile/100.0)
f = math.floor(k)
c = math.ceil(k)
if f == c:
return values[int(k)]
return values[int(f)] * (c-k) + values[int(c)] * (k-f)
def validate_data(self, data, var_type):
"""
Validate input data including: pool_id, pool_values and percentile. Performance tradeoff once applying this
but it would help the api-proccess more reliable.
"""
if var_type == "pool_values":
for d in data:
if not type(d) in (int, float):
return False
elif var_type == "pool_id":
if not type(data) == int:
return False
elif var_type == "percentile":
if not type(data) in (int, float):
return False
if data < 0 or data > 100:
return False
else:
raise ValueError(f"No suport data validation for '{var_type}'")
return True
def query(self, pool_id, post_data):
"""
Handle '/query' endpoint call.
"""
percentile = post_data.get("percentile", None)
if self.validate_data(percentile, var_type="percentile"):
self.set_response(resp_type="success")
values = self.pool_data.get(pool_id, None)
val_length = None if values is None else len(values)
result = None if values is None else self.get_percentile(values, percentile)
resp_data = {
"length": val_length,
"result": result
}
self.wfile.write(json.dumps(resp_data).encode(encoding="utf8"))
else:
self.set_response(resp_type="client_error")
def append(self, pool_id, post_data):
"""
Handle '/append' endpoint call.
"""
pool_values = post_data.get("poolValues", [])
if self.validate_data(pool_values, var_type="pool_values"):
self.set_response("success")
if pool_id in self.pool_data:
self.pool_data[pool_id] += pool_values
resp_status = {"status": "appended"}
logger.debug(f"Values appended: {self.pool_data}")
else:
self.pool_data[pool_id] = pool_values
resp_status = {"status": "inserted"}
logger.debug(f"Values inserted: {self.pool_data}")
# Write status response and sorting pool_values to improve query speed
self.wfile.write(json.dumps(resp_status).encode(encoding="utf8"))
self.pool_data[pool_id] = sorted(self.pool_data[pool_id])
else:
self.set_response("client_error")
def set_response(self, resp_type):
"""
Set return response code and header.
"""
if resp_type == "success":
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
elif resp_type == "client_error":
self.send_response(400)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"error": "Bad input data."}).encode(encoding="utf8"))
elif resp_type == "server_error":
self.send_response(500)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"error": "Internal server error."}).encode(encoding="utf8"))
else:
raise ValueError(f"Not support this reponse type '{type}'")
def do_POST(self):
"""
Post method for 2 endpoints: /append and /query
"""
try:
content_length = int(self.headers["Content-Length"])
post_data = json.loads(self.rfile.read(content_length).decode("utf-8"))
pool_id = post_data.get("poolId", None)
if self.validate_data(pool_id, "pool_id"):
if self.path == "/append":
self.append(pool_id, post_data)
elif self.path == "/query":
self.query(pool_id, post_data)
else:
self.set_response("client_error")
except Exception:
logger.error(traceback.format_exc())
self.set_response("server_error")
if __name__ == "__main__":
# Setup logging
base_path = os.path.dirname(os.path.realpath(__file__))
logging.config.fileConfig(os.path.join(base_path, "logging.ini"))
logger = logging.getLogger("pool_value_app")
# HTTP Server operation
port = 8081
server_address = ("", port)
httpd = HTTPServer(server_address, PoolDataServer)
logger.info("HTTP Server starting...")
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
logger.info("HTTP Server stoping...")