From 48201acea4e6490b79675fae9791c048e8d37c12 Mon Sep 17 00:00:00 2001
From: Philippe PITTOLI
Date: Sat, 10 Aug 2019 15:31:19 +0200
Subject: [PATCH] websocketd: simpler protocol
---
src/websocketd.cr | 133 +++++++++++++++++-----------------------------
1 file changed, 49 insertions(+), 84 deletions(-)
diff --git a/src/websocketd.cr b/src/websocketd.cr
index 1c8e78f..4098358 100644
--- a/src/websocketd.cr
+++ b/src/websocketd.cr
@@ -55,25 +55,25 @@ end
class InstanceStorage
property service : IPC::SwitchingService
- property fdlist : Hash(Int32,String)
- property socklist : Hash(Int32, TCPSocket)
- property wssocklist : Hash(Int32, WebSocket)
+ property is_client : Hash(Int32,Bool)
property switchtable : Hash(Int32, Int32)
- property connectionlist : Hash(Int32, IPC::Connection)
+ property fdtotcpsocket : Hash(Int32, TCPSocket)
+ property fdtows : Hash(Int32, WebSocket)
+ property fdtoipcconnection : Hash(Int32, IPC::Connection)
def initialize (@service : IPC::SwitchingService)
# fdlist_client = [] of TCPSocket
- @fdlist = Hash(Int32,String).new
- @socklist = Hash(Int32, TCPSocket).new
- @wssocklist = Hash(Int32, WebSocket).new
+ @is_client = Hash(Int32,Bool).new
+ @fdtotcpsocket = Hash(Int32, TCPSocket).new
+ @fdtows = Hash(Int32, WebSocket).new
@switchtable = Hash(Int32, Int32).new
- @connectionlist = Hash(Int32, IPC::Connection).new
+ @fdtoipcconnection = Hash(Int32, IPC::Connection).new
end
def remove_fd (fdclient : Int32)
# 1. closing both the client and the service
fdservice = @switchtable[fdclient]?
- tcpfdc = @socklist[fdclient]
+ tcpfdc = @fdtotcpsocket[fdclient]
# 2. closing the TCP connections
tcpfdc.close unless tcpfdc.closed?
@@ -86,18 +86,18 @@ class InstanceStorage
fdc != fdclient && fds != fdclient
end
- # 6. removing the client and the service from fdlist
- @fdlist = @fdlist.select do |fd,v| fd != fdclient end
+ # 6. removing the client and the service from is_client
+ @is_client = @is_client.select do |fd,v| fd != fdclient end
unless fdservice.nil?
- service = @connectionlist[fdservice]
+ service = @fdtoipcconnection[fdservice]
service.close
@service.remove_fd (fdservice)
- @connectionlist = @connectionlist.select do |k, v|
+ @fdtoipcconnection = @fdtoipcconnection.select do |k, v|
k != fdservice
end
- @fdlist = @fdlist.select do |fd,v| fd != fdservice end
+ @is_client = @is_client.select do |fd,v| fd != fdservice end
end
end
end
@@ -122,9 +122,7 @@ def websocket_client_connection(client, context : InstanceStorage)
end
# FIXME: check they actually wanted to upgrade to websocket
-
key = request.headers["Sec-WebSocket-Key"]
-
response_key = Digest::SHA1.base64digest key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
# puts response_key
@@ -138,50 +136,55 @@ def websocket_client_connection(client, context : InstanceStorage)
headers = headers.map { |key, value| "#{key}: #{value[0]}\r\n" }.join
+ # requested service, fd
+ req_service = request.path.lchop
+ if req_service.empty?
+ client.close
+ puts "should send a PATH"
+ return
+ end
+
+ websocket_connection_procedure req_service, client.fd, context
+
# puts "#{headers_header}\n#{headers.to_s}\r\n"
client.send "#{headers_header}\n#{headers.to_s}\r\n"
wsclient = WebSocket.new client
- wsclient.send "are we websocket yet?"
# the client is still not connected to a service
- context.fdlist[client.fd] = "not connected"
+ context.is_client[client.fd] = true
# listen to the client's file descriptor
context.service << client.fd
# puts "#{CBLUE}new client: #{client.fd}#{CRESET}"
# registering the client into storing structures to avoid being garbage collected
- context.socklist[client.fd] = client
- context.wssocklist[client.fd] = wsclient
+ context.fdtotcpsocket[client.fd] = client
+ context.fdtows[client.fd] = wsclient
end
def closing_client (fdclient : Int, context : InstanceStorage)
- # puts "closing the client #{fdclient}"
+ puts "#{CBLUE}closing the client:#{CRESET} #{fdclient}"
context.remove_fd fdclient
end
# first message from the client: requested service name
-# 1. extracting the requested service from the message
-# 2. connection to the service
-# 3. listening on the service fd
-# 4. bounding both file descriptors (through switchtable hash)
-# 5. indicating that the client is connected (in fdlist)
-# 6. sending an ack to the client
-def websocket_connection_procedure (message : String, clientfd : Int32, context : InstanceStorage)
+# 1. connection to the service
+# 2. listening on the service fd
+# 3. bounding both file descriptors (through switchtable hash)
+# 4. indicating that the client is connected (in is_client)
+def websocket_connection_procedure (requested_service : String, clientfd : Int32, context : InstanceStorage)
begin
- # 1. reading the service name from the received message
- requested_service = message
-
# 2. establishing a connection to the service
newservice = IPC::Connection.new requested_service
- context.connectionlist[newservice.fd] = newservice
+ context.fdtoipcconnection[newservice.fd] = newservice
- # 3. listening on the service fd
+ # 3. listening on the client fd and the service fd
context.service << newservice.fd
# cannot perform automatic switching due to websockets headers
+ # future version of the libipc lib should include some workaround, probably
# service.switch.add fdclient, newservice.fd
# 4. bounding the client and the service fd
@@ -189,12 +192,8 @@ def websocket_connection_procedure (message : String, clientfd : Int32, context
context.switchtable[newservice.fd] = clientfd
# 5. the client is then connected, send it a message
- context.fdlist[clientfd] = "connected"
- context.fdlist[newservice.fd] = "service"
-
- # 6. ack
- wsclient = context.wssocklist[clientfd]
- wsclient.send "OK\n"
+ context.is_client[clientfd] = true
+ context.is_client[newservice.fd] = false
rescue e
puts "#{CRED}Exception during connection to the service:#{CRESET} #{e}"
closing_client clientfd, context
@@ -203,11 +202,10 @@ end
def websocket_switching_procedure (activefd : Int, context : InstanceStorage)
begin
-
- if context.fdlist[activefd] == "connected"
+ if context.is_client[activefd]
# The client is a WebSocket on top of a TCP connection
- client = context.socklist[activefd]
- wsclient = context.wssocklist[activefd]
+ client = context.fdtotcpsocket[activefd]
+ wsclient = context.fdtows[activefd]
begin
message = wsclient.read
rescue e
@@ -235,32 +233,30 @@ def websocket_switching_procedure (activefd : Int, context : InstanceStorage)
# XXX: this is not a TCP fd, but since behind the scene this is compatible, I'm hacking a bit
serv = WrappedTCPFileDescriptor.new(fd: fdservice, family: Socket::Family::INET)
- #serv = context.connectionlist[fdservice]
+ #serv = context.fdtoipcconnection[fdservice]
serv.send message
- elsif context.fdlist[activefd] == "service"
+ else
# puts "switching: service to client"
# service => client
fdclient = context.switchtable[activefd]
- wsclient = context.wssocklist[fdclient]
+ wsclient = context.fdtows[fdclient]
- serv = context.connectionlist[activefd]
+ serv = context.fdtoipcconnection[activefd]
message = serv.read
# puts "received message from service: #{message.to_s}"
buf = message.to_buffer
# print_hexa String.new(buf), "\033[31m Message to send to the client \033[00m "
wsclient.send buf
- else
- raise "cannot understand if the fd is a client or a service"
end
rescue e
puts "#{CRED}Exception during message transfer:#{CRESET} #{e}"
- if context.fdlist[activefd] == "service"
+ if context.is_client[activefd]
+ closing_client activefd, context
+ else
clientfd = context.switchtable[activefd]
closing_client clientfd, context
- elsif context.fdlist[activefd] == "service"
- closing_client activefd, context
end
end
@@ -289,39 +285,8 @@ service.loop do |event|
end
# 2. active fd != server fd
- # is this a service or a client?
activefd = event.connection.fd
- if context.fdlist[activefd] == "not connected"
-
- # since it's an external communication
- # we have to read the message here, it's not handled by default in libipc
- wsclient = context.wssocklist[activefd]
- begin
- message = wsclient.read
- rescue e
- puts "#{CRED}Exception (receiving a message)#{CRESET} #{e}"
- closing_client activefd, context
- next
- end
-
- if message.nil?
- puts "#{CBLUE}disconnection of client#{CRESET} #{event.connection.fd}"
- closing_client activefd, context
- next
- end
-
- # requested service, fd
- websocket_connection_procedure String.new(message), activefd, context
-
- elsif context.fdlist[activefd] == "connected"
- # the service is sending a message
- websocket_switching_procedure activefd, context
- elsif context.fdlist[activefd] == "service"
- # the service is sending a message
- websocket_switching_procedure activefd, context
- else
- raise "should not happen: client not recorded in fdlist"
- end
+ websocket_switching_procedure activefd, context
when IPC::Event::Switch
puts "\033[36mIPC::Event::Switch#{CRESET}: from fd #{event.connection.fd}"