nio4r-websocket 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 5209eee6409bb44849b1d3ff55fd1407052dc43933c6bb480a14f7b224808ed4
4
- data.tar.gz: 3b0c7fadb007ba93f0b0034e5930d4d0d050b6d1a4c6bcd8e2c134ccb1e21557
2
+ SHA1:
3
+ metadata.gz: ef260d2997eba2db8dcff293dd847bc51488f477
4
+ data.tar.gz: 173b08a770153db1e35132edb4c438d530a5455e
5
5
  SHA512:
6
- metadata.gz: 66c8d050a9d7d492f983a608f906c6624666b79fb35a75789350f254b3cef0a13b6e1d54518d135fa015c2a9d85243505dce53863885ea10b0965ca147261b17
7
- data.tar.gz: 1d9ed2d04ac94a8e4be0300234045ac684bf8d0ba50505f7d172d55d35218257a55f661c52de4af6e199057aa7b96e86e76f4ce6952a51e7b1570617fed8b75a
6
+ metadata.gz: a93ff5fafb1488c319b3cb5b1383639e6a5b47d856fda7dae9ab55cea2a42b46196aaa126061a3ca9f601346dc217d02036895d34d5f7bc415c18f970c3e7920
7
+ data.tar.gz: 07a552ff14074cd8211bbb47c9a7d493b1eefe0e591165a3439e97788ba2d39e8cbd835d4b799d0e3dbc000c86fcde642ae305f759ed3d7fc9ee554b67015ad1
@@ -1,9 +1,7 @@
1
- sudo: false
2
1
  language: ruby
3
2
  rvm:
4
- - 2.1.0
5
3
  - 2.4.3
6
- - 2.5.0
4
+ - 2.5.1
7
5
  - ruby-head
8
6
  matrix:
9
7
  allow_failures:
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
data/Rakefile CHANGED
@@ -1,5 +1,5 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
  task default: :spec
@@ -1,9 +1,9 @@
1
- Vagrant.configure('2') do |config|
2
- config.vm.box = 'ubuntu/trusty64'
3
- config.vm.provider 'virtualbox' do |vb|
4
- vb.memory = '512'
1
+ Vagrant.configure("2") do |config|
2
+ config.vm.box = "ubuntu/trusty64"
3
+ config.vm.provider "virtualbox" do |vb|
4
+ vb.memory = "512"
5
5
  end
6
- config.vm.provision 'chef_apply' do |chef|
6
+ config.vm.provision "chef_apply" do |chef|
7
7
  chef.recipe = <<-RECIPE
8
8
  apt_update 'update' do
9
9
  action :nothing
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'bundler/setup'
4
- require 'nio4r/websocket'
3
+ require "bundler/setup"
4
+ require "nio4r/websocket"
5
5
 
6
- require 'irb'
6
+ require "irb"
7
7
  IRB.start(__FILE__)
@@ -1,13 +1,14 @@
1
- require 'nio/websocket/version'
2
- require 'websocket/driver'
3
- require 'nio'
4
- require 'socket'
5
- require 'uri'
6
- require 'openssl'
7
- require 'logger'
8
- require 'nio/websocket/reactor'
9
- require 'nio/websocket/adapter/client'
10
- require 'nio/websocket/adapter/server'
1
+ require "nio/websocket/version"
2
+ require "websocket/driver"
3
+ require "nio"
4
+ require "socket"
5
+ require "uri"
6
+ require "openssl"
7
+ require "logger"
8
+ require "nio/websocket/reactor"
9
+ require "nio/websocket/adapter/client"
10
+ require "nio/websocket/adapter/server"
11
+ require "nio/websocket/adapter/proxy"
11
12
 
12
13
  module NIO
13
14
  module WebSocket
@@ -16,7 +17,7 @@ module NIO
16
17
  # @return [Logger] the current logger instance
17
18
  def logger
18
19
  @logger ||= begin
19
- logger = Logger.new(STDERR, progname: 'WebSocket', level: Logger::ERROR)
20
+ logger = Logger.new(STDERR, progname: "WebSocket", level: Logger::ERROR)
20
21
  logger.level = Logger::ERROR
21
22
  logger
22
23
  end
@@ -29,7 +30,6 @@ module NIO
29
30
  def log_traffic=(enable)
30
31
  @log_traffic = enable
31
32
  logger.level = Logger::DEBUG if enable
32
- enable
33
33
  end
34
34
 
35
35
  # Should raw traffic be logged through the logger? Disabled by default for security reasons
