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}"