Files
fps_project_1/addons/http-sse-client/HTTPSSEClient.gd

130 lines
4.5 KiB
GDScript

@tool
extends Node
signal new_sse_event(headers, event, data)
signal connected
signal connection_error(error)
const event_tag = "event:"
const data_tag = "data:"
const continue_internal = "continue_internal"
var httpclient = HTTPClient.new()
var is_connected = false
var domain
var url_after_domain
var port
var trusted_chain
var common_name_override
var told_to_connect = false
var connection_in_progress = false
var is_requested = false
var response_body = PackedByteArray()
func connect_to_host(domain : String, url_after_domain : String, port : int = -1, trusted_chain : X509Certificate = null, common_name_override : String = ""):
process_mode = Node.PROCESS_MODE_INHERIT
self.domain = domain
self.url_after_domain = url_after_domain
self.port = port
self.trusted_chain = trusted_chain
self.common_name_override = common_name_override
told_to_connect = true
func attempt_to_connect():
var tls_options = TLSOptions.client(trusted_chain, common_name_override)
var err = httpclient.connect_to_host(domain, port, tls_options)
if err == OK:
connected.emit()
is_connected = true
else:
connection_error.emit(str(err))
func attempt_to_request(httpclient_status):
if httpclient_status == HTTPClient.STATUS_CONNECTING or httpclient_status == HTTPClient.STATUS_RESOLVING:
return
if httpclient_status == HTTPClient.STATUS_CONNECTED:
var err = httpclient.request(HTTPClient.METHOD_POST, url_after_domain, ["Accept: text/event-stream"])
if err == OK:
is_requested = true
func _parse_response_body(headers):
var body = response_body.get_string_from_utf8()
if body:
var event_data = get_event_data(body)
if event_data.event != "keep-alive" and event_data.event != continue_internal:
var result = Utilities.get_json_data(event_data.data)
if result != null:
var parsed_text = result
if response_body.size() > 0: # stop here if the value doesn't parse
response_body.resize(0)
new_sse_event.emit(headers, event_data.event, result)
else:
if event_data.event != continue_internal:
response_body.resize(0)
func _process(delta):
if !told_to_connect:
return
if !is_connected:
if !connection_in_progress:
attempt_to_connect()
connection_in_progress = true
return
httpclient.poll()
var httpclient_status = httpclient.get_status()
if !is_requested:
attempt_to_request(httpclient_status)
return
if httpclient.has_response() or httpclient_status == HTTPClient.STATUS_BODY:
var headers = httpclient.get_response_headers_as_dictionary()
if httpclient_status == HTTPClient.STATUS_BODY:
httpclient.poll()
var chunk = httpclient.read_response_body_chunk()
if(chunk.size() == 0):
return
else:
response_body = response_body + chunk
_parse_response_body(headers)
elif Firebase.emulating and Firebase._config.workarounds.database_connection_closed_issue:
# Emulation does not send the close connection header currently, so we need to manually read the response body
# see issue https://github.com/firebase/firebase-tools/issues/3329 in firebase-tools
# also comment https://github.com/GodotNuts/GodotFirebase/issues/154#issuecomment-831377763 which explains the issue
while httpclient.connection.get_available_bytes():
var data = httpclient.connection.get_partial_data(1)
if data[0] == OK:
response_body.append_array(data[1])
if response_body.size() > 0:
_parse_response_body(headers)
func get_event_data(body : String):
var result = {}
var event_idx = body.find(event_tag)
if event_idx == -1:
result["event"] = continue_internal
return result
assert(event_idx != -1)
var data_idx = body.find(data_tag, event_idx + event_tag.length())
assert(data_idx != -1)
var event = body.substr(event_idx, data_idx)
var event_value = event.replace(event_tag, "").strip_edges()
assert(event_value)
assert(event_value.length() > 0)
result["event"] = event_value
var data = body.right(body.length() - (data_idx + data_tag.length())).strip_edges()
assert(data)
assert(data.length() > 0)
result["data"] = data
return result
func _exit_tree():
if httpclient:
httpclient.close()