You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +00:00
[WebSocket] Refactor websocket module.
This commit is a huge refactor of the websocket module. The module is really old, and some design choices had to be re-evaluated. The WebSocketClient and WebSocketServer classes are now gone, and WebSocketPeer can act as either client or server. The WebSocketMultiplayerPeer class is no longer abstract, and implements the Multiplayer API on top of the lower level WebSocketPeer. WebSocketPeer is now a "raw" peer, like StreamPeerTCP and StreamPeerTLS, so it emits no signal, and just needs polling to update its internal state. To use it as a client, simply call WebSocketPeer.coonect_to_url, then frequently poll the peer until STATE_OPEN is reached and then you can write or read from it, or STATE_CLOSED and then you can check the disconnect code and reason). To implement a server instead, a TCPServer must be created, and the accepted connections needs to be provided to WebSocketPeer.accept_stream (which will perform the HTTP handshake). A full example of a WebSocketServer using TLS will be provided in the demo repository.
This commit is contained in:
@@ -34,55 +34,116 @@
|
||||
|
||||
#include "core/io/ip.h"
|
||||
|
||||
void EMWSPeer::set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size) {
|
||||
peer_sock = p_sock;
|
||||
_in_buffer.resize(p_in_pkt_size, p_in_buf_size);
|
||||
_packet_buffer.resize((1 << p_in_buf_size));
|
||||
_out_buf_size = p_out_buf_size;
|
||||
void EMWSPeer::_esws_on_connect(void *p_obj, char *p_proto) {
|
||||
EMWSPeer *peer = static_cast<EMWSPeer *>(p_obj);
|
||||
peer->ready_state = STATE_OPEN;
|
||||
peer->selected_protocol.parse_utf8(p_proto);
|
||||
}
|
||||
|
||||
void EMWSPeer::set_write_mode(WriteMode p_mode) {
|
||||
write_mode = p_mode;
|
||||
}
|
||||
|
||||
EMWSPeer::WriteMode EMWSPeer::get_write_mode() const {
|
||||
return write_mode;
|
||||
}
|
||||
|
||||
Error EMWSPeer::read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_string) {
|
||||
void EMWSPeer::_esws_on_message(void *p_obj, const uint8_t *p_data, int p_data_size, int p_is_string) {
|
||||
EMWSPeer *peer = static_cast<EMWSPeer *>(p_obj);
|
||||
uint8_t is_string = p_is_string ? 1 : 0;
|
||||
return _in_buffer.write_packet(p_data, p_size, &is_string);
|
||||
peer->in_buffer.write_packet(p_data, p_data_size, &is_string);
|
||||
}
|
||||
|
||||
Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
|
||||
ERR_FAIL_COND_V(_out_buf_size && ((uint64_t)godot_js_websocket_buffered_amount(peer_sock) + p_buffer_size >= (1ULL << _out_buf_size)), ERR_OUT_OF_MEMORY);
|
||||
void EMWSPeer::_esws_on_error(void *p_obj) {
|
||||
EMWSPeer *peer = static_cast<EMWSPeer *>(p_obj);
|
||||
peer->ready_state = STATE_CLOSED;
|
||||
}
|
||||
|
||||
int is_bin = write_mode == WebSocketPeer::WRITE_MODE_BINARY ? 1 : 0;
|
||||
void EMWSPeer::_esws_on_close(void *p_obj, int p_code, const char *p_reason, int p_was_clean) {
|
||||
EMWSPeer *peer = static_cast<EMWSPeer *>(p_obj);
|
||||
peer->close_code = p_code;
|
||||
peer->close_reason.parse_utf8(p_reason);
|
||||
peer->ready_state = STATE_CLOSED;
|
||||
}
|
||||
|
||||
if (godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, is_bin) != 0) {
|
||||
return FAILED;
|
||||
Error EMWSPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate) {
|
||||
ERR_FAIL_COND_V(ready_state != STATE_CLOSED, ERR_ALREADY_IN_USE);
|
||||
_clear();
|
||||
|
||||
String host;
|
||||
String path;
|
||||
String scheme;
|
||||
int port = 0;
|
||||
Error err = p_url.parse_url(scheme, host, port, path);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url);
|
||||
|
||||
if (scheme.is_empty()) {
|
||||
scheme = "ws://";
|
||||
}
|
||||
ERR_FAIL_COND_V_MSG(scheme != "ws://" && scheme != "wss://", ERR_INVALID_PARAMETER, vformat("Invalid protocol: \"%s\" (must be either \"ws://\" or \"wss://\").", scheme));
|
||||
|
||||
String proto_string;
|
||||
for (int i = 0; i < supported_protocols.size(); i++) {
|
||||
if (i != 0) {
|
||||
proto_string += ",";
|
||||
}
|
||||
proto_string += supported_protocols[i];
|
||||
}
|
||||
|
||||
if (handshake_headers.size()) {
|
||||
WARN_PRINT_ONCE("Custom headers are not supported in Web platform.");
|
||||
}
|
||||
if (p_tls_certificate.is_valid()) {
|
||||
WARN_PRINT_ONCE("Custom SSL certificates are not supported in Web platform.");
|
||||
}
|
||||
|
||||
requested_url = scheme + host;
|
||||
|
||||
if (port && ((scheme == "ws://" && port != 80) || (scheme == "wss://" && port != 443))) {
|
||||
requested_url += ":" + String::num(port);
|
||||
}
|
||||
|
||||
peer_sock = godot_js_websocket_create(this, requested_url.utf8().get_data(), proto_string.utf8().get_data(), &_esws_on_connect, &_esws_on_message, &_esws_on_error, &_esws_on_close);
|
||||
if (peer_sock == -1) {
|
||||
return FAILED;
|
||||
}
|
||||
in_buffer.resize(nearest_shift(inbound_buffer_size), max_queued_packets);
|
||||
packet_buffer.resize(inbound_buffer_size);
|
||||
ready_state = STATE_CONNECTING;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error EMWSPeer::accept_stream(Ref<StreamPeer> p_stream) {
|
||||
WARN_PRINT_ONCE("Acting as WebSocket server is not supported in Web platforms.");
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
Error EMWSPeer::_send(const uint8_t *p_buffer, int p_buffer_size, bool p_binary) {
|
||||
ERR_FAIL_COND_V(outbound_buffer_size > 0 && (get_current_outbound_buffered_amount() + p_buffer_size >= outbound_buffer_size), ERR_OUT_OF_MEMORY);
|
||||
|
||||
if (godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, p_binary ? 1 : 0) != 0) {
|
||||
return FAILED;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error EMWSPeer::send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) {
|
||||
return _send(p_buffer, p_buffer_size, p_mode == WRITE_MODE_TEXT);
|
||||
}
|
||||
|
||||
Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
|
||||
return _send(p_buffer, p_buffer_size, true);
|
||||
}
|
||||
|
||||
Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
|
||||
if (_in_buffer.packets_left() == 0) {
|
||||
if (in_buffer.packets_left() == 0) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
Error err = _in_buffer.read_packet(_packet_buffer.ptrw(), _packet_buffer.size(), &_is_string, read);
|
||||
Error err = in_buffer.read_packet(packet_buffer.ptrw(), packet_buffer.size(), &was_string, read);
|
||||
ERR_FAIL_COND_V(err != OK, err);
|
||||
|
||||
*r_buffer = _packet_buffer.ptr();
|
||||
*r_buffer = packet_buffer.ptr();
|
||||
r_buffer_size = read;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int EMWSPeer::get_available_packet_count() const {
|
||||
return _in_buffer.packets_left();
|
||||
return in_buffer.packets_left();
|
||||
}
|
||||
|
||||
int EMWSPeer::get_current_outbound_buffered_amount() const {
|
||||
@@ -93,20 +154,66 @@ int EMWSPeer::get_current_outbound_buffered_amount() const {
|
||||
}
|
||||
|
||||
bool EMWSPeer::was_string_packet() const {
|
||||
return _is_string;
|
||||
return was_string;
|
||||
}
|
||||
|
||||
bool EMWSPeer::is_connected_to_host() const {
|
||||
return peer_sock != -1;
|
||||
void EMWSPeer::_clear() {
|
||||
if (peer_sock != -1) {
|
||||
godot_js_websocket_destroy(peer_sock);
|
||||
peer_sock = -1;
|
||||
}
|
||||
ready_state = STATE_CLOSED;
|
||||
was_string = 0;
|
||||
close_code = -1;
|
||||
close_reason.clear();
|
||||
selected_protocol.clear();
|
||||
requested_url.clear();
|
||||
in_buffer.clear();
|
||||
packet_buffer.clear();
|
||||
}
|
||||
|
||||
void EMWSPeer::close(int p_code, String p_reason) {
|
||||
if (peer_sock != -1) {
|
||||
godot_js_websocket_close(peer_sock, p_code, p_reason.utf8().get_data());
|
||||
if (p_code < 0) {
|
||||
if (peer_sock != -1) {
|
||||
godot_js_websocket_destroy(peer_sock);
|
||||
peer_sock = -1;
|
||||
}
|
||||
ready_state = STATE_CLOSED;
|
||||
}
|
||||
_is_string = 0;
|
||||
_in_buffer.clear();
|
||||
peer_sock = -1;
|
||||
if (ready_state == STATE_CONNECTING || ready_state == STATE_OPEN) {
|
||||
ready_state = STATE_CLOSING;
|
||||
if (peer_sock != -1) {
|
||||
godot_js_websocket_close(peer_sock, p_code, p_reason.utf8().get_data());
|
||||
} else {
|
||||
ready_state = STATE_CLOSED;
|
||||
}
|
||||
}
|
||||
in_buffer.clear();
|
||||
packet_buffer.clear();
|
||||
}
|
||||
|
||||
void EMWSPeer::poll() {
|
||||
// Automatically polled by the navigator.
|
||||
}
|
||||
|
||||
WebSocketPeer::State EMWSPeer::get_ready_state() const {
|
||||
return ready_state;
|
||||
}
|
||||
|
||||
int EMWSPeer::get_close_code() const {
|
||||
return close_code;
|
||||
}
|
||||
|
||||
String EMWSPeer::get_close_reason() const {
|
||||
return close_reason;
|
||||
}
|
||||
|
||||
String EMWSPeer::get_selected_protocol() const {
|
||||
return selected_protocol;
|
||||
}
|
||||
|
||||
String EMWSPeer::get_requested_url() const {
|
||||
return requested_url;
|
||||
}
|
||||
|
||||
IPAddress EMWSPeer::get_connected_host() const {
|
||||
@@ -122,11 +229,10 @@ void EMWSPeer::set_no_delay(bool p_enabled) {
|
||||
}
|
||||
|
||||
EMWSPeer::EMWSPeer() {
|
||||
close();
|
||||
}
|
||||
|
||||
EMWSPeer::~EMWSPeer() {
|
||||
close();
|
||||
_clear();
|
||||
}
|
||||
|
||||
#endif // WEB_ENABLED
|
||||
|
||||
Reference in New Issue
Block a user