@@ -58,6 +58,36 @@ module NIO
58
58
  adapter.driver
59
59
  end
60
60
 
61
+ # Establish a proxy host listening on the given port and address, that marshalls all data to/from a new connection on remote
62
+ # @param [Hash] options
63
+ # @param remote [String] remote server in "hostname_or_ip:port" format
64
+ # @option options [Integer] :port required: Port on which to listen for incoming connections
65
+ # @option options [String] :address optional: Specific Address on which to bind the TCPServer
66
+ # @option options [Hash] :ssl_context Hash from which to create the OpenSSL::SSL::SSLContext object
67
+ # @yield [::WebSocket::Driver]
68
+ # @return server, as passed in, or a new TCPServer if no server was specified
69
+ def proxy(remote, options = {})
70
+ server = create_server(options)
71
+ host, port, extra = remote.split(":", 3)
72
+ raise "Specify the remote parameter in 'hostname_or_ip:port' format" if extra || port.to_i == 0 || host.empty?
73
+ Reactor.queue_task do
74
+ monitor = Reactor.selector.register(server, :r)
75
+ monitor.value = proc do
76
+ accept_socket server, options do |client|
77
+ srv = open_socket "tcp://#{remote}", options
78
+ adapter = PROXY_ADAPTER.new(srv, client, options)
79
+ Reactor.queue_task do
80
+ adapter.add_to_reactor
81
+ end
82
+ logger.info "Proxy connection established between #{srv} and #{client}"
83
+ end
84
+ end
85
+ end
86
+ logger.info "Proxy Host listening for new connections on port " + options[:port].to_s
87
+ Reactor.start
88
+ server
89
+ end
90
+
61
91
  # Start handling new connections, passing each through the supplied block
62
92
  # @param [Hash] options
63
93
  # @param server [TCPServer] (DI) TCPServer-like object to use in lieu of starting a new server
@@ -83,16 +113,17 @@ module NIO
83
113
  end
84
114
  end
85
115
  Reactor.start
86
- logger.info 'Host listening for new connections on port ' + options[:port].to_s
116
+ logger.info "Host listening for new connections on port " + options[:port].to_s
87
117
  server
88
118
  end
89
119
 
90
120
  SERVER_ADAPTER = NIO::WebSocket::Adapter::Server
91
121
  CLIENT_ADAPTER = NIO::WebSocket::Adapter::Client
122
+ PROXY_ADAPTER = NIO::WebSocket::Adapter::Proxy
92
123
 
93
124
  # Resets this API to a fresh state
94
125
  def reset
95
- logger.info 'Resetting reactor subsystem'
126
+ logger.info "Resetting reactor subsystem"
96
127
  Reactor.reset
97
128
  end
98
129
 
@@ -103,10 +134,10 @@ module NIO
103
134
  # return an open socket given the url and options
104
135
  def open_socket(url, options)
105
136
  uri = URI(url)
106
- port = uri.port || (uri.scheme == 'wss' ? 443 : 80) # redundant? test uri.port if port is unspecified but because ws: & wss: aren't default protocols we'll maybe still need this(?)
137
+ port = uri.port || (uri.scheme == "wss" ? 443 : 80) # redundant? test uri.port if port is unspecified but because ws: & wss: aren't default protocols we'll maybe still need this(?)
107
138
  logger.debug "Opening Connection to #{uri.hostname} on port #{port}"
108
139
  io = TCPSocket.new uri.hostname, port
109
- return io unless uri.scheme == 'wss'
140
+ return io unless uri.scheme == "wss"
110
141
  logger.debug "Upgrading Connection #{io} to ssl"
111
142
  ssl = upgrade_to_ssl(io, options).connect
112
143
  logger.info "Connection #{io} upgraded to #{ssl}"
@@ -121,7 +152,7 @@ module NIO
121
152
  def accept_socket(server, options)
122
153
  waiting = accept_nonblock server
123
154
  if [:r, :w].include? waiting
124
- logger.warn 'Expected to receive new connection, but the server is not quite ready'
155
+ logger.warn "Expected to receive new connection, but the server is not quite ready"
125
156
  return
126
157
  end
127
158
  logger.debug "Receiving new connection #{waiting} on port #{options[:port]}"
@@ -157,11 +188,11 @@ module NIO
157
188
  end
158
189
 
159
190
  def accept_nonblock(io)
160
- return io.accept_nonblock
191
+ io.accept_nonblock
161
192
  rescue IO::WaitReadable
162
- return :r
193
+ :r
163
194
  rescue IO::WaitWritable
