Skip to content

Commit bf6f368

Browse files
authored
v2.0.0
2 parents 3aef0fe + 03642ba commit bf6f368

File tree

10 files changed

+213
-79
lines changed

10 files changed

+213
-79
lines changed

.gitattributes

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
* text=auto eol=lf
22

3-
/** export-ignore
4-
/AwaitableHTTPRequest.gd !export-ignore
3+
/.gitattributes export-ignore
4+
/README.md export-ignore

.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

AwaitableHTTPRequest.gd

Lines changed: 0 additions & 73 deletions
This file was deleted.

README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
1-
The AwaitableHTTPRequest Node allows you to make a HTTP request with one line of code!
1+
<img alt="project icon: syntax-highlighted text reading 'await http dot request()'" src="addons/awaitable_http_request/icon.png" width="64">
22

3-
![Usage](example.png)
3+
## AwaitableHTTPRequest Node for Godot 4
44

5-
This script can be found on the [Godot AssetLib](https://godotengine.org/asset-library/asset/2502).
5+
This addon makes HTTP requests much more convenient to use by introducing the `await`-syntax and removing the need for signals.
6+
7+
### Usage
8+
9+
Here is an example with minimal error-handling:
10+
11+
```py
12+
@export var http: AwaitableHTTPRequest
13+
14+
func _ready() -> void:
15+
var resp := await http.async_request("https://api.github.com/users/swarkin")
16+
if resp.success():
17+
print(resp.status) # 200
18+
print(resp.headers["content-type"]) # application/json
19+
20+
var json := resp.body_as_json()
21+
print(json["login"]) # Swarkin
22+
```
23+
24+
See `examples.tscn` for more.
25+
26+
---
27+
28+
> Available on the [Godot Asset Library](https://godotengine.org/asset-library/asset/2502)
29+
30+
<sub>Requires Godot 4.1</sub>
File renamed without changes.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
class_name AwaitableHTTPRequest
2+
extends HTTPRequest
3+
## [img width=64]res://addons/awaitable_http_request/icon.png[/img] [url=https://github.com/Swarkin/Godot-AwaitableHTTPRequest]AwaitableHTTPRequest[/url] 2.0.0 by Swarkin & [url=https://github.com/Swarkin/Godot-AwaitableHTTPRequest/graphs/contributors]contributors[/url].
4+
# View the formatted documentation in Godot by pressing F1 and typing "AwaitableHTTPRequest"!
5+
6+
signal request_finished ## Emits once the current request finishes, right after [member is_requesting] is set to false.
7+
var is_requesting := false ## Whether the node is busy performing a request. This variable is read-only.
8+
9+
## Performs an awaitable HTTP request.[br]
10+
## Take a look at the [code]examples.tscn[/code] scene in the addon directory for inspiration![br]
11+
## [br]
12+
## [b]Note:[/b] Header names will be in lowercase, as some web servers prefer this approach and them being case-insensitive as per specification. Therefore, it is good practice to not rely on capitalization.
13+
## [br]
14+
## Here is an example with minimal error-handling:
15+
## [codeblock]
16+
## @export var http: AwaitableHTTPRequest
17+
##
18+
## func _ready() -> void:
19+
## var resp := await http.async_request("https://api.github.com/users/swarkin")
20+
## if resp.success():
21+
## print(resp.status) # 200
22+
## print(resp.headers["content-type"]) # application/json
23+
##
24+
## var json := resp.body_as_json()
25+
## print(json["login"]) # Swarkin
26+
## [/codeblock]
27+
func async_request(url: String, custom_headers := PackedStringArray(), method := HTTPClient.Method.METHOD_GET, request_data := "") -> HTTPResult:
28+
if is_requesting:
29+
push_error("AwaitableHTTPRequest is busy performing a request.")
30+
return
31+
32+
is_requesting = true
33+
34+
var err := request(url, custom_headers, method, request_data)
35+
if err:
36+
return HTTPResult._from_error(err)
37+
38+
@warning_ignore("unsafe_cast")
39+
var result := await request_completed as Array
40+
is_requesting = false
41+
request_finished.emit()
42+
43+
return HTTPResult._from_array(result)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
[gd_scene load_steps=3 format=3 uid="uid://bnqsp15ey3wrl"]
2+
3+
[ext_resource type="Script" path="res://addons/awaitable_http_request/awaitable_http_request.gd" id="1_b05hb"]
4+
5+
[sub_resource type="GDScript" id="GDScript_qphse"]
6+
resource_name = "example"
7+
script/source = "extends Node
8+
9+
@export var http: AwaitableHTTPRequest
10+
11+
12+
func _ready() -> void:
13+
print(\"Example 1: JSON API\")
14+
var data := await request_api()
15+
if not data.is_empty():
16+
var user := data[\"login\"] as String
17+
print(\"User: \", user)
18+
19+
print(\"\\nExample 2: Downloading an image\")
20+
var bytes := await request_image()
21+
if not bytes.is_empty():
22+
# Snippet for loading a PackedByteArray into an Image,
23+
# as well as an ImageTexture to use in your app/game.
24+
#var img := Image.new()
25+
#img.load_png_from_buffer(bytes)
26+
#var tex := ImageTexture.create_from_image(img)
27+
28+
var path := OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS)+\"/image.png\"
29+
var file := FileAccess.open(path, FileAccess.WRITE)
30+
if not file:
31+
push_error(\"Failed to save image.\")
32+
return
33+
34+
file.store_buffer(bytes)
35+
print(\"Downloaded and saved a random image to %s, take a look!\" % path)
36+
37+
38+
#region Example 1: JSON API
39+
func request_api() -> Dictionary:
40+
var resp := await http.async_request(
41+
\"https://api.github.com/users/swarkin\",
42+
PackedStringArray([ # headers
43+
\"accept: application/vnd.github+json\",
44+
\"user-agent: Swarkin/AwaitableHTTPRequest/2.0.0\",
45+
]),
46+
)
47+
48+
if !resp.success() or resp.status_err():
49+
push_error(\"Request failed.\")
50+
return {}
51+
52+
print(\"Status code: \", resp.status)
53+
print(\"Content-Type:\", resp.headers[\"content-type\"])
54+
55+
var json := resp.body_as_json()
56+
if not json:
57+
push_error(\"JSON invalid.\")
58+
return {}
59+
60+
return json as Dictionary
61+
#endregion
62+
63+
#region Example 2: Downloading an image
64+
func request_image() -> PackedByteArray:
65+
var resp := await http.async_request(\"https://picsum.photos/256\")
66+
if !resp.success() or resp.status_err():
67+
push_error(\"Request failed.\")
68+
return PackedByteArray()
69+
70+
return resp.bytes
71+
#endregion
72+
"
73+
74+
[node name="Press F6 to run examples" type="Node" node_paths=PackedStringArray("http")]
75+
editor_description = "This scene is not required and may be deleted freely."
76+
script = SubResource("GDScript_qphse")
77+
http = NodePath("AwaitableHTTPRequest")
78+
79+
[node name="AwaitableHTTPRequest" type="HTTPRequest" parent="."]
80+
script = ExtResource("1_b05hb")
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
class_name HTTPResult
2+
extends RefCounted
3+
## A dataclass returned by [method AwaitableHTTPRequest.async_request].
4+
5+
var _error: Error ## Contains the [method HTTPRequest.request] error, [constant Error.OK] otherwise. See also [method success].[br](For advanced use-cases)
6+
var _result: HTTPRequest.Result ## Contains the [annotation HTTPRequest] error, [constant HTTPRequest.RESULT_SUCCESS] otherwise. See also [method success].[br](For advanced use-cases)
7+
var status: int ## The response status code.
8+
var headers: Dictionary ## The response headers.
9+
var bytes: PackedByteArray ## The response body as a [PackedByteArray].[br][b]Note:[/b] Any [Array] is always passed by reference.
10+
11+
## Checks whether the HTTP request succeeded, meaning [member _error] and [member _result] aren't in an error state.[br]
12+
## [b]Note:[/b] This does not check the response [member status] code.
13+
func success() -> bool:
14+
return _error == OK and _result == HTTPRequest.RESULT_SUCCESS
15+
16+
## Checks whether the [member status] is between 200 and 299 (inclusive), see [url]https://developer.mozilla.org/en-US/docs/Web/HTTP/Status[/url].
17+
func status_ok() -> bool:
18+
return status >= 200 and status < 300
19+
20+
## Checks whether the [member status] is between 400 and 599 (inclusive), see [url]https://developer.mozilla.org/en-US/docs/Web/HTTP/Status[/url].
21+
func status_err() -> bool:
22+
return status >= 400 and status < 599
23+
24+
## The response body as a [String].[br]
25+
## For other formatting (ascii, utf16, ...) or special use-cases (file I/O, ...), it is possible to access the raw body's [member bytes].[br]
26+
## You should cache this return value instead of calling the funciton multiple times.
27+
func body_as_string() -> String:
28+
return bytes.get_string_from_utf8()
29+
30+
## Attempt to parse the response [member bytes] into a [Dictionary] or [Array], returns null on failure.[br][br]
31+
## It is possible to cast the return type to a [Dictionary] with "[code]as Dictionary[/code]" to receive autocomplete and other benefits when the parsing was successful.[br]
32+
## If you want error handling for the JSON deserialization, make an instance of [JSON] and call [method JSON.parse] on it, passing in the return value of [method HTTPResult.body_as_string]. This allows the usage of [method JSON.get_error_message] and [method JSON.get_error_line] to get potential error information.[br][br]
33+
## [b]Note:[/b] Godot always converts JSON numbers to [float]s!
34+
func body_as_json() -> Variant:
35+
return JSON.parse_string(body_as_string())
36+
37+
# Constructs a new [HTTPResult] from an [enum @GlobalScope.Error] code. (Used internally, hidden from API list)
38+
static func _from_error(err: Error) -> HTTPResult:
39+
var h := HTTPResult.new()
40+
h._error = err
41+
return h
42+
43+
# Constructs a new [HTTPResult] from the return value of [signal HTTPRequest.request_completed]. (Used internally, hidden from API list)
44+
@warning_ignore("unsafe_cast")
45+
static func _from_array(a: Array) -> HTTPResult:
46+
var h := HTTPResult.new()
47+
h._result = a[0] as HTTPRequest.Result
48+
h.status = a[1] as int
49+
h.headers = _headers_to_dict(a[2] as PackedStringArray)
50+
h.bytes = a[3] as PackedByteArray
51+
return h
52+
53+
# Converts a [PackedStringArray] of headers into a [Dictionary]. The header names will be in lowercase, as some web servers prefer this approach and them being case-insensitive as per specification. Therefore, it is good practice to not rely on capitalization. (Used internally, hidden from API list)
54+
static func _headers_to_dict(headers_arr: PackedStringArray) -> Dictionary:
55+
var dict := {}
56+
for h in headers_arr:
57+
var split := h.split(":")
58+
dict[split[0].to_lower()] = split[1].strip_edges()
59+
60+
return dict
File renamed without changes.

example.png

-38.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)