From 608a467f89f9b3f9c799f9c315f1add29d2598de Mon Sep 17 00:00:00 2001 From: Philippe PITTOLI Date: Mon, 5 Aug 2019 01:53:35 +0200 Subject: [PATCH] playing with websockets --- src/debuggingws.cr | 220 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 src/debuggingws.cr diff --git a/src/debuggingws.cr b/src/debuggingws.cr new file mode 100644 index 0000000..20a622a --- /dev/null +++ b/src/debuggingws.cr @@ -0,0 +1,220 @@ +require "option_parser" +require "ipc" +require "socket" +require "./colors" +require "./ws" + +require "socket" +require "http" +require "http/server" +require "base64" +require "digest" +require "./utils" + +class IPC::Connection + def initialize(fd : Int32) + external_connection = LibIPC::Connection.new + external_connection.fd = fd + initialize(external_connection) + end +end + +class IPC::Message + def to_buffer + to_message @type, String.new(@payload) + end +end + + +class WrappedTCPFileDescriptor < TCPSocket + # do not close the connection when garbage collected!! + def finalize + # puts "WrappedTCPFileDescriptor garbage collection!!" + # super + end +end + +service_name = "websocket" +port_to_listen = 1234 + +OptionParser.parse! do |parser| + parser.on "-p port", "--port port", "Port to listen on." do |port| + port_to_listen = port.to_u16 + end + + parser.on "-s service-name", "--service-name service-name", "Service name." do |name| + service_name = name + end + + parser.on "-h", "--help", "Show this help" do + puts parser + exit 0 + end +end + + +class InstanceStorage + property service : IPC::SwitchingService + property fdlist : Hash(Int32,String) + property socklist : Hash(Int32, TCPSocket) + property wssocklist : Hash(Int32, WebSocket) + property switchtable : Hash(Int32, Int32) + property connectionlist : 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 + @switchtable = Hash(Int32, Int32).new + @connectionlist = 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] + + # 2. closing the TCP connections + tcpfdc.close unless tcpfdc.closed? + + # 3. removing the client and the service fds from the loop check + @service.remove_fd (fdclient) + + # 5. removing both the client and the service from the switchtable + @switchtable = @switchtable.select do |fdc, fds| + fdc != fdclient && fds != fdclient + end + + # 6. removing the client and the service from fdlist + @fdlist = @fdlist.select do |fd,v| fd != fdclient end + + unless fdservice.nil? + service = @connectionlist[fdservice] + service.close + @service.remove_fd (fdservice) + @connectionlist = @connectionlist.select do |k, v| + k != fdservice + end + + @fdlist = @fdlist.select do |fd,v| fd != fdservice end + end + end +end + +server = TCPServer.new("localhost", port_to_listen) +service = IPC::SwitchingService.new service_name +service << server.fd +context = InstanceStorage.new service + + + +def websocket_client_connection(client, context : InstanceStorage) + request = HTTP::Request.from_io client + # pp! request + + if request.nil? + raise "#REQUEST IS NIL" + end + + if request.is_a? HTTP::Request::BadRequest + raise "BAD REQUEST DAZE~" + 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 + + # HTTP inside bru + headers_header = "HTTP/1.1 101 Switching Protocols" + headers = HTTP::Headers { + "Upgrade" => "websocket", + "Connection" => "Upgrade", + "Sec-WebSocket-Accept" => response_key + } + + headers = headers.map { |key, value| "#{key}: #{value[0]}\r\n" }.join + + # 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" + + # 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 +end + + +def closing_client (fdclient : Int, context : InstanceStorage) + # puts "closing the client #{fdclient}" + context.remove_fd fdclient +end + +service.loop do |event| + case event + when IPC::Event::Connection + puts "#{CBLUE}IPC::Event::Connection: #{event.connection.fd}#{CRESET}" + when IPC::Event::Disconnection + puts "#{CBLUE}IPC::Event::Disconnection: #{event.connection.fd}#{CRESET}" + when IPC::Event::ExtraSocket + puts "#{CBLUE}IPC::Event::ExtraSocket#{CRESET}" + + # 1. accept new websocket clients + if server.fd == event.connection.fd + client = server.accept + begin + websocket_client_connection client, context + puts "#{CBLUE}new client:#{CRESET} #{client.fd}" + rescue e + puts "Exception: #{CRED}#{e}#{CRESET}" + client.close + end + next + end + + # 2. active fd != server fd + # is this a service or a client? + activefd = event.connection.fd + + # 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}" + end + + if message.nil? + puts "#{CBLUE}disconnection of client#{CRESET} #{event.connection.fd}" + closing_client activefd, context + next + end + + print_hexa(String.new(message), "#{CBLUE}Received message hexa#{CRESET}") + + when IPC::Event::Switch + puts "\033[36mIPC::Event::Switch#{CRESET}: from fd #{event.connection.fd}" + + raise "Not implemented." + + # IPC::Event::Message has to be the last entry + # because ExtraSocket and Switch inherit from Message class + when IPC::Event::Message + puts "#{CBLUE}IPC::Event::Message#{CRESET}: #{event.connection.fd}" + + raise "Not implemented." + end +end