Skip to content

Instantly share code, notes, and snippets.

@freehuntx
Last active January 20, 2024 15:07
Show Gist options
  • Save freehuntx/4170405eeb4729a6cb817ea7afa3be0e to your computer and use it in GitHub Desktop.
Save freehuntx/4170405eeb4729a6cb817ea7afa3be0e to your computer and use it in GitHub Desktop.
Godot HTTP
class_name Http extends RefCounted
class Response:
var code := -1
var headers := {}
var error := ""
var text := ""
var json = null
func _init(opts:={}):
for key in opts.keys():
self[key] = opts[key]
func _to_string() -> String:
return JSON.stringify({"code":code,"headers":headers,"error":error,"text":text,"json":json})
static func parse_url(url):
var regex := RegEx.new()
regex.compile("^(https?)://([^:/]+)(:\\d+)?([^?#]*)?(\\?[^#]*)?(\\#.*)?$")
var match := regex.search(url)
if match == null: return null
var proto = match.get_string(1)
var host = match.get_string(2)
var port = match.get_string(3)
var path = match.get_string(4)
var search = match.get_string(5)
var hashstr = match.get_string(6)
if port != "":
port = int(port.substr(1))
else:
port = 443 if proto == "https" else 80
if path == "": path = "/"
return { proto=proto, host=host, port=port, path=path, search=search, hash=hashstr }
static func request_get(url: String, headers:={}, search:={}) -> Response:
return await request({ method=HTTPClient.METHOD_GET, url=url, headers=headers, search=search })
static func request_post(url: String, data=null, headers:={}, search:={}) -> Response:
var options = {
method=HTTPClient.METHOD_POST,
url=url,
headers=headers,
search=search
}
if data != null:
if typeof(data) == TYPE_DICTIONARY or typeof(data) == TYPE_ARRAY:
options.headers['Content-Type'] = 'application/json'
options.body = JSON.stringify(data)
else:
options.headers['Content-Type'] = 'application/text'
options.body = str(data)
return await request(options)
static func request(options) -> Response:
var method = HTTPClient.METHOD_GET if not "method" in options else options.method
var url = options.url
var headers = {} if not "headers" in options else options.headers
var search = {} if not "search" in options else options.search # TODO: Implement this
var body = "" if not "body" in options else options.body
var packed_headers = []
for key in headers:
packed_headers.append("%s: %s" % [key, headers[key]])
var parsed_url = parse_url(url)
var http := HTTPClient.new()
var err := http.connect_to_host(parsed_url.host, parsed_url.port, TLSOptions.client() if parsed_url.proto == "https" else null)
if err != OK:
return Response.new({ "error": "CONNECTION_FAILED" })
while http.get_status() == HTTPClient.STATUS_CONNECTING or http.get_status() == HTTPClient.STATUS_RESOLVING:
await Engine.get_main_loop().process_frame
http.poll()
if http.get_status() != HTTPClient.STATUS_CONNECTED:
return Response.new({ "error": "CONNECTION_FAILED" })
err = http.request(method, parsed_url.path + parsed_url.search, packed_headers, body)
if err != OK:
return Response.new({ "error": "REQUEST_FAILED" })
while http.get_status() == HTTPClient.STATUS_REQUESTING:
await Engine.get_main_loop().process_frame
http.poll()
if http.get_status() != HTTPClient.STATUS_BODY and http.get_status() != HTTPClient.STATUS_CONNECTED:
return Response.new({ "error": "GETTING_RESPONSE_FAILED" })
if not http.has_response():
return null
var response := Response.new({
"code": http.get_response_code(),
"headers": http.get_response_headers_as_dictionary()
})
var buffer = PackedByteArray()
while http.get_status() == HTTPClient.STATUS_BODY:
await Engine.get_main_loop().process_frame
http.poll()
var chunk = http.read_response_body_chunk()
if chunk.size() > 0:
buffer += chunk
response.text = buffer.get_string_from_ascii()
if "Content-Type" in response.headers and response.headers["Content-Type"].contains("application/json"):
response.json = JSON.parse_string(response.text)
return response
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment