pokepurple/addons/twitcher/lib/http/http_server.gd

178 lines
4.8 KiB
GDScript3
Raw Permalink Normal View History

@tool
extends Node
## Provides a simple HTTP Service to serve web stuff
class_name HTTPServer
## Key: int | Value: WeakRef(Server)
static var _servers : Dictionary = {}
class Server extends TCPServer:
var _bind_address: String
var _port: int
var _clients: Array[Client] = []
var _listeners: int
signal request_received(client: Client)
signal client_connected(client: Client)
signal client_disconnected(client: Client)
signal client_error_occured(client: Client, error: Error)
func _init(bind_address: String, port: int) -> void:
_bind_address = bind_address
_port = port
HTTPServer._servers[_port] = weakref(self)
func start_listening() -> void:
_listeners += 1
if !is_listening():
var status: Error = listen(_port, _bind_address)
Engine.get_main_loop().process_frame.connect(_process)
if status != OK:
HTTPServer.logError("Could not listen to port %d: %s" % [_port, error_string(status)])
else:
HTTPServer.logInfo("{%s:%s} listening" % [ _bind_address, _port ])
else:
HTTPServer.logDebug("{%s:%s} already listening" % [ _bind_address, _port ])
func stop_listening() -> void:
_listeners -= 1
HTTPServer.logDebug("{%s:%s} listener node detached %s left" % [ _bind_address, _port, _listeners ])
if _listeners <= 0:
_stop_server()
func _stop_server() -> void:
HTTPServer.logInfo("{%s:%s} stop" % [ _bind_address, _port ])
Engine.get_main_loop().process_frame.disconnect(_process)
for client in _clients:
client.peer.disconnect_from_host()
stop()
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
HTTPServer.logInfo("{%s:%s} removed" % [ _bind_address, _port ])
HTTPServer._servers.erase(_port)
func _process() -> void:
if !is_listening(): return
if is_connection_available():
_handle_connect()
for client in _clients:
_process_request(client)
_handle_disconnect(client)
func _process_request(client: Client) -> void:
var peer := client.peer
if peer.get_status() == StreamPeerTCP.STATUS_CONNECTED:
var error = peer.poll()
if error != OK:
HTTPServer.logError("Could not poll client %d: %s" % [_port, error_string(error)])
client_error_occured.emit(client, error)
elif peer.get_available_bytes() > 0:
request_received.emit(client)
func _handle_connect() -> void:
var peer := take_connection()
var client := Client.new()
client.peer = peer
_clients.append(client)
client_connected.emit(client)
HTTPServer.logInfo("{%s:%s} client connected" % [ _bind_address, _port ])
func _handle_disconnect(client: Client) -> void:
if client.peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
client_disconnected.emit(client)
HTTPServer.logInfo("{%s:%s} client disconnected" % [ _bind_address, _port ])
_clients.erase(client)
class Client extends RefCounted:
var peer: StreamPeerTCP
## Called when a new request was made
signal request_received(client: Client)
@export var _port: int
@export var _bind_address: String
var _server : Server
var _listening: bool
static func create(port: int, bind_address: String = "*") -> HTTPServer:
var server = HTTPServer.new()
server._bind_address = bind_address
server._port = port
return server
func _ready() -> void:
if _servers.has(_port) && _servers[_port] != null:
_server = _servers[_port].get_ref()
else:
_server = Server.new(_bind_address, _port)
_server.request_received.connect(_on_request_received)
logInfo("{%s:%s} start" % [ _bind_address, _port ])
func _on_request_received(client: Client) -> void:
request_received.emit(client)
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
stop_listening()
func start_listening() -> void:
_listening = true
_server.start_listening()
func stop_listening() -> void:
if _listening:
_listening = false
_server.stop_listening()
func send_response(client: Client, response_code : String, body : PackedByteArray) -> void:
var peer = client.peer
peer.put_data(("HTTP/1.1 %s\r\n" % response_code).to_utf8_buffer())
peer.put_data("Server: Godot Engine (Twitcher)\r\n".to_utf8_buffer())
peer.put_data(("Content-Length: %d\r\n"% body.size()).to_utf8_buffer())
peer.put_data("Connection: close\r\n".to_utf8_buffer())
peer.put_data("Content-Type: text/html charset=UTF-8\r\n".to_utf8_buffer())
peer.put_data("\r\n".to_utf8_buffer())
peer.put_data(body)
# === LOGGER ===
static var logger: Dictionary = {}
static func set_logger(error: Callable, info: Callable, debug: Callable) -> void:
logger.debug = debug
logger.info = info
logger.error = error
static func logDebug(text: String) -> void:
if logger.has("debug"): logger.debug.call(text)
static func logInfo(text: String) -> void:
if logger.has("info"): logger.info.call(text)
static func logError(text: String) -> void:
if logger.has("error"): logger.error.call(text)