Created Sammi API Plugin
Created code for Sammi API plugin
This commit is contained in:
parent
9a4e0230c2
commit
759a19fcf1
8 changed files with 596 additions and 0 deletions
162
addons/sammi/lib/sammi_client.gd
Normal file
162
addons/sammi/lib/sammi_client.gd
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
extends Node
|
||||
class_name SammiClient
|
||||
|
||||
## Creates a HTTP Client connection to communicate with SAMMI. Due to current restrictions
|
||||
## Godot expects Content-Length or transfer-enoding: chunked headers to be set properly, otherwise
|
||||
## Godot assumes there is no body, and doesn't read it. SAMMI's custom internal HTTP Server does
|
||||
## not set the Content-Length header for response headers to the API. Hence it will fail with a
|
||||
## normal Godot HTTPClient.
|
||||
|
||||
signal request_completed(response: SammiResponse)
|
||||
|
||||
@export_category("SAMMI Connection Information")
|
||||
@export var host: String = "127.0.0.1"
|
||||
@export var port: int = 9450
|
||||
@export var user_agent: String = "GodotSammi/0.1 (%s)" % OS.get_name()
|
||||
@export_category("Authentication")
|
||||
@export var password: String = ""
|
||||
|
||||
var client: StreamPeerTCP
|
||||
var state: State = State.CLOSED
|
||||
var buffer: PackedByteArray
|
||||
var headers: PackedStringArray
|
||||
var response: SammiResponse
|
||||
var query: String = ""
|
||||
var method: Method = Method.GET
|
||||
var body: String = ""
|
||||
|
||||
enum State {
|
||||
CONNECTING,
|
||||
REQUESTING,
|
||||
RESPONSE,
|
||||
ERROR,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
enum Method {
|
||||
GET,
|
||||
POST,
|
||||
}
|
||||
|
||||
func request(method: Method, api_path: String, body: String) -> void:
|
||||
assert(state == State.CLOSED, "A request is in the process of being made.")
|
||||
|
||||
self.query = api_path
|
||||
self.body = body
|
||||
self.client = StreamPeerTCP.new()
|
||||
self.client.connect_to_host(host, port)
|
||||
self.state = State.CONNECTING
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not client:
|
||||
return
|
||||
|
||||
if state == State.CLOSED:
|
||||
client.disconnect_from_host()
|
||||
client = null
|
||||
return
|
||||
|
||||
client.poll()
|
||||
if client.get_status() == StreamPeerTCP.Status.STATUS_CONNECTED and state == State.CONNECTING:
|
||||
state = State.REQUESTING
|
||||
_send_request()
|
||||
elif client.get_status() == StreamPeerTCP.Status.STATUS_CONNECTED and state == State.REQUESTING:
|
||||
state = State.RESPONSE
|
||||
_read_response()
|
||||
elif client.get_status() == StreamPeerTCP.Status.STATUS_CONNECTED and state == State.RESPONSE:
|
||||
_read_response()
|
||||
elif client.get_status() == StreamPeerTCP.Status.STATUS_ERROR:
|
||||
state = State.ERROR
|
||||
print("Error: ", client.get_error())
|
||||
client.disconnect_from_host()
|
||||
client = null
|
||||
|
||||
func _send_request() -> void:
|
||||
var request_str = ""
|
||||
if method == Method.GET:
|
||||
request_str = "GET %s HTTP/1.1\r\n" % query
|
||||
else:
|
||||
request_str = "POST %s HTTP/1.1\r\n" % query
|
||||
|
||||
|
||||
request_str += "Host: %s:%d\r\n" % [host, port]
|
||||
request_str += "User-Agent: %s\r\n" % user_agent
|
||||
request_str += "Accept: */*\r\n"
|
||||
if password != "":
|
||||
request_str += "Authorization: %s\r\n" % password
|
||||
if method == Method.POST and body != "":
|
||||
request_str += "Content-Type: application/json\r\n"
|
||||
request_str += "Content-Length: %d\r\n" % body.length()
|
||||
|
||||
request_str += "\r\n"
|
||||
|
||||
if method == Method.POST and body != "":
|
||||
request_str += body
|
||||
client.put_data(request_str.to_utf8_buffer())
|
||||
|
||||
func _read_response() -> void:
|
||||
client.poll()
|
||||
|
||||
if headers.size() == 0:
|
||||
while true:
|
||||
var byte := client.get_partial_data(1)
|
||||
if byte[0] == OK and byte[1].size() == 0:
|
||||
await get_tree().process_frame
|
||||
client.poll()
|
||||
continue
|
||||
elif byte[0] == OK:
|
||||
buffer.push_back(byte[1][0])
|
||||
else:
|
||||
state = State.ERROR
|
||||
print("Error reading response headers.")
|
||||
return
|
||||
|
||||
if buffer.size() < 4:
|
||||
await get_tree().process_frame
|
||||
client.poll()
|
||||
continue
|
||||
|
||||
if (buffer[-2] == 10 and buffer[-1] == 10) or \
|
||||
(buffer[-4] == 13 and buffer[-3] == 10 and buffer[-2] == 13 and buffer[-1] == 10):
|
||||
var data := buffer.get_string_from_utf8()
|
||||
buffer = []
|
||||
headers = data.replace("\r", "").rstrip("\n\n").split("\n")
|
||||
break
|
||||
await get_tree().process_frame
|
||||
client.poll()
|
||||
|
||||
if headers.size() == 0:
|
||||
return
|
||||
|
||||
var chunk := client.get_partial_data(1024)
|
||||
if chunk[0] == OK and chunk[1].size() > 0:
|
||||
buffer.append_array(chunk[1])
|
||||
else:
|
||||
response = SammiResponse.new(headers, buffer, query)
|
||||
request_completed.emit(response)
|
||||
state = State.CLOSED
|
||||
|
||||
|
||||
class SammiResponse:
|
||||
var code: int
|
||||
var headers: Dictionary
|
||||
var body: String
|
||||
var query: String
|
||||
var error_message: String
|
||||
|
||||
func _init(p_headers: PackedStringArray, p_buffer: PackedByteArray, p_query: String) -> void:
|
||||
body = p_buffer.get_string_from_utf8()
|
||||
headers = {}
|
||||
for line in p_headers:
|
||||
if line.contains(": "):
|
||||
var parts := line.split(": ")
|
||||
headers[parts[0]] = parts[1]
|
||||
else:
|
||||
var parts := Array(line.split(" "))
|
||||
if parts.pop_front() == "HTTP/1.1":
|
||||
code = int(parts.pop_front())
|
||||
error_message = " ".join(parts)
|
||||
else:
|
||||
code = 500
|
||||
error_message = "Unknown Error"
|
||||
query = p_query
|
||||
Loading…
Add table
Add a link
Reference in a new issue