ticle-godot-frontend/addons/http_server/http_server.gd

173 lines
4.5 KiB
GDScript

class_name HTTPServer extends TCP_Server
# Public constants
const Method = preload("res://addons/http_server/method.gd")
const Request = preload("res://addons/http_server/request.gd")
const Response = preload("res://addons/http_server/response.gd")
const Status = preload("res://addons/http_server/status.gd")
# Private variables
var __endpoints: Dictionary = {
# key: [Int, String], array with 0 index representing method, 1 index representing endpoint
# value: [FuncRef, Array], index 0 = reference to function to call, index 1 = binds to pass to func
}
var __fallback: FuncRef = null
var __server: TCP_Server = null
# Public methods
func endpoint(type: int, endpoint: String, function: FuncRef, binds: Array = []) -> void:
var endpoint_hash: Array = [type, endpoint]
if endpoint_hash in __endpoints:
print(
"[ERR] Endpoint already defined type: %s, endpoint: %s" % [
Method.type_to_identifier(type),
endpoint,
]
)
return
__endpoints[endpoint_hash] = [function, binds]
func fallback(function: FuncRef) -> void:
__fallback = function
func take_connection() -> StreamPeerTCP:
if !is_listening():
print(
"[ERR] Server is not listening, please initialize and listen before calling `take_connection`"
)
return null
var connection: StreamPeerTCP = .take_connection()
if connection:
__process_connection(connection)
return connection
# Private methods
func __process_connection(connection: StreamPeerTCP) -> void:
var content: PoolByteArray = PoolByteArray([])
while true:
var bytes = connection.get_available_bytes()
if bytes == 0:
break
var data = connection.get_partial_data(bytes)
content.append_array(data[1])
if content.empty():
return
var content_string: String = content.get_string_from_utf8()
var content_parts: Array = content_string.split("\r\n")
if content_parts.empty():
connection.put_data(__response_from_status(Status.BAD_REQUEST).to_utf8())
return
var request_line = content_parts[0]
var request_line_parts = request_line.split(" ")
var method: String = request_line_parts[0]
var endpoint: String = request_line_parts[1]
var headers: Dictionary = {}
var header_index: int = content_parts.find("")
if header_index == -1:
print(
"[ERR] Error parsing request data: %s" % [String(content)]
)
connection.put_data(__response_from_status(Status.BAD_REQUEST).to_utf8())
return
for i in range(1, header_index):
var header_parts: Array = content_parts[i].split(":", true, 1)
var header = header_parts[0].strip_edges().to_lower()
var value = header_parts[1].strip_edges()
headers[header] = value
var body: String = ""
if header_index != content_parts.size() - 1:
var body_parts: Array = content_parts.slice(header_index + 1, content_parts.size())
body = PoolStringArray(body_parts).join("\r\n")
var response: Response = __process_request(method, endpoint, headers, body)
connection.put_data(response.get_data())
func __process_request(method: String, endpoint: String, headers: Dictionary, body: String) -> Response:
var type: int = Method.description_to_type(method)
var request: Request = Request.new(
type,
endpoint,
headers,
body
)
var endpoint_func: FuncRef = null
var endpoint_parts: PoolStringArray = endpoint.split("/", false)
var binds
# special case for if endpoint is just root
if endpoint == "/":
var endpoint_hash: Array = [type, "/"]
if __endpoints.has(endpoint_hash):
endpoint_func = __endpoints[endpoint_hash][0]
binds = __endpoints[endpoint_hash][1]
else:
while (!endpoint_func && !endpoint_parts.empty()):
var endpoint_hash: Array = [type, "/" + endpoint_parts.join("/")]
if __endpoints.has(endpoint_hash):
endpoint_func = __endpoints[endpoint_hash][0]
binds = __endpoints[endpoint_hash][1]
else:
endpoint_parts.remove(endpoint_parts.size() - 1)
if !endpoint_func:
print(
"[WRN] Recieved request for unknown endpoint, method: %s, endpoint: %s" % [method, endpoint]
)
if __fallback:
endpoint_func = __fallback
else:
return __response_from_status(Status.NOT_FOUND)
var response: Response = Response.new()
if !endpoint_func.is_valid():
print(
"[ERR] FuncRef for endpoint not valid, method: %s, endpoint: %s" % [method, endpoint]
)
else:
print(
"[INF] Recieved request method: %s, endpoint: %s" % [method, endpoint]
)
endpoint_func.call_func(request, response, binds if binds else [request.endpoint()])
return response
func __response_from_status(code: int) -> Response:
var response: Response = Response.new()
response.status(code)
return response