Skip to content

Commit 25add24

Browse files
Fix HTTPSSEClient to correctly parse all events sent in one message (#471)
1 parent 313ff17 commit 25add24

File tree

1 file changed

+118
-95
lines changed

1 file changed

+118
-95
lines changed

addons/http-sse-client/HTTPSSEClient.gd

Lines changed: 118 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -23,107 +23,130 @@ var is_requested = false
2323
var response_body = PackedByteArray()
2424

2525
func connect_to_host(domain : String, url_after_domain : String, port : int = -1, trusted_chain : X509Certificate = null, common_name_override : String = ""):
26-
process_mode = Node.PROCESS_MODE_INHERIT
27-
self.domain = domain
28-
self.url_after_domain = url_after_domain
29-
self.port = port
30-
self.trusted_chain = trusted_chain
31-
self.common_name_override = common_name_override
32-
told_to_connect = true
26+
process_mode = Node.PROCESS_MODE_INHERIT
27+
self.domain = domain
28+
self.url_after_domain = url_after_domain
29+
self.port = port
30+
self.trusted_chain = trusted_chain
31+
self.common_name_override = common_name_override
32+
told_to_connect = true
3333

3434
func attempt_to_connect():
35-
var tls_options = TLSOptions.client(trusted_chain, common_name_override)
36-
var err = httpclient.connect_to_host(domain, port, tls_options)
37-
if err == OK:
38-
connected.emit()
39-
is_connected = true
40-
else:
41-
connection_error.emit(str(err))
35+
var tls_options = TLSOptions.client(trusted_chain, common_name_override)
36+
var err = httpclient.connect_to_host(domain, port, tls_options)
37+
if err == OK:
38+
connected.emit()
39+
is_connected = true
40+
else:
41+
connection_error.emit(str(err))
4242

4343
func attempt_to_request(httpclient_status):
44-
if httpclient_status == HTTPClient.STATUS_CONNECTING or httpclient_status == HTTPClient.STATUS_RESOLVING:
45-
return
44+
if httpclient_status == HTTPClient.STATUS_CONNECTING or httpclient_status == HTTPClient.STATUS_RESOLVING:
45+
return
4646

47-
if httpclient_status == HTTPClient.STATUS_CONNECTED:
48-
var err = httpclient.request(HTTPClient.METHOD_POST, url_after_domain, ["Accept: text/event-stream"])
49-
if err == OK:
50-
is_requested = true
51-
52-
func _parse_response_body(headers):
53-
var body = response_body.get_string_from_utf8()
54-
if body:
55-
var event_data = get_event_data(body)
56-
if event_data.event != "keep-alive" and event_data.event != continue_internal:
57-
var result = Utilities.get_json_data(event_data.data)
58-
if result != null:
59-
var parsed_text = result
60-
if response_body.size() > 0: # stop here if the value doesn't parse
61-
response_body.resize(0)
62-
new_sse_event.emit(headers, event_data.event, result)
63-
else:
64-
if event_data.event != continue_internal:
65-
response_body.resize(0)
47+
if httpclient_status == HTTPClient.STATUS_CONNECTED:
48+
var err = httpclient.request(HTTPClient.METHOD_POST, url_after_domain, ["Accept: text/event-stream"])
49+
if err == OK:
50+
is_requested = true
6651

6752
func _process(delta):
68-
if !told_to_connect:
69-
return
70-
71-
if !is_connected:
72-
if !connection_in_progress:
73-
attempt_to_connect()
74-
connection_in_progress = true
75-
return
76-
77-
httpclient.poll()
78-
var httpclient_status = httpclient.get_status()
79-
if !is_requested:
80-
attempt_to_request(httpclient_status)
81-
return
82-
83-
if httpclient.has_response() or httpclient_status == HTTPClient.STATUS_BODY:
84-
var headers = httpclient.get_response_headers_as_dictionary()
85-
86-
if httpclient_status == HTTPClient.STATUS_BODY:
87-
httpclient.poll()
88-
var chunk = httpclient.read_response_body_chunk()
89-
if(chunk.size() == 0):
90-
return
91-
else:
92-
response_body = response_body + chunk
93-
94-
_parse_response_body(headers)
95-
96-
elif Firebase.emulating and Firebase._config.workarounds.database_connection_closed_issue:
97-
# Emulation does not send the close connection header currently, so we need to manually read the response body
98-
# see issue https://github.com/firebase/firebase-tools/issues/3329 in firebase-tools
99-
# also comment https://github.com/GodotNuts/GodotFirebase/issues/154#issuecomment-831377763 which explains the issue
100-
while httpclient.connection.get_available_bytes():
101-
var data = httpclient.connection.get_partial_data(1)
102-
if data[0] == OK:
103-
response_body.append_array(data[1])
104-
if response_body.size() > 0:
105-
_parse_response_body(headers)
106-
107-
func get_event_data(body : String):
108-
var result = {}
109-
var event_idx = body.find(event_tag)
110-
if event_idx == -1:
111-
result["event"] = continue_internal
112-
return result
113-
assert(event_idx != -1)
114-
var data_idx = body.find(data_tag, event_idx + event_tag.length())
115-
assert(data_idx != -1)
116-
var event = body.substr(event_idx, data_idx)
117-
var event_value = event.replace(event_tag, "").strip_edges()
118-
assert(event_value)
119-
assert(event_value.length() > 0)
120-
result["event"] = event_value
121-
var data = body.right(body.length() - (data_idx + data_tag.length())).strip_edges()
122-
assert(data)
123-
assert(data.length() > 0)
124-
result["data"] = data
125-
return result
53+
if !told_to_connect:
54+
return
55+
56+
if !is_connected:
57+
if !connection_in_progress:
58+
attempt_to_connect()
59+
connection_in_progress = true
60+
return
61+
62+
httpclient.poll()
63+
var httpclient_status = httpclient.get_status()
64+
if !is_requested:
65+
attempt_to_request(httpclient_status)
66+
return
67+
68+
if httpclient.has_response() or httpclient_status == HTTPClient.STATUS_BODY:
69+
var headers = httpclient.get_response_headers_as_dictionary()
70+
71+
if httpclient_status == HTTPClient.STATUS_BODY:
72+
httpclient.poll()
73+
var chunk = httpclient.read_response_body_chunk()
74+
if(chunk.size() == 0):
75+
return
76+
else:
77+
response_body = response_body + chunk
78+
79+
_parse_response_body(headers)
80+
81+
elif Firebase.emulating and Firebase._config.workarounds.database_connection_closed_issue:
82+
# Emulation does not send the close connection header currently, so we need to manually read the response body
83+
# see issue https://github.com/firebase/firebase-tools/issues/3329 in firebase-tools
84+
# also comment https://github.com/GodotNuts/GodotFirebase/issues/154#issuecomment-831377763 which explains the issue
85+
while httpclient.connection.get_available_bytes():
86+
var data = httpclient.connection.get_partial_data(1)
87+
if data[0] == OK:
88+
response_body.append_array(data[1])
89+
if response_body.size() > 0:
90+
_parse_response_body(headers)
91+
92+
func _parse_response_body(headers):
93+
var body = response_body.get_string_from_utf8()
94+
if body:
95+
var event_datas = get_event_data(body)
96+
var resize_response_body_to_zero_after_for_loop_flag = false
97+
for event_data in event_datas:
98+
if event_data.event != "keep-alive" and event_data.event != continue_internal:
99+
var result = Utilities.get_json_data(event_data.data)
100+
if result != null:
101+
var parsed_text = result
102+
if response_body.size() > 0:
103+
resize_response_body_to_zero_after_for_loop_flag = true
104+
new_sse_event.emit(headers, event_data.event, result)
105+
else:
106+
if event_data.event != continue_internal:
107+
response_body.resize(0)
108+
if resize_response_body_to_zero_after_for_loop_flag:
109+
response_body.resize(0)
110+
111+
func get_event_data(body : String) -> Array:
112+
var results = []
113+
var start_idx = 0
114+
115+
if body.find(event_tag, start_idx) == -1:
116+
return [{"event":continue_internal}]
117+
118+
while true:
119+
# Find the index of the next event tag
120+
var event_idx = body.find(event_tag, start_idx)
121+
if event_idx == -1:
122+
break # No more events found
123+
124+
# Find the index of the corresponding data tag
125+
var data_idx = body.find(data_tag, event_idx + event_tag.length())
126+
if data_idx == -1:
127+
break # No corresponding data found
128+
129+
# Extract the event
130+
var event_value = body.substr(event_idx + event_tag.length(), data_idx - (event_idx + event_tag.length())).strip_edges()
131+
if event_value == "":
132+
break # No valid event value found
133+
134+
# Extract the data
135+
var data_end = body.find(event_tag, data_idx) # Assume data ends at the next event tag
136+
if data_end == -1:
137+
data_end = body.length() # If no new event tag, read till the end of the body
138+
139+
var data_value = body.substr(data_idx + data_tag.length(), data_end - (data_idx + data_tag.length())).strip_edges()
140+
if data_value == "":
141+
break # No valid data found
142+
143+
# Append the event and data to results
144+
results.append({"event": event_value, "data": data_value})
145+
# Update the start index for the next iteration
146+
start_idx = data_end # Move past the current data section
147+
148+
return results
126149

127150
func _exit_tree():
128-
if httpclient:
129-
httpclient.close()
151+
if httpclient:
152+
httpclient.close()

0 commit comments

Comments
 (0)