Initial Commit
This commit is contained in:
commit
0321a1b77e
10 changed files with 774 additions and 0 deletions
189
test_node.gd
Normal file
189
test_node.gd
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
extends Node2D
|
||||
class_name DebuggableHttpRequest
|
||||
|
||||
|
||||
signal request_completed(response: HTTPResponse)
|
||||
|
||||
|
||||
@export_category("HTTP Server")
|
||||
@export var host: String = "192.168.1.29"
|
||||
@export var port: int = 80
|
||||
@export var query: String = "/"
|
||||
@export var ssl: bool = false
|
||||
@export_category("HTTP Headers")
|
||||
@export var user_agent: String = "Godot/4.4.1 (Windows)"
|
||||
@export var accept: String = "*/*"
|
||||
|
||||
|
||||
var client: StreamPeerTCP
|
||||
var sclient: StreamPeerTLS
|
||||
var state: State = State.CLOSED
|
||||
var buffer: PackedByteArray
|
||||
var headers: PackedStringArray
|
||||
|
||||
|
||||
enum State {
|
||||
CONNECTING,
|
||||
REQUESTING,
|
||||
RESPONSE,
|
||||
ERROR,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
func _ready() -> void:
|
||||
request()
|
||||
request_completed.connect(func(resp: HTTPResponse) -> void:
|
||||
print_rich("[color=green][b]Request Completed:[/b] Status ", resp.code, "[/color]\n")
|
||||
print_rich("[color=cyan]Headers:\n[/color]")
|
||||
print_rich("[color=cyan]", JSON.stringify(resp.headers, "\t"), "[/color]\n")
|
||||
print("Body:")
|
||||
print(resp.body.get_string_from_utf8())
|
||||
)
|
||||
|
||||
|
||||
func request(path: String = "") -> void:
|
||||
assert(state == State.CLOSED, "A request is already being made.")
|
||||
if path != "":
|
||||
query = path
|
||||
if port == 443:
|
||||
ssl = true
|
||||
print_rich("[color=yellow]Starting connection to %s:%d[/color]\n" % [host, port])
|
||||
client = StreamPeerTCP.new()
|
||||
client.connect_to_host(host, port)
|
||||
state = State.CONNECTING
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not client:
|
||||
return
|
||||
|
||||
if state == State.CLOSED:
|
||||
if ssl and sclient:
|
||||
sclient.disconnect_from_stream()
|
||||
sclient = null
|
||||
client.disconnect_from_host()
|
||||
client = null
|
||||
return
|
||||
|
||||
client.poll()
|
||||
if client.get_status() == StreamPeerTCP.Status.STATUS_CONNECTED and state == State.CONNECTING:
|
||||
if ssl:
|
||||
_handle_ssl()
|
||||
return
|
||||
print_rich("[color=green]Connected to %s:%d[/color]\n" % [host, port])
|
||||
state = State.REQUESTING
|
||||
print_rich("[color=green]Sending Request for %s from server %s:%d[/color]\n" % [query, host, port])
|
||||
_send_request(client)
|
||||
elif client.get_status() == StreamPeerTCP.Status.STATUS_CONNECTED and state == State.REQUESTING:
|
||||
print_rich("[color=green]Reading response from %s:%d[/color]\n" % [host, port])
|
||||
state = State.RESPONSE
|
||||
_handle_response(sclient if ssl else client)
|
||||
elif client.get_status() == StreamPeerTCP.Status.STATUS_CONNECTED and state == State.RESPONSE:
|
||||
_handle_response(sclient if ssl else client)
|
||||
elif client.get_status() == StreamPeerTCP.Status.STATUS_ERROR:
|
||||
state = State.ERROR
|
||||
_handle_error(client.get_status())
|
||||
|
||||
|
||||
func _send_request(peer: StreamPeer) -> void:
|
||||
var request_str := "GET %s://%s:%d/%s HTTP/1.1\r\n" % [
|
||||
"https" if ssl else "http",
|
||||
host, port, query
|
||||
]
|
||||
if (ssl and port == 443) or (not ssl and port == 80):
|
||||
request_str += "Host: %s\r\n" % host
|
||||
else:
|
||||
request_str += "Host: %s:%d\r\n" % [host,port]
|
||||
# TODO: Handle Body Length here
|
||||
#"3.1.4.stable.mono.official"
|
||||
var ver := Engine.get_version_info()
|
||||
var version := "%d.%d.%d.%s.%s" % [
|
||||
ver["major"], ver["minor"], ver["patch"],
|
||||
ver["status"], ver["build"]
|
||||
]
|
||||
request_str += "User-Agent: GodotEngine/%s (%s)" % [
|
||||
version,
|
||||
OS.get_name()
|
||||
]
|
||||
request_str += "Accept: %s\r\n\r\n" % accept
|
||||
peer.put_data(request_str.to_utf8_buffer())
|
||||
|
||||
func _handle_response(peer: StreamPeer) -> void:
|
||||
peer.poll()
|
||||
|
||||
if headers.size() == 0:
|
||||
while true:
|
||||
var byte := peer.get_partial_data(1)
|
||||
if byte[0] == OK and byte[1].size() == 0:
|
||||
peer.poll()
|
||||
continue
|
||||
elif byte[0] == OK:
|
||||
buffer.push_back(byte[1][0])
|
||||
else:
|
||||
state = State.ERROR
|
||||
_handle_error(byte[0])
|
||||
return
|
||||
if buffer.size() < 4:
|
||||
peer.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
|
||||
peer.poll()
|
||||
|
||||
if headers.size() == 0:
|
||||
return
|
||||
|
||||
var chunk := peer.get_partial_data(1024)
|
||||
if chunk[0] == OK and chunk[1].size() > 0:
|
||||
buffer.append_array(chunk[1])
|
||||
else:
|
||||
var resp = HTTPResponse.new(headers, buffer)
|
||||
request_completed.emit(resp)
|
||||
state = State.CLOSED
|
||||
|
||||
|
||||
func _handle_error(error: Variant) -> void:
|
||||
print_rich("[color=brickred]Error: %s[/color]" % error)
|
||||
|
||||
|
||||
func _handle_ssl() -> void:
|
||||
if sclient == null:
|
||||
print_rich("[color=yellow]SSL Connection requested, negotiating SSL Connection with %s:%d[/color]\n" % [host,port])
|
||||
sclient = StreamPeerTLS.new()
|
||||
sclient.connect_to_stream(client, host)
|
||||
|
||||
sclient.poll()
|
||||
|
||||
if sclient.get_status() == StreamPeerTLS.Status.STATUS_CONNECTED:
|
||||
print_rich("[color=yellow]SSL Connection established, Sending Request for %s from server %s:%d[/color]" % [query, host, port])
|
||||
state = State.REQUESTING
|
||||
_send_request(sclient)
|
||||
elif sclient.get_status() == StreamPeerTLS.Status.STATUS_ERROR or \
|
||||
sclient.get_status() == StreamPeerTLS.Status.STATUS_ERROR_HOSTNAME_MISMATCH:
|
||||
_handle_error(sclient.get_status())
|
||||
|
||||
|
||||
class HTTPResponse:
|
||||
var headers: Dictionary[String, String] = {}
|
||||
var body: PackedByteArray = []
|
||||
var code: String
|
||||
var message: String
|
||||
|
||||
func _init(p_headers: PackedStringArray, p_buffer: PackedByteArray) -> void:
|
||||
body = p_buffer
|
||||
for line in p_headers:
|
||||
if line.contains(": "):
|
||||
var parts := line.split(": ")
|
||||
headers[parts[0]] = parts[1]
|
||||
else:
|
||||
# HTTP Intro Line: HTTP/1.1 200 OK
|
||||
var parts := Array(line.split(" "))
|
||||
if parts.pop_front() == "HTTP/1.1":
|
||||
code = parts.pop_front()
|
||||
message = " ".join(parts)
|
||||
else:
|
||||
code = "INVALID_PROTOCOL"
|
||||
Loading…
Add table
Add a link
Reference in a new issue