Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/platform/web/http_client_web.cpp
10277 views
1
/**************************************************************************/
2
/* http_client_web.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "http_client_web.h"
32
33
void HTTPClientWeb::_parse_headers(int p_len, const char **p_headers, void *p_ref) {
34
HTTPClientWeb *client = static_cast<HTTPClientWeb *>(p_ref);
35
for (int i = 0; i < p_len; i++) {
36
client->response_headers.push_back(String::utf8(p_headers[i]));
37
}
38
}
39
40
Error HTTPClientWeb::connect_to_host(const String &p_host, int p_port, Ref<TLSOptions> p_tls_options) {
41
ERR_FAIL_COND_V(p_tls_options.is_valid() && p_tls_options->is_server(), ERR_INVALID_PARAMETER);
42
43
close();
44
45
port = p_port;
46
use_tls = p_tls_options.is_valid();
47
48
host = p_host;
49
50
String host_lower = host.to_lower();
51
if (host_lower.begins_with("http://")) {
52
host = host.substr(7);
53
use_tls = false;
54
} else if (host_lower.begins_with("https://")) {
55
use_tls = true;
56
host = host.substr(8);
57
}
58
59
ERR_FAIL_COND_V(host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER);
60
61
if (port < 0) {
62
if (use_tls) {
63
port = PORT_HTTPS;
64
} else {
65
port = PORT_HTTP;
66
}
67
}
68
69
status = host.is_valid_ip_address() ? STATUS_CONNECTING : STATUS_RESOLVING;
70
71
return OK;
72
}
73
74
void HTTPClientWeb::set_connection(const Ref<StreamPeer> &p_connection) {
75
ERR_FAIL_MSG("Accessing an HTTPClientWeb's StreamPeer is not supported for the Web platform.");
76
}
77
78
Ref<StreamPeer> HTTPClientWeb::get_connection() const {
79
ERR_FAIL_V_MSG(Ref<RefCounted>(), "Accessing an HTTPClientWeb's StreamPeer is not supported for the Web platform.");
80
}
81
82
Error HTTPClientWeb::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) {
83
ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
84
ERR_FAIL_COND_V_MSG(p_method == METHOD_TRACE || p_method == METHOD_CONNECT, ERR_UNAVAILABLE, "HTTP methods TRACE and CONNECT are not supported for the Web platform.");
85
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
86
ERR_FAIL_COND_V(host.is_empty(), ERR_UNCONFIGURED);
87
ERR_FAIL_COND_V(port < 0, ERR_UNCONFIGURED);
88
ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
89
90
Error err = verify_headers(p_headers);
91
if (err) {
92
return err;
93
}
94
95
String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url;
96
Vector<CharString> keeper;
97
Vector<const char *> c_strings;
98
for (int i = 0; i < p_headers.size(); i++) {
99
keeper.push_back(p_headers[i].utf8());
100
c_strings.push_back(keeper[i].get_data());
101
}
102
if (js_id) {
103
godot_js_fetch_free(js_id);
104
}
105
js_id = godot_js_fetch_create(_methods[p_method], url.utf8().get_data(), c_strings.ptrw(), c_strings.size(), p_body, p_body_len);
106
status = STATUS_REQUESTING;
107
return OK;
108
}
109
110
void HTTPClientWeb::close() {
111
host = "";
112
port = -1;
113
use_tls = false;
114
status = STATUS_DISCONNECTED;
115
polled_response_code = 0;
116
response_headers.resize(0);
117
response_buffer.resize(0);
118
if (js_id) {
119
godot_js_fetch_free(js_id);
120
js_id = 0;
121
}
122
}
123
124
HTTPClientWeb::Status HTTPClientWeb::get_status() const {
125
return status;
126
}
127
128
bool HTTPClientWeb::has_response() const {
129
return response_headers.size() > 0;
130
}
131
132
bool HTTPClientWeb::is_response_chunked() const {
133
return godot_js_fetch_is_chunked(js_id);
134
}
135
136
int HTTPClientWeb::get_response_code() const {
137
return polled_response_code;
138
}
139
140
Error HTTPClientWeb::get_response_headers(List<String> *r_response) {
141
if (!response_headers.size()) {
142
return ERR_INVALID_PARAMETER;
143
}
144
for (int i = 0; i < response_headers.size(); i++) {
145
r_response->push_back(response_headers[i]);
146
}
147
response_headers.clear();
148
return OK;
149
}
150
151
int64_t HTTPClientWeb::get_response_body_length() const {
152
// Body length cannot be consistently retrieved from the web.
153
// Reading the "content-length" value will return a meaningless value when the response is compressed,
154
// as reading will return uncompressed chunks in any case, resulting in a mismatch between the detected
155
// body size and the actual size returned by repeatedly calling read_response_body_chunk.
156
// Additionally, while "content-length" is considered a safe CORS header, "content-encoding" is not,
157
// so using the "content-encoding" to decide if "content-length" is meaningful is not an option either.
158
// We simply must accept the fact that browsers are awful when it comes to networking APIs.
159
// See GH-47597, and GH-79327.
160
return -1;
161
}
162
163
PackedByteArray HTTPClientWeb::read_response_body_chunk() {
164
ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
165
166
if (response_buffer.size() != read_limit) {
167
response_buffer.resize(read_limit);
168
}
169
int read = godot_js_fetch_read_chunk(js_id, response_buffer.ptrw(), read_limit);
170
171
// Check if the stream is over.
172
godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id);
173
if (state == GODOT_JS_FETCH_STATE_DONE) {
174
status = STATUS_DISCONNECTED;
175
} else if (state != GODOT_JS_FETCH_STATE_BODY) {
176
status = STATUS_CONNECTION_ERROR;
177
}
178
179
PackedByteArray chunk;
180
if (!read) {
181
return chunk;
182
}
183
chunk.resize(read);
184
memcpy(chunk.ptrw(), response_buffer.ptr(), read);
185
return chunk;
186
}
187
188
void HTTPClientWeb::set_blocking_mode(bool p_enable) {
189
ERR_FAIL_COND_MSG(p_enable, "HTTPClientWeb blocking mode is not supported for the Web platform.");
190
}
191
192
bool HTTPClientWeb::is_blocking_mode_enabled() const {
193
return false;
194
}
195
196
void HTTPClientWeb::set_read_chunk_size(int p_size) {
197
read_limit = p_size;
198
}
199
200
int HTTPClientWeb::get_read_chunk_size() const {
201
return read_limit;
202
}
203
204
Error HTTPClientWeb::poll() {
205
switch (status) {
206
case STATUS_DISCONNECTED:
207
return ERR_UNCONFIGURED;
208
209
case STATUS_RESOLVING:
210
status = STATUS_CONNECTING;
211
return OK;
212
213
case STATUS_CONNECTING:
214
status = STATUS_CONNECTED;
215
return OK;
216
217
case STATUS_CONNECTED:
218
return OK;
219
220
case STATUS_BODY: {
221
godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id);
222
if (state == GODOT_JS_FETCH_STATE_DONE) {
223
status = STATUS_DISCONNECTED;
224
} else if (state != GODOT_JS_FETCH_STATE_BODY) {
225
status = STATUS_CONNECTION_ERROR;
226
return ERR_CONNECTION_ERROR;
227
}
228
return OK;
229
}
230
231
case STATUS_CONNECTION_ERROR:
232
return ERR_CONNECTION_ERROR;
233
234
case STATUS_REQUESTING: {
235
#ifdef DEBUG_ENABLED
236
// forcing synchronous requests is not possible on the web
237
if (last_polling_frame == Engine::get_singleton()->get_process_frames()) {
238
WARN_PRINT("HTTPClientWeb polled multiple times in one frame, "
239
"but request cannot progress more than once per "
240
"frame on the Web platform.");
241
}
242
last_polling_frame = Engine::get_singleton()->get_process_frames();
243
#endif
244
245
polled_response_code = godot_js_fetch_http_status_get(js_id);
246
godot_js_fetch_state_t js_state = godot_js_fetch_state_get(js_id);
247
if (js_state == GODOT_JS_FETCH_STATE_REQUESTING) {
248
return OK;
249
} else if (js_state == GODOT_JS_FETCH_STATE_ERROR) {
250
// Fetch is in error state.
251
status = STATUS_CONNECTION_ERROR;
252
return ERR_CONNECTION_ERROR;
253
}
254
if (godot_js_fetch_read_headers(js_id, &_parse_headers, this)) {
255
// Failed to parse headers.
256
status = STATUS_CONNECTION_ERROR;
257
return ERR_CONNECTION_ERROR;
258
}
259
status = STATUS_BODY;
260
break;
261
}
262
263
default:
264
ERR_FAIL_V(ERR_BUG);
265
}
266
return OK;
267
}
268
269
HTTPClient *HTTPClientWeb::_create_func(bool p_notify_postinitialize) {
270
return static_cast<HTTPClient *>(ClassDB::creator<HTTPClientWeb>(p_notify_postinitialize));
271
}
272
273
HTTPClient *(*HTTPClient::_create)(bool p_notify_postinitialize) = HTTPClientWeb::_create_func;
274
275
HTTPClientWeb::HTTPClientWeb() {
276
}
277
278
HTTPClientWeb::~HTTPClientWeb() {
279
close();
280
}
281
282