|
@@ -0,0 +1,162 @@
|
|
|
+extends Node
|
|
|
+class_name WebSocketServer
|
|
|
+
|
|
|
+signal message_received(peer_id : int, message)
|
|
|
+signal client_connected(peer_id : int)
|
|
|
+signal client_disconnected(peer_id : int)
|
|
|
+
|
|
|
+@export var handshake_headers := PackedStringArray()
|
|
|
+@export var supported_protocols : PackedStringArray
|
|
|
+@export var handshake_timout := 3000
|
|
|
+@export var use_tls := false
|
|
|
+@export var tls_cert : X509Certificate
|
|
|
+@export var tls_key : CryptoKey
|
|
|
+@export var refuse_new_connections := false :
|
|
|
+ set(refuse):
|
|
|
+ if refuse:
|
|
|
+ pending_peers.clear()
|
|
|
+
|
|
|
+
|
|
|
+class PendingPeer:
|
|
|
+ var connect_time : int
|
|
|
+ var tcp : StreamPeerTCP
|
|
|
+ var connection : StreamPeer
|
|
|
+ var ws : WebSocketPeer
|
|
|
+
|
|
|
+ func _init(p_tcp: StreamPeerTCP):
|
|
|
+ tcp = p_tcp
|
|
|
+ connection = p_tcp
|
|
|
+ connect_time = Time.get_ticks_msec()
|
|
|
+
|
|
|
+
|
|
|
+var tcp_server := TCPServer.new()
|
|
|
+var pending_peers : Array[PendingPeer] = []
|
|
|
+var peers : Dictionary
|
|
|
+
|
|
|
+
|
|
|
+func listen(port : int) -> int:
|
|
|
+ assert(not tcp_server.is_listening())
|
|
|
+ return tcp_server.listen(port)
|
|
|
+
|
|
|
+
|
|
|
+func stop():
|
|
|
+ tcp_server.stop()
|
|
|
+ pending_peers.clear()
|
|
|
+ peers.clear()
|
|
|
+
|
|
|
+
|
|
|
+func send(peer_id, message) -> int:
|
|
|
+ var type = typeof(message)
|
|
|
+ if peer_id <= 0:
|
|
|
+ # Send to multiple peers, (zero = brodcast, negative = exclude one)
|
|
|
+ for id in peers:
|
|
|
+ if id == -peer_id:
|
|
|
+ continue
|
|
|
+ if type == TYPE_STRING:
|
|
|
+ peers[id].send_text(message)
|
|
|
+ else:
|
|
|
+ peers[id].put_packet(message)
|
|
|
+ return OK
|
|
|
+
|
|
|
+ assert(peers.has(peer_id))
|
|
|
+ var socket = peers[peer_id]
|
|
|
+ if type == TYPE_STRING:
|
|
|
+ return socket.send_text(message)
|
|
|
+ return socket.send(var_to_bytes(message))
|
|
|
+
|
|
|
+
|
|
|
+func get_message(peer_id) -> Variant:
|
|
|
+ assert(peers.has(peer_id))
|
|
|
+ var socket = peers[peer_id]
|
|
|
+ if socket.get_available_packet_count() < 1:
|
|
|
+ return null
|
|
|
+ var pkt = socket.get_packet()
|
|
|
+ if socket.was_string_packet():
|
|
|
+ return pkt.get_string_from_utf8()
|
|
|
+ return bytes_to_var(pkt)
|
|
|
+
|
|
|
+
|
|
|
+func has_message(peer_id) -> bool:
|
|
|
+ assert(peers.has(peer_id))
|
|
|
+ return peers[peer_id].get_available_packet_count() > 0
|
|
|
+
|
|
|
+
|
|
|
+func _create_peer() -> WebSocketPeer:
|
|
|
+ var ws = WebSocketPeer.new()
|
|
|
+ ws.supported_protocols = supported_protocols
|
|
|
+ ws.handshake_headers = handshake_headers
|
|
|
+ return ws
|
|
|
+
|
|
|
+
|
|
|
+func poll() -> void:
|
|
|
+ if not tcp_server.is_listening():
|
|
|
+ return
|
|
|
+ while not refuse_new_connections and tcp_server.is_connection_available():
|
|
|
+ var conn = tcp_server.take_connection()
|
|
|
+ assert(conn != null)
|
|
|
+ pending_peers.append(PendingPeer.new(conn))
|
|
|
+ var to_remove := []
|
|
|
+ for p in pending_peers:
|
|
|
+ if not _connect_pending(p):
|
|
|
+ if p.connect_time + handshake_timout < Time.get_ticks_msec():
|
|
|
+ # Timeout
|
|
|
+ to_remove.append(p)
|
|
|
+ continue # Still pending
|
|
|
+ to_remove.append(p)
|
|
|
+ for r in to_remove:
|
|
|
+ pending_peers.erase(r)
|
|
|
+ to_remove.clear()
|
|
|
+ for id in peers:
|
|
|
+ var p : WebSocketPeer = peers[id]
|
|
|
+ var packets = p.get_available_packet_count()
|
|
|
+ p.poll()
|
|
|
+ if p.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
|
|
+ client_disconnected.emit(id)
|
|
|
+ to_remove.append(id)
|
|
|
+ continue
|
|
|
+ while p.get_available_packet_count():
|
|
|
+ message_received.emit(id, get_message(id))
|
|
|
+ for r in to_remove:
|
|
|
+ peers.erase(r)
|
|
|
+ to_remove.clear()
|
|
|
+
|
|
|
+
|
|
|
+func _connect_pending(p: PendingPeer) -> bool:
|
|
|
+ if p.ws != null:
|
|
|
+ # Poll websocket client if doing handshake
|
|
|
+ p.ws.poll()
|
|
|
+ var state = p.ws.get_ready_state()
|
|
|
+ if state == WebSocketPeer.STATE_OPEN:
|
|
|
+ var id = randi_range(2, 1 << 30)
|
|
|
+ peers[id] = p.ws
|
|
|
+ client_connected.emit(id)
|
|
|
+ return true # Success.
|
|
|
+ elif state != WebSocketPeer.STATE_CONNECTING:
|
|
|
+ return true # Failure.
|
|
|
+ return false # Still connecting.
|
|
|
+ elif p.tcp.get_status() != StreamPeerTCP.STATUS_CONNECTED:
|
|
|
+ return true # TCP disconnected.
|
|
|
+ elif not use_tls:
|
|
|
+ # TCP is ready, create WS peer
|
|
|
+ p.ws = _create_peer()
|
|
|
+ p.ws.accept_stream(p.tcp)
|
|
|
+ return false # WebSocketPeer connection is pending.
|
|
|
+ else:
|
|
|
+ if p.connection == p.tcp:
|
|
|
+ assert(tls_key != null and tls_cert != null)
|
|
|
+ var tls = StreamPeerTLS.new()
|
|
|
+ tls.accept_stream(p.tcp, tls_key, tls_cert)
|
|
|
+ p.connection = tls
|
|
|
+ p.connection.poll()
|
|
|
+ var status = p.connection.get_status()
|
|
|
+ if status == StreamPeerTLS.STATUS_CONNECTED:
|
|
|
+ p.ws = _create_peer()
|
|
|
+ p.ws.accept_stream(p.connection)
|
|
|
+ return false # WebSocketPeer connection is pending.
|
|
|
+ if status != StreamPeerTLS.STATUS_HANDSHAKING:
|
|
|
+ return true # Failure.
|
|
|
+ return false
|
|
|
+
|
|
|
+
|
|
|
+func _process(delta):
|
|
|
+ poll()
|