nio4r-websocket 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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