164
- return :w
195
+ :w
165
196
  end
166
197
 
167
198
  def upgrade_to_ssl(io, options)
@@ -1,12 +1,10 @@
1
+ require "nio/websocket/raw_adapter"
2
+
1
3
  module NIO
2
4
  module WebSocket
3
- class Adapter
5
+ class Adapter < RawAdapter
4
6
  def initialize(io, driver, options)
5
- @inner = io
6
- @options = options
7
7
  @driver = driver
8
- @buffer = ''
9
- @mutex = Mutex.new
10
8
 
11
9
  driver.on :close do |ev|
12
10
  WebSocket.logger.info "Driver initiated #{inner} close (code #{ev.code}): #{ev.reason}"
@@ -16,85 +14,27 @@ module NIO
16
14
  WebSocket.logger.error "Driver reports error on #{inner}: #{ev.message}"
17
15
  close :driver
18
16
  end
17
+
18
+ super io, options
19
19
  end
20
- attr_reader :inner, :options, :driver, :monitor
20
+ attr_reader :driver
21
21
 
22
22
  def teardown
23
+ driver.force_state :closed
24
+ driver.emit :io_error
23
25
  @driver = nil # circular reference
24
- monitor.close
25
- inner.close
26
+ super
26
27
  end
27
28
 
28
29
  def close(from = nil)
29
- return false if @closing
30
-
31
- driver.close if from.nil?
32
- @closing = true
33
- monitor.interests = :rw
34
- Reactor.selector.wakeup
35
- true
36
- end
37
-
38
- def add_to_reactor
39
- @monitor = Reactor.selector.register(inner, :rw) # This can block if this is the main thread and the reactor is busy
40
- monitor.value = proc do
41
- begin
42
- read if monitor.readable?
43
- pump_buffer if monitor.writable?
44
- rescue Errno::ECONNRESET, EOFError, Errno::ECONNABORTED
45
- driver.force_state :closed
46
- driver.emit :io_error
47
- teardown
48
- WebSocket.logger.info "#{inner} socket closed"
49
- rescue IO::WaitReadable # rubocop:disable Lint/HandleExceptions
50
- rescue IO::WaitWritable
51
- monitor.interests = :rw
52
- end
53
- if @closing
54
- if !monitor.readable? && @buffer.empty?
55
- teardown
56
- WebSocket.logger.info "#{inner} closed"
57
- else
58
- monitor.interests = :rw unless monitor.closed? # keep the :w interest so that our block runs each time
59
- # edge case: if monitor was readable this time, and the write buffer is empty, if we emptied the read buffer this time our block wouldn't run again
60
- end
61
- end
62
- end
30
+ driver.close if from.nil? && !closing
31
+ super()
63
32
  end
64
33
 
65
34
  def read
66
- data = inner.read_nonblock(16384)
67
- if data
68
- WebSocket.logger.debug { "Incoming data on #{inner}:\n#{data}" } if WebSocket.log_traffic?
35
+ super do |data|
69
36
  driver.parse data
70
37
  end
71
- data
72
- end
73
-
74
- def write(data)
75
- @mutex.synchronize do
76
- @buffer << data
77
- end
78
- return unless monitor
79
- pump_buffer
80
- Reactor.selector.wakeup unless monitor.interests == :r
81
- end
82
-
83
- def pump_buffer
84
- @mutex.synchronize do
85
- written = 0
86
- begin
87
- written = inner.write_nonblock @buffer unless @buffer.empty?
88
- WebSocket.logger.debug { "Pumped #{written} bytes of data from buffer to #{inner}:\n#{@buffer}" } unless @buffer.empty? || !WebSocket.log_traffic?
89
- @buffer = @buffer.byteslice(written..-1) if written > 0
90
- WebSocket.logger.debug { "The buffer is now:\n#{@buffer}" } unless @buffer.empty? || !WebSocket.log_traffic?
91
- rescue IO::WaitWritable, IO::WaitReadable
92
- return written
93
- ensure
94
- monitor.interests = @buffer.empty? ? :r : :rw
95
- end
96
- written
97
- end
98
38
  end
99
39
  end
100
40
  end
@@ -1,4 +1,4 @@
1
- require 'nio/websocket/adapter'
1
+ require "nio/websocket/adapter"
2
2
 
3
3
  module NIO
4
4
  module WebSocket
