From f81ccced1d8f65ded31ef5ba3bfe92ef98e6d6a8 Mon Sep 17 00:00:00 2001 From: Brandon Dewitt Date: Thu, 4 Aug 2016 10:41:52 -0600 Subject: [PATCH 1/7] allow an ssl_context to be passed via :ssl_context in options to connect --- lib/websocket-client-simple/client.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/websocket-client-simple/client.rb b/lib/websocket-client-simple/client.rb index 128f523..6994354 100644 --- a/lib/websocket-client-simple/client.rb +++ b/lib/websocket-client-simple/client.rb @@ -20,13 +20,17 @@ def connect(url, options={}) @socket = TCPSocket.new(uri.host, uri.port || (uri.scheme == 'wss' ? 443 : 80)) if ['https', 'wss'].include? uri.scheme - ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = options[:ssl_version] || 'SSLv23' - ctx.verify_mode = options[:verify_mode] || OpenSSL::SSL::VERIFY_NONE #use VERIFY_PEER for verification - cert_store = OpenSSL::X509::Store.new - cert_store.set_default_paths - ctx.cert_store = cert_store - @socket = ::OpenSSL::SSL::SSLSocket.new(@socket, ctx) + ssl_context = options[:ssl_context] || begin + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = options[:ssl_version] || 'SSLv23' + ctx.verify_mode = options[:verify_mode] || OpenSSL::SSL::VERIFY_NONE #use VERIFY_PEER for verification + cert_store = OpenSSL::X509::Store.new + cert_store.set_default_paths + ctx.cert_store = cert_store + ctx + end + + @socket = ::OpenSSL::SSL::SSLSocket.new(@socket, ssl_context) @socket.connect end @handshake = ::WebSocket::Handshake::Client.new :url => url, :headers => options[:headers] From ff3aec1f5a5981f75cdb9f6e60b30753e1495db0 Mon Sep 17 00:00:00 2001 From: Brandon Dewitt Date: Thu, 4 Aug 2016 17:51:45 -0600 Subject: [PATCH 2/7] try disable nodelay --- lib/websocket-client-simple/client.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/websocket-client-simple/client.rb b/lib/websocket-client-simple/client.rb index 6994354..97d8c43 100644 --- a/lib/websocket-client-simple/client.rb +++ b/lib/websocket-client-simple/client.rb @@ -19,6 +19,7 @@ def connect(url, options={}) uri = URI.parse url @socket = TCPSocket.new(uri.host, uri.port || (uri.scheme == 'wss' ? 443 : 80)) + @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if ['https', 'wss'].include? uri.scheme ssl_context = options[:ssl_context] || begin ctx = OpenSSL::SSL::SSLContext.new From fe767b97561bce6be72ed3bd1f3344057d960608 Mon Sep 17 00:00:00 2001 From: Brandon Dewitt Date: Fri, 5 Aug 2016 16:59:55 -0600 Subject: [PATCH 3/7] use non_block write and read methods and make ssl work with non_block .... also update send => send_data --- lib/websocket-client-simple/client.rb | 112 +++++++++++++++++--------- 1 file changed, 74 insertions(+), 38 deletions(-) diff --git a/lib/websocket-client-simple/client.rb b/lib/websocket-client-simple/client.rb index 97d8c43..67e9e14 100644 --- a/lib/websocket-client-simple/client.rb +++ b/lib/websocket-client-simple/client.rb @@ -34,50 +34,30 @@ def connect(url, options={}) @socket = ::OpenSSL::SSL::SSLSocket.new(@socket, ssl_context) @socket.connect end + @handshake = ::WebSocket::Handshake::Client.new :url => url, :headers => options[:headers] @handshaked = false @pipe_broken = false - frame = ::WebSocket::Frame::Incoming::Client.new @closed = false once :__close do |err| close emit :close, err end - @thread = Thread.new do - while !@closed do - begin - unless recv_data = @socket.getc - sleep 1 - next - end - unless @handshaked - @handshake << recv_data - if @handshake.finished? - @handshaked = true - emit :open - end - else - frame << recv_data - while msg = frame.next - emit :message, msg - end - end - rescue => e - emit :error, e - end - end - end - - @socket.write @handshake.to_s + handshake + @thread = poll end - def send(data, opt={:type => :text}) - return if !@handshaked or @closed - type = opt[:type] - frame = ::WebSocket::Frame::Outgoing::Client.new(:data => data, :type => type, :version => @handshake.version) + def send_data(data, opt={:type => :text}) + return if !@handshaked || @closed + + frame = ::WebSocket::Frame::Outgoing::Client.new(:data => data, :type => opt[:type], :version => @handshake.version) + begin - @socket.write frame.to_s + @socket.write_nonblock(frame.to_s) + rescue IO::WaitWritable, Errno::EINTR + IO.select(nil, [@socket]) + retry rescue Errno::EPIPE => e @pipe_broken = true emit :__close, e @@ -86,22 +66,78 @@ def send(data, opt={:type => :text}) def close return if @closed - if !@pipe_broken - send nil, :type => :close - end + + send_data nil, :type => :close if !@pipe_broken + emit :__close + ensure @closed = true @socket.close if @socket @socket = nil - emit :__close Thread.kill @thread if @thread end + def handshake + @socket.write @handshake.to_s + + while !@handshaked + begin + read_sockets, _, _ = IO.select([@socket], [], [], 10) + + if read_sockets && read_sockets[0] + @handshake << @socket.read_nonblock(1024) + + if @socket.respond_to?(:pending) # SSLSocket + @handshake << @socket.read(@socket.pending) while @socket.pending > 0 + end + + @handshaked = @handshake.finished? + end + rescue IO::WaitReadable + # No op + rescue IO::WaitWritable + IO.select(nil, [socket]) + retry + end + end + end + + def poll + return Thread.new(@socket) do |socket| + frame = ::WebSocket::Frame::Incoming::Client.new + emit :open + + while !@closed do + read_sockets, _, _ = IO.select([socket], nil, nil, 1) + + if read_sockets && read_sockets[0] + begin + frame << socket.read_nonblock(1024) + + if socket.respond_to?(:pending) + frame << socket.read(socket.pending) while socket.pending > 0 + end + + if msg = frame.next + emit :message, msg + frame = ::WebSocket::Frame::Incoming::Client.new + end + rescue IO::WaitReadable + # Nothing + rescue IO::WaitWritable + IO.select(nil, [socket]) + retry + rescue => e + emit :error, e + end + end + end + end + end + def open? @handshake.finished? and !@closed end - end - end end end From 304724bc0fbdd098da36abeb1181fd46ef50a508 Mon Sep 17 00:00:00 2001 From: Brandon Dewitt Date: Fri, 5 Aug 2016 17:18:31 -0600 Subject: [PATCH 4/7] update examples and tests to use send_data --- sample/client.rb | 2 +- sample/echo_server.rb | 2 +- test/echo_server.rb | 2 +- test/test_connect_block.rb | 2 +- test/test_websocket_client_simple.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sample/client.rb b/sample/client.rb index e20a6e8..3bd8fcd 100644 --- a/sample/client.rb +++ b/sample/client.rb @@ -27,5 +27,5 @@ end loop do - ws.send STDIN.gets.strip + ws.send_data STDIN.gets.strip end diff --git a/sample/echo_server.rb b/sample/echo_server.rb index fb49502..f22aa34 100644 --- a/sample/echo_server.rb +++ b/sample/echo_server.rb @@ -12,7 +12,7 @@ WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => PORT) do |ws| ws.onopen do sid = @channel.subscribe do |mes| - ws.send mes + ws.send_data mes end puts "<#{sid}> connect" diff --git a/test/echo_server.rb b/test/echo_server.rb index b2d11a8..c4e23aa 100644 --- a/test/echo_server.rb +++ b/test/echo_server.rb @@ -4,7 +4,7 @@ def self.start @channel = EM::Channel.new ws.onopen do sid = @channel.subscribe do |mes| - ws.send mes # echo to client + ws.send_data mes # echo to client end ws.onmessage do |msg| @channel.push msg diff --git a/test/test_connect_block.rb b/test/test_connect_block.rb index bf6a90e..20c54c3 100644 --- a/test/test_connect_block.rb +++ b/test/test_connect_block.rb @@ -13,7 +13,7 @@ def test_onopen EM::add_timer 1 do WebSocket::Client::Simple.connect EchoServer.url do |client| client.on :open do - client.send "hello world" + client.send_data "hello world" end client.on :message do |msg| diff --git a/test/test_websocket_client_simple.rb b/test/test_websocket_client_simple.rb index a30a34f..b80a8e8 100644 --- a/test/test_websocket_client_simple.rb +++ b/test/test_websocket_client_simple.rb @@ -27,7 +27,7 @@ def test_echo client1.on :open do msgs.each do |m| - client1.send m + client1.send_data m end end From 4d76c51ab4dec36110ae853a695599b6fc1b7211 Mon Sep 17 00:00:00 2001 From: Brandon Dewitt Date: Sat, 6 Aug 2016 20:36:33 -0600 Subject: [PATCH 5/7] ignore ruby-version and ruby-gemset --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8f994bd..a405876 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.gem *.rbc +*.ruby-* .bundle .config .yardoc From faa2799d90da23f99146371298ae38412709461f Mon Sep 17 00:00:00 2001 From: Brandon Dewitt Date: Sat, 6 Aug 2016 20:42:50 -0600 Subject: [PATCH 6/7] use consistent timeout in select calls and make sure we catch WaitReadable when attempting to write as SSL cmay need an internal read --- lib/websocket-client-simple/client.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/websocket-client-simple/client.rb b/lib/websocket-client-simple/client.rb index 67e9e14..2552194 100644 --- a/lib/websocket-client-simple/client.rb +++ b/lib/websocket-client-simple/client.rb @@ -55,6 +55,9 @@ def send_data(data, opt={:type => :text}) begin @socket.write_nonblock(frame.to_s) + rescue IO::WaitReadable + IO.select([@socket]) # OpenSSL needs to read internally + retry rescue IO::WaitWritable, Errno::EINTR IO.select(nil, [@socket]) retry @@ -81,7 +84,7 @@ def handshake while !@handshaked begin - read_sockets, _, _ = IO.select([@socket], [], [], 10) + read_sockets, _, _ = IO.select([@socket], nil, nil, 10) if read_sockets && read_sockets[0] @handshake << @socket.read_nonblock(1024) @@ -107,7 +110,7 @@ def poll emit :open while !@closed do - read_sockets, _, _ = IO.select([socket], nil, nil, 1) + read_sockets, _, _ = IO.select([socket], nil, nil, 10) if read_sockets && read_sockets[0] begin From cc847e49cd28831787f0b2a22f167a112797111b Mon Sep 17 00:00:00 2001 From: Mo Morsi Date: Sun, 25 Nov 2018 15:00:15 -0500 Subject: [PATCH 7/7] Follow on to non_block & close issues, handle EOF close when running 'non blocking' logic --- lib/websocket-client-simple/client.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/websocket-client-simple/client.rb b/lib/websocket-client-simple/client.rb index 2552194..6df9788 100644 --- a/lib/websocket-client-simple/client.rb +++ b/lib/websocket-client-simple/client.rb @@ -39,10 +39,6 @@ def connect(url, options={}) @handshaked = false @pipe_broken = false @closed = false - once :__close do |err| - close - emit :close, err - end handshake @thread = poll @@ -63,15 +59,18 @@ def send_data(data, opt={:type => :text}) retry rescue Errno::EPIPE => e @pipe_broken = true - emit :__close, e + close(e) + rescue OpenSSL::SSL::SSLError => e + @pipe_broken = true + close(e) end end - def close + def close(err=nil) return if @closed send_data nil, :type => :close if !@pipe_broken - emit :__close + emit :close, err ensure @closed = true @socket.close if @socket @@ -129,6 +128,9 @@ def poll rescue IO::WaitWritable IO.select(nil, [socket]) retry + rescue EOFError => e + emit :error, e + close(e) rescue => e emit :error, e end