1/**************************************************************************/
2/* http_request.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_request.h"
32#include "core/io/compression.h"
33#include "scene/main/timer.h"
34
35Error HTTPRequest::_request() {
36 return client->connect_to_host(url, port, use_tls ? tls_options : nullptr);
37}
38
39Error HTTPRequest::_parse_url(const String &p_url) {
40 use_tls = false;
41 request_string = "";
42 port = 80;
43 request_sent = false;
44 got_response = false;
45 body_len = -1;
46 body.clear();
47 downloaded.set(0);
48 final_body_size.set(0);
49 redirections = 0;
50
51 String scheme;
52 Error err = p_url.parse_url(scheme, url, port, request_string);
53 ERR_FAIL_COND_V_MSG(err != OK, err, "Error parsing URL: " + p_url + ".");
54 if (scheme == "https://") {
55 use_tls = true;
56 } else if (scheme != "http://") {
57 ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Invalid URL scheme: " + scheme + ".");
58 }
59 if (port == 0) {
60 port = use_tls ? 443 : 80;
61 }
62 if (request_string.is_empty()) {
63 request_string = "/";
64 }
65 return OK;
66}
67
68bool HTTPRequest::has_header(const PackedStringArray &p_headers, const String &p_header_name) {
69 bool exists = false;
70
71 String lower_case_header_name = p_header_name.to_lower();
72 for (int i = 0; i < p_headers.size() && !exists; i++) {
73 String sanitized = p_headers[i].strip_edges().to_lower();
74 if (sanitized.begins_with(lower_case_header_name)) {
75 exists = true;
76 }
77 }
78
79 return exists;
80}
81
82String HTTPRequest::get_header_value(const PackedStringArray &p_headers, const String &p_header_name) {
83 String value = "";
84
85 String lowwer_case_header_name = p_header_name.to_lower();
86 for (int i = 0; i < p_headers.size(); i++) {
87 if (p_headers[i].find(":") > 0) {
88 Vector<String> parts = p_headers[i].split(":", false, 1);
89 if (parts.size() > 1 && parts[0].strip_edges().to_lower() == lowwer_case_header_name) {
90 value = parts[1].strip_edges();
91 break;
92 }
93 }
94 }
95
96 return value;
97}
98
99Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_headers, HTTPClient::Method p_method, const String &p_request_data) {
100 // Copy the string into a raw buffer.
101 Vector<uint8_t> raw_data;
102
103 CharString charstr = p_request_data.utf8();
104 size_t len = charstr.length();
105 if (len > 0) {
106 raw_data.resize(len);
107 uint8_t *w = raw_data.ptrw();
108 memcpy(w, charstr.ptr(), len);
109 }
110
111 return request_raw(p_url, p_custom_headers, p_method, raw_data);
112}
113
114Error HTTPRequest::request_raw(const String &p_url, const Vector<String> &p_custom_headers, HTTPClient::Method p_method, const Vector<uint8_t> &p_request_data_raw) {
115 ERR_FAIL_COND_V(!is_inside_tree(), ERR_UNCONFIGURED);
116 ERR_FAIL_COND_V_MSG(requesting, ERR_BUSY, "HTTPRequest is processing a request. Wait for completion or cancel it before attempting a new one.");
117
118 if (timeout > 0) {
119 timer->stop();
120 timer->start(timeout);
121 }
122
123 method = p_method;
124
125 Error err = _parse_url(p_url);
126 if (err) {
127 return err;
128 }
129
130 headers = p_custom_headers;
131
132 if (accept_gzip) {
133 // If the user has specified an Accept-Encoding header, don't overwrite it.
134 if (!has_header(headers, "Accept-Encoding")) {
135 headers.push_back("Accept-Encoding: gzip, deflate");
136 }
137 }
138
139 request_data = p_request_data_raw;
140
141 requesting = true;
142
143 if (use_threads.is_set()) {
144 thread_done.clear();
145 thread_request_quit.clear();
146 client->set_blocking_mode(true);
147 thread.start(_thread_func, this);
148 } else {
149 client->set_blocking_mode(false);
150 err = _request();
151 if (err != OK) {
152 _defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray());
153 return ERR_CANT_CONNECT;
154 }
155
156 set_process_internal(true);
157 }
158
159 return OK;
160}
161
162void HTTPRequest::_thread_func(void *p_userdata) {
163 HTTPRequest *hr = static_cast<HTTPRequest *>(p_userdata);
164
165 Error err = hr->_request();
166
167 if (err != OK) {
168 hr->_defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray());
169 } else {
170 while (!hr->thread_request_quit.is_set()) {
171 bool exit = hr->_update_connection();
172 if (exit) {
173 break;
174 }
175 OS::get_singleton()->delay_usec(1);
176 }
177 }
178
179 hr->thread_done.set();
180}
181
182void HTTPRequest::cancel_request() {
183 timer->stop();
184
185 if (!requesting) {
186 return;
187 }
188
189 if (!use_threads.is_set()) {
190 set_process_internal(false);
191 } else {
192 thread_request_quit.set();
193 if (thread.is_started()) {
194 thread.wait_to_finish();
195 }
196 }
197
198 file.unref();
199 decompressor.unref();
200 client->close();
201 body.clear();
202 got_response = false;
203 response_code = -1;
204 request_sent = false;
205 requesting = false;
206}
207
208bool HTTPRequest::_handle_response(bool *ret_value) {
209 if (!client->has_response()) {
210 _defer_done(RESULT_NO_RESPONSE, 0, PackedStringArray(), PackedByteArray());
211 *ret_value = true;
212 return true;
213 }
214
215 got_response = true;
216 response_code = client->get_response_code();
217 List<String> rheaders;
218 client->get_response_headers(&rheaders);
219 response_headers.clear();
220 downloaded.set(0);
221 final_body_size.set(0);
222 decompressor.unref();
223
224 for (const String &E : rheaders) {
225 response_headers.push_back(E);
226 }
227
228 if (response_code == 301 || response_code == 302) {
229 // Handle redirect.
230
231 if (max_redirects >= 0 && redirections >= max_redirects) {
232 _defer_done(RESULT_REDIRECT_LIMIT_REACHED, response_code, response_headers, PackedByteArray());
233 *ret_value = true;
234 return true;
235 }
236
237 String new_request;
238
239 for (const String &E : rheaders) {
240 if (E.findn("Location: ") != -1) {
241 new_request = E.substr(9, E.length()).strip_edges();
242 }
243 }
244
245 if (!new_request.is_empty()) {
246 // Process redirect.
247 client->close();
248 int new_redirs = redirections + 1; // Because _request() will clear it.
249 Error err;
250 if (new_request.begins_with("http")) {
251 // New url, new request.
252 _parse_url(new_request);
253 } else {
254 request_string = new_request;
255 }
256
257 err = _request();
258 if (err == OK) {
259 request_sent = false;
260 got_response = false;
261 body_len = -1;
262 body.clear();
263 downloaded.set(0);
264 final_body_size.set(0);
265 redirections = new_redirs;
266 *ret_value = false;
267 return true;
268 }
269 }
270 }
271
272 // Check if we need to start streaming decompression.
273 String content_encoding;
274 if (accept_gzip) {
275 content_encoding = get_header_value(response_headers, "Content-Encoding").to_lower();
276 }
277 if (content_encoding == "gzip") {
278 decompressor.instantiate();
279 decompressor->start_decompression(false, get_download_chunk_size());
280 } else if (content_encoding == "deflate") {
281 decompressor.instantiate();
282 decompressor->start_decompression(true, get_download_chunk_size());
283 }
284
285 return false;
286}
287
288bool HTTPRequest::_update_connection() {
289 switch (client->get_status()) {
290 case HTTPClient::STATUS_DISCONNECTED: {
291 _defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray());
292 return true; // End it, since it's disconnected.
293 } break;
294 case HTTPClient::STATUS_RESOLVING: {
295 client->poll();
296 // Must wait.
297 return false;
298 } break;
299 case HTTPClient::STATUS_CANT_RESOLVE: {
300 _defer_done(RESULT_CANT_RESOLVE, 0, PackedStringArray(), PackedByteArray());
301 return true;
302
303 } break;
304 case HTTPClient::STATUS_CONNECTING: {
305 client->poll();
306 // Must wait.
307 return false;
308 } break; // Connecting to IP.
309 case HTTPClient::STATUS_CANT_CONNECT: {
310 _defer_done(RESULT_CANT_CONNECT, 0, PackedStringArray(), PackedByteArray());
311 return true;
312
313 } break;
314 case HTTPClient::STATUS_CONNECTED: {
315 if (request_sent) {
316 if (!got_response) {
317 // No body.
318
319 bool ret_value;
320
321 if (_handle_response(&ret_value)) {
322 return ret_value;
323 }
324
325 _defer_done(RESULT_SUCCESS, response_code, response_headers, PackedByteArray());
326 return true;
327 }
328 if (body_len < 0) {
329 // Chunked transfer is done.
330 _defer_done(RESULT_SUCCESS, response_code, response_headers, body);
331 return true;
332 }
333
334 _defer_done(RESULT_CHUNKED_BODY_SIZE_MISMATCH, response_code, response_headers, PackedByteArray());
335 return true;
336 // Request might have been done.
337 } else {
338 // Did not request yet, do request.
339
340 int size = request_data.size();
341 Error err = client->request(method, request_string, headers, size > 0 ? request_data.ptr() : nullptr, size);
342 if (err != OK) {
343 _defer_done(RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray());
344 return true;
345 }
346
347 request_sent = true;
348 return false;
349 }
350 } break; // Connected: break requests only accepted here.
351 case HTTPClient::STATUS_REQUESTING: {
352 // Must wait, still requesting.
353 client->poll();
354 return false;
355
356 } break; // Request in progress.
357 case HTTPClient::STATUS_BODY: {
358 if (!got_response) {
359 bool ret_value;
360
361 if (_handle_response(&ret_value)) {
362 return ret_value;
363 }
364
365 if (!client->is_response_chunked() && client->get_response_body_length() == 0) {
366 _defer_done(RESULT_SUCCESS, response_code, response_headers, PackedByteArray());
367 return true;
368 }
369
370 // No body len (-1) if chunked or no content-length header was provided.
371 // Change your webserver configuration if you want body len.
372 body_len = client->get_response_body_length();
373
374 if (body_size_limit >= 0 && body_len > body_size_limit) {
375 _defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray());
376 return true;
377 }
378
379 if (!download_to_file.is_empty()) {
380 file = FileAccess::open(download_to_file, FileAccess::WRITE);
381 if (file.is_null()) {
382 _defer_done(RESULT_DOWNLOAD_FILE_CANT_OPEN, response_code, response_headers, PackedByteArray());
383 return true;
384 }
385 }
386 }
387
388 client->poll();
389 if (client->get_status() != HTTPClient::STATUS_BODY) {
390 return false;
391 }
392
393 PackedByteArray chunk;
394 if (decompressor.is_null()) {
395 // Chunk can be read directly.
396 chunk = client->read_response_body_chunk();
397 downloaded.add(chunk.size());
398 } else {
399 // Chunk is the result of decompression.
400 PackedByteArray compressed = client->read_response_body_chunk();
401 downloaded.add(compressed.size());
402
403 int pos = 0;
404 int left = compressed.size();
405 while (left) {
406 int w = 0;
407 Error err = decompressor->put_partial_data(compressed.ptr() + pos, left, w);
408 if (err == OK) {
409 PackedByteArray dc;
410 dc.resize(decompressor->get_available_bytes());
411 err = decompressor->get_data(dc.ptrw(), dc.size());
412 chunk.append_array(dc);
413 }
414 if (err != OK) {
415 _defer_done(RESULT_BODY_DECOMPRESS_FAILED, response_code, response_headers, PackedByteArray());
416 return true;
417 }
418 // We need this check here because a "zip bomb" could result in a chunk of few kilos decompressing into gigabytes of data.
419 if (body_size_limit >= 0 && final_body_size.get() + chunk.size() > body_size_limit) {
420 _defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray());
421 return true;
422 }
423 pos += w;
424 left -= w;
425 }
426 }
427 final_body_size.add(chunk.size());
428
429 if (body_size_limit >= 0 && final_body_size.get() > body_size_limit) {
430 _defer_done(RESULT_BODY_SIZE_LIMIT_EXCEEDED, response_code, response_headers, PackedByteArray());
431 return true;
432 }
433
434 if (chunk.size()) {
435 if (file.is_valid()) {
436 const uint8_t *r = chunk.ptr();
437 file->store_buffer(r, chunk.size());
438 if (file->get_error() != OK) {
439 _defer_done(RESULT_DOWNLOAD_FILE_WRITE_ERROR, response_code, response_headers, PackedByteArray());
440 return true;
441 }
442 } else {
443 body.append_array(chunk);
444 }
445 }
446
447 if (body_len >= 0) {
448 if (downloaded.get() == body_len) {
449 _defer_done(RESULT_SUCCESS, response_code, response_headers, body);
450 return true;
451 }
452 } else if (client->get_status() == HTTPClient::STATUS_DISCONNECTED) {
453 // We read till EOF, with no errors. Request is done.
454 _defer_done(RESULT_SUCCESS, response_code, response_headers, body);
455 return true;
456 }
457
458 return false;
459
460 } break; // Request resulted in body: break which must be read.
461 case HTTPClient::STATUS_CONNECTION_ERROR: {
462 _defer_done(RESULT_CONNECTION_ERROR, 0, PackedStringArray(), PackedByteArray());
463 return true;
464 } break;
465 case HTTPClient::STATUS_TLS_HANDSHAKE_ERROR: {
466 _defer_done(RESULT_TLS_HANDSHAKE_ERROR, 0, PackedStringArray(), PackedByteArray());
467 return true;
468 } break;
469 }
470
471 ERR_FAIL_V(false);
472}
473
474void HTTPRequest::_defer_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data) {
475 call_deferred(SNAME("_request_done"), p_status, p_code, p_headers, p_data);
476}
477
478void HTTPRequest::_request_done(int p_status, int p_code, const PackedStringArray &p_headers, const PackedByteArray &p_data) {
479 cancel_request();
480
481 emit_signal(SNAME("request_completed"), p_status, p_code, p_headers, p_data);
482}
483
484void HTTPRequest::_notification(int p_what) {
485 switch (p_what) {
486 case NOTIFICATION_INTERNAL_PROCESS: {
487 if (use_threads.is_set()) {
488 return;
489 }
490 bool done = _update_connection();
491 if (done) {
492 set_process_internal(false);
493 }
494 } break;
495
496 case NOTIFICATION_EXIT_TREE: {
497 if (requesting) {
498 cancel_request();
499 }
500 } break;
501 }
502}
503
504void HTTPRequest::set_use_threads(bool p_use) {
505 ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED);
506 use_threads.set_to(p_use);
507}
508
509bool HTTPRequest::is_using_threads() const {
510 return use_threads.is_set();
511}
512
513void HTTPRequest::set_accept_gzip(bool p_gzip) {
514 accept_gzip = p_gzip;
515}
516
517bool HTTPRequest::is_accepting_gzip() const {
518 return accept_gzip;
519}
520
521void HTTPRequest::set_body_size_limit(int p_bytes) {
522 ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED);
523
524 body_size_limit = p_bytes;
525}
526
527int HTTPRequest::get_body_size_limit() const {
528 return body_size_limit;
529}
530
531void HTTPRequest::set_download_file(const String &p_file) {
532 ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED);
533
534 download_to_file = p_file;
535}
536
537String HTTPRequest::get_download_file() const {
538 return download_to_file;
539}
540
541void HTTPRequest::set_download_chunk_size(int p_chunk_size) {
542 ERR_FAIL_COND(get_http_client_status() != HTTPClient::STATUS_DISCONNECTED);
543
544 client->set_read_chunk_size(p_chunk_size);
545}
546
547int HTTPRequest::get_download_chunk_size() const {
548 return client->get_read_chunk_size();
549}
550
551HTTPClient::Status HTTPRequest::get_http_client_status() const {
552 return client->get_status();
553}
554
555void HTTPRequest::set_max_redirects(int p_max) {
556 max_redirects = p_max;
557}
558
559int HTTPRequest::get_max_redirects() const {
560 return max_redirects;
561}
562
563int HTTPRequest::get_downloaded_bytes() const {
564 return downloaded.get();
565}
566
567int HTTPRequest::get_body_size() const {
568 return body_len;
569}
570
571void HTTPRequest::set_http_proxy(const String &p_host, int p_port) {
572 client->set_http_proxy(p_host, p_port);
573}
574
575void HTTPRequest::set_https_proxy(const String &p_host, int p_port) {
576 client->set_https_proxy(p_host, p_port);
577}
578
579void HTTPRequest::set_timeout(double p_timeout) {
580 ERR_FAIL_COND(p_timeout < 0);
581 timeout = p_timeout;
582}
583
584double HTTPRequest::get_timeout() {
585 return timeout;
586}
587
588void HTTPRequest::_timeout() {
589 cancel_request();
590 _defer_done(RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray());
591}
592
593void HTTPRequest::set_tls_options(const Ref<TLSOptions> &p_options) {
594 ERR_FAIL_COND(p_options.is_null() || p_options->is_server());
595 tls_options = p_options;
596}
597
598void HTTPRequest::_bind_methods() {
599 ClassDB::bind_method(D_METHOD("request", "url", "custom_headers", "method", "request_data"), &HTTPRequest::request, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(String()));
600 ClassDB::bind_method(D_METHOD("request_raw", "url", "custom_headers", "method", "request_data_raw"), &HTTPRequest::request_raw, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(PackedByteArray()));
601 ClassDB::bind_method(D_METHOD("cancel_request"), &HTTPRequest::cancel_request);
602 ClassDB::bind_method(D_METHOD("set_tls_options", "client_options"), &HTTPRequest::set_tls_options);
603
604 ClassDB::bind_method(D_METHOD("get_http_client_status"), &HTTPRequest::get_http_client_status);
605
606 ClassDB::bind_method(D_METHOD("set_use_threads", "enable"), &HTTPRequest::set_use_threads);
607 ClassDB::bind_method(D_METHOD("is_using_threads"), &HTTPRequest::is_using_threads);
608
609 ClassDB::bind_method(D_METHOD("set_accept_gzip", "enable"), &HTTPRequest::set_accept_gzip);
610 ClassDB::bind_method(D_METHOD("is_accepting_gzip"), &HTTPRequest::is_accepting_gzip);
611
612 ClassDB::bind_method(D_METHOD("set_body_size_limit", "bytes"), &HTTPRequest::set_body_size_limit);
613 ClassDB::bind_method(D_METHOD("get_body_size_limit"), &HTTPRequest::get_body_size_limit);
614
615 ClassDB::bind_method(D_METHOD("set_max_redirects", "amount"), &HTTPRequest::set_max_redirects);
616 ClassDB::bind_method(D_METHOD("get_max_redirects"), &HTTPRequest::get_max_redirects);
617
618 ClassDB::bind_method(D_METHOD("set_download_file", "path"), &HTTPRequest::set_download_file);
619 ClassDB::bind_method(D_METHOD("get_download_file"), &HTTPRequest::get_download_file);
620
621 ClassDB::bind_method(D_METHOD("get_downloaded_bytes"), &HTTPRequest::get_downloaded_bytes);
622 ClassDB::bind_method(D_METHOD("get_body_size"), &HTTPRequest::get_body_size);
623
624 ClassDB::bind_method(D_METHOD("_request_done"), &HTTPRequest::_request_done);
625
626 ClassDB::bind_method(D_METHOD("set_timeout", "timeout"), &HTTPRequest::set_timeout);
627 ClassDB::bind_method(D_METHOD("get_timeout"), &HTTPRequest::get_timeout);
628
629 ClassDB::bind_method(D_METHOD("set_download_chunk_size", "chunk_size"), &HTTPRequest::set_download_chunk_size);
630 ClassDB::bind_method(D_METHOD("get_download_chunk_size"), &HTTPRequest::get_download_chunk_size);
631
632 ClassDB::bind_method(D_METHOD("set_http_proxy", "host", "port"), &HTTPRequest::set_http_proxy);
633 ClassDB::bind_method(D_METHOD("set_https_proxy", "host", "port"), &HTTPRequest::set_https_proxy);
634
635 ADD_PROPERTY(PropertyInfo(Variant::STRING, "download_file", PROPERTY_HINT_FILE), "set_download_file", "get_download_file");
636 ADD_PROPERTY(PropertyInfo(Variant::INT, "download_chunk_size", PROPERTY_HINT_RANGE, "256,16777216,suffix:B"), "set_download_chunk_size", "get_download_chunk_size");
637 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_threads"), "set_use_threads", "is_using_threads");
638 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "accept_gzip"), "set_accept_gzip", "is_accepting_gzip");
639 ADD_PROPERTY(PropertyInfo(Variant::INT, "body_size_limit", PROPERTY_HINT_RANGE, "-1,2000000000,suffix:B"), "set_body_size_limit", "get_body_size_limit");
640 ADD_PROPERTY(PropertyInfo(Variant::INT, "max_redirects", PROPERTY_HINT_RANGE, "-1,64"), "set_max_redirects", "get_max_redirects");
641 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeout", PROPERTY_HINT_RANGE, "0,3600,0.1,or_greater,suffix:s"), "set_timeout", "get_timeout");
642
643 ADD_SIGNAL(MethodInfo("request_completed", PropertyInfo(Variant::INT, "result"), PropertyInfo(Variant::INT, "response_code"), PropertyInfo(Variant::PACKED_STRING_ARRAY, "headers"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "body")));
644
645 BIND_ENUM_CONSTANT(RESULT_SUCCESS);
646 BIND_ENUM_CONSTANT(RESULT_CHUNKED_BODY_SIZE_MISMATCH);
647 BIND_ENUM_CONSTANT(RESULT_CANT_CONNECT);
648 BIND_ENUM_CONSTANT(RESULT_CANT_RESOLVE);
649 BIND_ENUM_CONSTANT(RESULT_CONNECTION_ERROR);
650 BIND_ENUM_CONSTANT(RESULT_TLS_HANDSHAKE_ERROR);
651 BIND_ENUM_CONSTANT(RESULT_NO_RESPONSE);
652 BIND_ENUM_CONSTANT(RESULT_BODY_SIZE_LIMIT_EXCEEDED);
653 BIND_ENUM_CONSTANT(RESULT_BODY_DECOMPRESS_FAILED);
654 BIND_ENUM_CONSTANT(RESULT_REQUEST_FAILED);
655 BIND_ENUM_CONSTANT(RESULT_DOWNLOAD_FILE_CANT_OPEN);
656 BIND_ENUM_CONSTANT(RESULT_DOWNLOAD_FILE_WRITE_ERROR);
657 BIND_ENUM_CONSTANT(RESULT_REDIRECT_LIMIT_REACHED);
658 BIND_ENUM_CONSTANT(RESULT_TIMEOUT);
659}
660
661HTTPRequest::HTTPRequest() {
662 client = Ref<HTTPClient>(HTTPClient::create());
663 tls_options = TLSOptions::client();
664 timer = memnew(Timer);
665 timer->set_one_shot(true);
666 timer->connect("timeout", callable_mp(this, &HTTPRequest::_timeout));
667 add_child(timer);
668}
669