@@ -0,0 +1,38 @@
1
+ require "nio/websocket/raw_adapter"
2
+
3
+ module NIO
4
+ module WebSocket
5
+ class Adapter
6
+ class Proxy
7
+ def initialize(srv, client, options)
8
+ @srv_adapter = ProxyAdapter.new srv, options do |data|
9
+ client_adapter.write data
10
+ end
11
+ @client_adapter = ProxyAdapter.new client, options do |data|
12
+ srv_adapter.write data
13
+ end
14
+ WebSocket.logger.debug "Initiating proxy connection between #{srv} and #{client}"
15
+ end
16
+ attr_reader :srv_adapter, :client_adapter
17
+
18
+ def add_to_reactor
19
+ srv_adapter.add_to_reactor
20
+ client_adapter.add_to_reactor
21
+ end
22
+ end
23
+
24
+ class ProxyAdapter < RawAdapter
25
+ def initialize(io, options, &block)
26
+ super io, options
27
+ @read_event = block
28
+ end
29
+
30
+ def read
31
+ super do |data|
32
+ @read_event.call data
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,4 +1,4 @@
1
- require 'nio/websocket/adapter'
1
+ require "nio/websocket/adapter"
2
2
 
3
3
  module NIO
4
4
  module WebSocket
@@ -9,7 +9,7 @@ module NIO
9
9
  driver.on :connect do
10
10
  if ::WebSocket::Driver.websocket? driver.env
11
11
  driver.start
12
- WebSocket.logger.debug 'driver connected'
12
+ WebSocket.logger.debug "driver connected"
13
13
  end
14
14
  end
15
15
  super io, driver, options
@@ -0,0 +1,87 @@
1
+ module NIO
2
+ module WebSocket
3
+ class RawAdapter
4
+ def initialize(io, options)
5
+ @inner = io
6
+ @options = options
7
+ @buffer = ""
8
+ @mutex = Mutex.new
9
+ end
10
+ attr_reader :inner, :options, :monitor, :closing
11
+
12
+ def teardown
13
+ monitor.close
14
+ inner.close
15
+ end
16
+
17
+ def close
18
+ return false if @closing
19
+
20
+ @closing = true
21
+ monitor.interests = :rw
22
+ Reactor.selector.wakeup
23
+ true
24
+ end
25
+
26
+ def add_to_reactor
27
+ @monitor = Reactor.selector.register(inner, :rw) # This can block if this is the main thread and the reactor is busy
28
+ monitor.value = proc do
29
+ begin
30
+ read if monitor.readable?
31
+ pump_buffer if monitor.writable?
32
+ rescue Errno::ECONNRESET, EOFError, Errno::ECONNABORTED
33
+ teardown
34
+ WebSocket.logger.info "#{inner} socket closed"
35
+ rescue IO::WaitReadable # rubocop:disable Lint/HandleExceptions
36
+ rescue IO::WaitWritable
37
+ monitor.interests = :rw
38
+ end
39
+ if @closing
40
+ if !monitor.readable? && @buffer.empty?
41
+ teardown
42
+ WebSocket.logger.info "#{inner} closed"
43
+ else
44
+ monitor.interests = :rw unless monitor.closed? # keep the :w interest so that our block runs each time
45
+ # edge case: if monitor was readable this time, and the write buffer is empty, if we emptied the read buffer this time our block wouldn't run again
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def read
52
+ data = inner.read_nonblock(16384)
53
+ if data
54
+ WebSocket.logger.debug { "Incoming data on #{inner}:\n#{data}" } if WebSocket.log_traffic?
55
+ yield data if block_given?
56
+ end
57
+ data
58
+ end
59
+
60
+ def write(data)
61
+ @mutex.synchronize do
62
+ @buffer << data
63
+ end
64
+ return unless monitor
65
+ pump_buffer
66
+ Reactor.selector.wakeup unless monitor.interests == :r
67
+ end
68
+
69
+ def pump_buffer
70
+ @mutex.synchronize do
71
+ written = 0
72
+ begin
73
+ written = inner.write_nonblock @buffer unless @buffer.empty?
74
+ WebSocket.logger.debug { "Pumped #{written} bytes of data from buffer to #{inner}:\n#{@buffer}" } unless @buffer.empty? || !WebSocket.log_traffic?
75
+ @buffer = @buffer.byteslice(written..-1) if written > 0
76
+ WebSocket.logger.debug { "The buffer is now:\n#{@buffer}" } unless @buffer.empty? || !WebSocket.log_traffic?
77
+ rescue IO::WaitWritable, IO::WaitReadable
78
+ return written
79
+ ensure
80
+ monitor.interests = @buffer.empty? ? :r : :rw
81
+ end
82
+ written
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,4 +1,4 @@
1
- require 'nio'
1
+ require "nio"
2
2
 
