198 lines
4.7 KiB
Python
198 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import cbor, gzip, json, lzma, psycopg2, re, socket, time
|
|
|
|
DEFAULT_CONFIG = {
|
|
"db_blocks": {
|
|
"type": "postgre",
|
|
"info_postgre": {
|
|
"connect": "host=127.0.0.1 port=5432 user=minetest password=PASSWORD dbname=minetest-world"
|
|
}
|
|
},
|
|
"server": {
|
|
"listen_addr": "0.0.0.0",
|
|
"listen_port": 8060,
|
|
"write_passwords": []
|
|
}
|
|
}
|
|
|
|
CONFIG_PATH = "config.json"
|
|
RECBUF = 1024
|
|
|
|
AVAILABLE_FORMATS = {"json": "text/json", "cbor": "application/cbor"}
|
|
AVAILABLE_COMPRESSIONS = {"none": None, "gzip": "gzip", "lzma": "lzma"}
|
|
|
|
p_clen = re.compile("\r?\ncontent-length: *(\d+)\r?\n?", re.IGNORECASE)
|
|
p_stmt = re.compile("^(x|y|z)(<|>|=|<=|>=)-?\d{1,5}$")
|
|
encode = {
|
|
"json": lambda x: json.dumps(x).encode(),
|
|
"cbor": cbor.dumps
|
|
}
|
|
compress = {
|
|
"gzip": gzip.compress,
|
|
"lzma": lzma.compress
|
|
}
|
|
|
|
def send_response(client, code, resp, resp_format, resp_compression):
|
|
content_raw = encode[resp_format](resp)
|
|
|
|
compression_str = ""
|
|
if AVAILABLE_COMPRESSIONS[resp_compression]:
|
|
compression_str = "Content-Encoding: " + AVAILABLE_COMPRESSIONS[resp_compression] + "\r\n"
|
|
content_raw = compress[resp_compression](content_raw)
|
|
|
|
mime = AVAILABLE_FORMATS[resp_format]
|
|
|
|
client.sendall(("HTTP/1.1 "+code+"\r\nContent-type: "+mime+"; charset=UTF-8\r\n"+compression_str+"Access-Control-Allow-Origin: *\r\nContent-length: "+str(len(content_raw))+"\r\n\r\n").encode()+content_raw)
|
|
client.close()
|
|
|
|
if __name__ == "__main__":
|
|
|
|
# Read config
|
|
config_file = open(CONFIG_PATH, "r")
|
|
config = json.load(config_file)
|
|
config_file.close()
|
|
|
|
# Open DB
|
|
if config["db_blocks"]["type"] == "postgre":
|
|
conn = psycopg2.connect(CONFIG_DB_CONNECT)
|
|
|
|
# Start server
|
|
server_addr = CONFIG_LISTEN
|
|
if ":" in server_addr[0]: # IPv6
|
|
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
|
else: # IPv4
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
sock.settimeout(5)
|
|
server_addr = (config["server"]["listen_addr"], config["server"]["listen_port"])
|
|
sock.bind(server_addr)
|
|
sock.listen(1)
|
|
print("Server started at "+str(server_addr))
|
|
|
|
# Listen
|
|
while True:
|
|
try:
|
|
client, addr = sock.accept()
|
|
except socket.timeout:
|
|
continue
|
|
|
|
# Get request
|
|
paquet = b""
|
|
header = b""
|
|
content = b""
|
|
content_len = 0
|
|
resp = {}
|
|
resp_format = "json"
|
|
resp_compression = "none"
|
|
lf = 0
|
|
while True:
|
|
raw = client.recv(RECBUF)
|
|
if raw:
|
|
paquet += raw
|
|
if lf >= 0:
|
|
for c in raw:
|
|
if c == 10:# LF
|
|
lf += 1
|
|
elif c != 13:# CR
|
|
lf = 0
|
|
if lf > 1:
|
|
parts = paquet.split(b"\r\n\r\n")
|
|
header = parts[0]
|
|
content = parts[1]
|
|
try:
|
|
content_len = int(p_clen.search(header.decode()).group(1))
|
|
except (AttributeError, ValueError):
|
|
content_len = 0
|
|
break
|
|
if lf > 1:
|
|
break
|
|
else:
|
|
break
|
|
while len(content) < content_len:
|
|
raw = client.recv(RECBUF)
|
|
paquet += raw
|
|
content += raw
|
|
|
|
# Get URL
|
|
httpreq = paquet.split(b"\n")
|
|
try:
|
|
url = httpreq[0].split(b" ")[1].decode().split("/")
|
|
except IndexError:
|
|
send_response(client, "400 Bad Request", {"error": "bad_http"}, resp_format, resp_compression)
|
|
continue
|
|
while "" in url:
|
|
url.remove("")
|
|
urll = len(url)
|
|
|
|
if urll > 32:
|
|
send_response(client, "400 Bad Request", {"error": "too_many_args"}, resp_format, resp_compression)
|
|
continue
|
|
|
|
stmts = []
|
|
|
|
bad = False
|
|
token = None
|
|
for val in url:
|
|
if token:
|
|
if token == 1:
|
|
if not val in AVAILABLE_FORMATS:
|
|
bad = True
|
|
break
|
|
resp_format = val
|
|
token = None
|
|
continue
|
|
elif token == 2:
|
|
if not val in AVAILABLE_COMPRESSIONS:
|
|
bad = True
|
|
break
|
|
resp_compression = val
|
|
token = None
|
|
continue
|
|
bad = True
|
|
break
|
|
|
|
if val == "fmt":
|
|
token = 1
|
|
continue
|
|
elif val == "cpr":
|
|
token = 2
|
|
continue
|
|
|
|
if not p_stmt.match(val):
|
|
bad = True
|
|
break
|
|
stmts.append("pos"+val)
|
|
|
|
if bad:
|
|
send_response(client, "400 Bad Request", {"error": "bad_request"}, resp_format, resp_compression)
|
|
continue
|
|
|
|
if len(stmts) > 6:
|
|
send_response(client, "400 Bad Request", {"error": "too_many_statements"}, resp_format, resp_compression)
|
|
continue
|
|
|
|
if len(stmts) == 0:
|
|
send_response(client, "400 Bad Request", {"error": "no_statement"}, resp_format, resp_compression)
|
|
continue
|
|
|
|
req = " AND ".join(stmts)
|
|
|
|
cur = conn.cursor()
|
|
cur.execute("SELECT * FROM blocks WHERE "+req+" LIMIT 1000000;")
|
|
resp["blocks"] = []
|
|
while True:
|
|
block = cur.fetchone()
|
|
if not block:
|
|
break
|
|
resp["blocks"].append([block[0], block[1], block[2],
|
|
block[3].hex() if resp_format == "json" else block[3].tobytes()
|
|
])
|
|
|
|
print(req)
|
|
|
|
# Send response
|
|
send_response(client, "200 OK", resp, resp_format, resp_compression)
|
|
|
|
time.sleep(.2)
|