3
3
  module NIO
4
4
  module WebSocket
@@ -26,10 +26,10 @@ module NIO
26
26
  end
27
27
 
28
28
  def start
29
- WebSocket.logger.debug 'Starting reactor' unless @reactor
29
+ WebSocket.logger.debug "Starting reactor" unless @reactor
30
30
  @reactor ||= Thread.start do
31
31
  Thread.current.abort_on_exception = true
32
- WebSocket.logger.info 'Reactor started'
32
+ WebSocket.logger.info "Reactor started"
33
33
  begin
34
34
  loop do
35
35
  queue = []
@@ -53,7 +53,7 @@ module NIO
53
53
  Thread.pass # give other threads a chance at manipulating our selector (e.g. a new connection on the main thread trying to register)
54
54
  end
55
55
  rescue => e
56
- WebSocket.logger.fatal 'Error occured in reactor subsystem.'
56
+ WebSocket.logger.fatal "Error occured in reactor subsystem."
57
57
  WebSocket.logger.fatal "#{e.class}: #{e.message}"
58
58
  e.backtrace.map { |s| WebSocket.logger.fatal "\t#{s}" }
59
59
  raise
@@ -1,5 +1,5 @@
1
1
  module NIO
2
2
  module WebSocket
3
- VERSION = '0.6.3'.freeze
3
+ VERSION = "0.7.0".freeze
4
4
  end
5
5
  end
@@ -1,30 +1,30 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'nio/websocket/version'
4
+ require "nio/websocket/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = 'nio4r-websocket'
7
+ spec.name = "nio4r-websocket"
8
8
  spec.version = NIO::WebSocket::VERSION
9
- spec.authors = ['Sean Zachariasen']
10
- spec.email = ['thewyzard@hotmail.com']
9
+ spec.authors = ["Sean Zachariasen"]
10
+ spec.email = ["thewyzard@hotmail.com"]
11
11
 
12
- spec.summary = 'websocket-driver implementation built over nio4r'
13
- spec.homepage = 'https://github.com/nexussw/nio4r-websocket'
14
- spec.license = 'MIT'
12
+ spec.summary = "websocket-driver implementation built over nio4r"
13
+ spec.homepage = "https://github.com/nexussw/nio4r-websocket"
14
+ spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
17
  f.match(%r{^(test|spec|features)/})
18
18
  end
19
- spec.bindir = 'exe'
19
+ spec.bindir = "exe"
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
- spec.require_paths = ['lib']
21
+ spec.require_paths = ["lib"]
22
22
 
23
- spec.add_dependency 'nio4r', '>= 1.2.1', '< 3.0' # Allow older nio4r, if possible, so as to not lock our ruby version to 2.2.2
24
- spec.add_dependency 'websocket-driver', '~> 0.6'
23
+ spec.add_dependency "nio4r", ">= 1.2.1", "< 3.0" # Allow older nio4r, if possible, so as to not lock our ruby version to 2.2.2
24
+ spec.add_dependency "websocket-driver", "~> 0.6"
25
25
 
26
- spec.add_development_dependency 'bundler'
27
- spec.add_development_dependency 'rake'
28
- spec.add_development_dependency 'rspec'
29
- spec.add_development_dependency 'simplecov'
26
+ spec.add_development_dependency "bundler"
27
+ spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "rspec"
29
+ spec.add_development_dependency "simplecov"
30
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nio4r-websocket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Zachariasen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-03 00:00:00.000000000 Z
11
+ date: 2019-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -120,7 +120,9 @@ files:
120
120
  - lib/nio/websocket.rb
121
121
  - lib/nio/websocket/adapter.rb
122
122
  - lib/nio/websocket/adapter/client.rb
123
+ - lib/nio/websocket/adapter/proxy.rb
123
124
  - lib/nio/websocket/adapter/server.rb
125
+ - lib/nio/websocket/raw_adapter.rb
124
126
  - lib/nio/websocket/reactor.rb
125
127
  - lib/nio/websocket/version.rb
126
128
  - nio4r-websocket.gemspec
@@ -144,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
146
  version: '0'
145
147
  requirements: []
146
148
  rubyforge_project:
147
- rubygems_version: 2.7.6
149
+ rubygems_version: 2.6.14
148
150
  signing_key:
149
151
  specification_version: 4
150
152
  summary: websocket-driver implementation built over nio4r