nio4r-websocket 0.6.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1d236ea0ed2486632c8d4deab40592bbc49d2e88
4
+ data.tar.gz: b13b3d9545f08e05f7387eb21f38d39245d21f57
5
+ SHA512:
6
+ metadata.gz: 2e1b4039e7f6712d08be30096c3edce281c53b8b2768b5ac284bd779906ca129b39148b238edbd21b0bb149ea3e9b9988682fb6609114c2125ac270175dd7327
7
+ data.tar.gz: '0139edfb59b125dd4d8ba09c967d9e1f5a75da90d9bcfe22c1e619b0258be3fa1fb4c0a26b8f963ac97ca476f3126a3707c991a8e4136536c19a3249fe4cbb26'
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .vscode
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
14
+
15
+ .vagrant
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+
data/.travis.yml ADDED
@@ -0,0 +1,27 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.1.0
5
+ - 2.2.2
6
+ - 2.3.1
7
+ - ruby-head
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: ruby-head
11
+ before_script:
12
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
13
+ - chmod +x ./cc-test-reporter
14
+ - "./cc-test-reporter before-build"
15
+ - ip a
16
+ - cat /etc/hosts
17
+ after_script:
18
+ - cat Gemfile.lock
19
+ - "./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
20
+ deploy:
21
+ provider: rubygems
22
+ api_key:
23
+ secure: TcdJ+iGasCJJFqG9BqWBwsZrKKMTbvWJ+CNMeSYLLCuFOSW74m/A0DBkVddgaADlY1EYIkNkAAaFEaPgQsPunsll8wvAwbddXX5LOsrUaIk3gaYk3V8sudnn437HGsFIvMVQ8yg8fc25p2MMz/pCVPyb1JcMfRBHVDB4I39LUHthi24aGJJn2EZPgiarXYFbsiO/aMId22/Yxw32gG3+ULd2GAhxQ55zZzxfTzvwRian3U9kY4uO79a3rjKH9beSGXYfH3hecp0NEt65vEjwUnfohx7M4We9SHYLm3bEpPtLjUrI2eaaMbeY9bp3UkwVetsL3Ms4METU49sVbYwDrjt+H5s7GvSKdZt/ybmgJWu/Z7llUVrZzJLXRIpMTEGOYh2l+pgzOon+0fmKkcjSbveM6BCiQbMkt+kHHEGlm01VrZPlBeuhnl0ORAxTvAb9PZjs5/myrMrd7C+MAFp9xT+kJ3BodnhV8oPP8imHLqZiu3eAQBk6YtjEOJ3VPlk4VZzL2risXWdKpOeTXU/5WAJJLJmUWNFtfWDsd+EkxprTpNHzymNGUxR2m3OVdqlEwUELCggrkaSNVrYgBFVkrPz0Th23ALsd5YbN16p5eRVbozJP35SxHWQzmCj8Ye7vRgeIN9AyT6FSmuNTbJuPFFGPQaFDmpFkH/CreOXJFNU=
24
+ gem: nio4r-websocket
25
+ on:
26
+ tags: true
27
+ rvm: 2.3.1
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Sean Zachariasen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # NIO::WebSocket [![Build Status](https://travis-ci.org/NexusSW/nio4r-websocket.svg?branch=master)](https://travis-ci.org/NexusSW/nio4r-websocket) [![Dependency Status](https://gemnasium.com/badges/github.com/NexusSW/nio4r-websocket.svg)](https://gemnasium.com/github.com/NexusSW/nio4r-websocket)
2
+
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/cce01221d575804b09f5/maintainability)](https://codeclimate.com/github/NexusSW/nio4r-websocket/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/cce01221d575804b09f5/test_coverage)](https://codeclimate.com/github/NexusSW/nio4r-websocket/test_coverage) [![Gem Version](https://badge.fury.io/rb/nio4r-websocket.svg)](https://badge.fury.io/rb/nio4r-websocket)
4
+
5
+ This gem ties websocket-driver, a transport agnostic WebSockets library, together with a nio4r driven socket implementation.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'nio4r-websocket'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ gem install nio4r-websocket
22
+
23
+ ## Usage
24
+
25
+ [YARD Documentation](http://www.rubydoc.info/gems/nio4r-websocket/)
26
+
27
+ The only usage patterns introduced by this module are in how to instantiate 'websocket-driver' objects. Please refer to their documentation at <https://github.com/faye/websocket-driver-ruby#driver-api> on how to use them.
28
+
29
+ Additionally, the WebSocket driver object will emit an `:io_error` event. In the case that the underlying IO object gets disconnected, or otherwise closed without completing the `WebSocket::Driver#close` mechanism, you will be notified via subscribing to `:io_error` like:
30
+
31
+ ```ruby
32
+ driver.on :io_error do
33
+ # some cleanup logic
34
+ # `driver.on :close` may or may not be called - likely not
35
+ end
36
+ ```
37
+
38
+ ### Examples
39
+
40
+ `require 'nio/websocket'`
41
+
42
+ Client:
43
+
44
+ ```ruby
45
+ NIO::WebSocket.connect 'wss://example.com/' do |driver|
46
+ driver.on :message do |event|
47
+ puts event.data
48
+ end
49
+ ... other wireup code (refer to 'websocket-driver' documentation)
50
+ end
51
+ ```
52
+
53
+ Server:
54
+
55
+ ```ruby
56
+ NIO::WebSocket.listen port:443, ssl_context: { key: openssl_pkey_rsa_obj, cert: x509_cert_obj } do |driver|
57
+ driver.on :message do |event|
58
+ puts event.data
59
+ end
60
+ ... other wireup code (refer to 'websocket-driver' documentation)
61
+ end
62
+ ```
63
+
64
+ > Note: The above server block (`listen`) is executed on a per-connection basis
65
+
66
+ ### Options
67
+
68
+ `NIO::WebSocket.listen` accepts `port:` and `address:` options. Port is required, but address is optional for if you care to bind to a specific IP address on your host.
69
+
70
+ Both `listen` and `NIO::WebSocket.connect` accept `websocket_options:` which is passed to the corresponding 'websocket-driver' calls. Additionally, `ssl_context:` is available if you care to enable and customize your SSL experience.
71
+
72
+ ## Development
73
+
74
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
75
+
76
+ To install this gem onto your local machine, run `bundle exec rake install`.
77
+
78
+ ## Contributing
79
+
80
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/NexusSW/nio4r-websocket>. Ensure that you sign off on all of your commits.
81
+
82
+ ## License
83
+
84
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
data/Vagrantfile ADDED
@@ -0,0 +1,27 @@
1
+ Vagrant.configure('2') do |config|
2
+ config.vm.box = 'ubuntu/trusty64'
3
+ config.vm.provider 'virtualbox' do |vb|
4
+ vb.memory = '512'
5
+ end
6
+ config.vm.provision 'chef_apply' do |chef|
7
+ chef.recipe = <<-RECIPE
8
+ apt_update 'update' do
9
+ action :nothing
10
+ end
11
+ apt_repository 'ruby-ng' do
12
+ uri 'ppa:brightbox/ruby-ng'
13
+ distribution node['lsb']['codename']
14
+ only_if { node['lsb']['codename'] == 'trusty' }
15
+ notifies :update, 'apt_update[update]', :immediately
16
+ end
17
+ package %w(git)
18
+ package %w(ruby2.1 ruby2.1-dev) do # raise/lower this if our minimum version ever changes - only affects local testing
19
+ only_if { node['lsb']['codename'] == 'trusty' }
20
+ end
21
+ gem_package 'bundler'
22
+ execute 'bundle install' do
23
+ cwd '/vagrant'
24
+ end
25
+ RECIPE
26
+ end
27
+ end
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'nio4r/websocket'
5
+
6
+ require 'irb'
7
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,18 @@
1
+ require 'nio/websocket/adapter'
2
+
3
+ module NIO
4
+ module WebSocket
5
+ class Adapter
6
+ class Client < Adapter
7
+ def initialize(url, io, options)
8
+ @url = url
9
+ driver = ::WebSocket::Driver.client(self, options[:websocket_options] || {})
10
+ super io, driver, options
11
+ WebSocket.logger.debug "Initiating handshake on #{io}"
12
+ driver.start
13
+ end
14
+ attr_reader :url
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ require 'nio/websocket/adapter'
2
+
3
+ module NIO
4
+ module WebSocket
5
+ class Adapter
6
+ class Server < Adapter
7
+ def initialize(io, options)
8
+ driver = ::WebSocket::Driver.server(self, options[:websocket_options] || {})
9
+ driver.on :connect do
10
+ if ::WebSocket::Driver.websocket? driver.env
11
+ driver.start
12
+ WebSocket.logger.debug 'driver connected'
13
+ end
14
+ end
15
+ super io, driver, options
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,107 @@
1
+ module NIO
2
+ module WebSocket
3
+ class Adapter
4
+ def initialize(io, driver, options)
5
+ @inner = io
6
+ @options = options
7
+ @driver = driver
8
+ @buffer = ''
9
+ @mutex = Mutex.new
10
+
11
+ driver.on :close do |ev|
12
+ WebSocket.logger.info "Driver initiated #{inner} close (code #{ev.code}): #{ev.reason}"
13
+ close :driver
14
+ end
15
+ driver.on :error do |ev|
16
+ WebSocket.logger.error "Driver reports error on #{inner}: #{ev.message}"
17
+ close :driver
18
+ end
19
+ end
20
+ attr_reader :inner, :options, :driver, :monitor
21
+
22
+ def teardown
23
+ @driver = nil # circular reference
24
+ monitor.close
25
+ inner.close
26
+ end
27
+
28
+ 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
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
63
+ end
64
+
65
+ 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?
69
+ driver.parse data
70
+ 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
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ class ::WebSocket::Driver
104
+ def force_state(newstate)
105
+ @ready_state = STATES.index newstate
106
+ end
107
+ end
@@ -0,0 +1,72 @@
1
+ require 'nio'
2
+
3
+ module NIO
4
+ module WebSocket
5
+ class Reactor
6
+ class << self
7
+ def queue_task(&blk)
8
+ return unless block_given?
9
+ task_mutex.synchronize do
10
+ @task_queue ||= []
11
+ @task_queue << blk
12
+ end
13
+ selector.wakeup
14
+ end
15
+
16
+ def selector
17
+ @selector ||= NIO::Selector.new
18
+ end
19
+
20
+ def reset
21
+ @reactor.exit if @reactor
22
+ @selector = nil
23
+ @reactor = nil
24
+ @task_queue = nil
25
+ @task_mutex = nil
26
+ end
27
+
28
+ def start
29
+ WebSocket.logger.debug 'Starting reactor' unless @reactor
30
+ @reactor ||= Thread.start do
31
+ Thread.current.abort_on_exception = true
32
+ WebSocket.logger.info 'Reactor started'
33
+ begin
34
+ loop do
35
+ queue = []
36
+ task_mutex.synchronize do
37
+ queue = @task_queue || []
38
+ @task_queue = []
39
+ end
40
+ # If something queues up while this runs, then the selector will also be awoken & won't block
41
+ queue.each(&:call)
42
+
43
+ selector.select 1 do |monitor|
44
+ begin
45
+ monitor.value.call if monitor.value.respond_to? :call
46
+ rescue => e
47
+ WebSocket.logger.error "Error occured in callback on socket #{monitor.io}. No longer handling this connection."
48
+ WebSocket.logger.error "#{e.class}: #{e.message}"
49
+ e.backtrace.map { |s| WebSocket.logger.error "\t#{s}" }
50
+ monitor.close # protect global loop from being crashed by a misbehaving driver, or a sloppy disconnect
51
+ end
52
+ end
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
+ end
55
+ rescue => e
56
+ WebSocket.logger.fatal 'Error occured in reactor subsystem.'
57
+ WebSocket.logger.fatal "#{e.class}: #{e.message}"
58
+ e.backtrace.map { |s| WebSocket.logger.fatal "\t#{s}" }
59
+ raise
60
+ end
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def task_mutex
67
+ @task_mutex ||= Mutex.new
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ module NIO
2
+ module WebSocket
3
+ VERSION = '0.6.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,178 @@
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
+
12
+ module NIO
13
+ module WebSocket
14
+ class << self
15
+ # Returns the current logger, or creates one at level ERROR if one has not been assigned
16
+ # @return [Logger] the current logger instance
17
+ def logger
18
+ @logger ||= begin
19
+ logger = Logger.new(STDERR, progname: 'WebSocket', level: Logger::ERROR)
20
+ logger.level = Logger::ERROR
21
+ logger
22
+ end
23
+ end
24
+
25
+ attr_writer :logger
26
+
27
+ # Should raw traffic be logged through the logger? Disabled by default for security reasons
28
+ # @param enable [Boolean]
29
+ def log_traffic=(enable)
30
+ @log_traffic = enable
31
+ logger.level = Logger::DEBUG if enable
32
+ enable
33
+ end
34
+
35
+ # Should raw traffic be logged through the logger? Disabled by default for security reasons
36
+ def log_traffic?
37
+ @log_traffic
38
+ end
39
+
40
+ # Create and return a websocket client that communicates either over the given IO object (upgrades the connection),
41
+ # or we'll create a new connection to url if io is not supplied
42
+ # @param [String] url ws:// or wss:// location to connect
43
+ # @param [Hash] options
44
+ # @param [IO] io (DI) raw IO object to use in lieu of opening a new connection to url
45
+ # @option options [Hash] :websocket_options Hash to pass to the ::WebSocket::Driver.client
46
+ # @option options [Hash] :ssl_context Hash from which to create the OpenSSL::SSL::SSLContext object
47
+ # @yield [::WebSocket::Driver]
48
+ # @return [::WebSocket::Driver]
49
+ def connect(url, options = {}, io = nil)
50
+ io ||= open_socket(url, options)
51
+ adapter = CLIENT_ADAPTER.new(url, io, options)
52
+ yield(adapter.driver, adapter) if block_given?
53
+ Reactor.queue_task do
54
+ adapter.add_to_reactor
55
+ end
56
+ Reactor.start
57
+ logger.info "Client #{io} connected to #{url}"
58
+ adapter.driver
59
+ end
60
+
61
+ # Start handling new connections, passing each through the supplied block
62
+ # @param [Hash] options
63
+ # @param server [TCPServer] (DI) TCPServer-like object to use in lieu of starting a new server
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] :websocket_options Hash to pass to the ::WebSocket::Driver.server
67
+ # @option options [Hash] :ssl_context Hash from which to create the OpenSSL::SSL::SSLContext object
68
+ # @yield [::WebSocket::Driver]
69
+ # @return server, as passed in, or a new TCPServer if no server was specified
70
+ def listen(options = {}, server = nil)
71
+ server ||= create_server(options)
72
+ Reactor.queue_task do
73
+ monitor = Reactor.selector.register(server, :r)
74
+ monitor.value = proc do
75
+ accept_socket server, options do |io| # this next block won't run until ssl (if enabled) has started
76
+ adapter = SERVER_ADAPTER.new(io, options)
77
+ yield(adapter.driver, adapter) if block_given?
78
+ Reactor.queue_task do
79
+ adapter.add_to_reactor
80
+ end
81
+ logger.info "Host accepted client connection #{io} on port #{options[:port]}"
82
+ end
83
+ end
84
+ end
85
+ Reactor.start
86
+ logger.info 'Host listening for new connections on port ' + options[:port].to_s
87
+ server
88
+ end
89
+
90
+ SERVER_ADAPTER = NIO::WebSocket::Adapter::Server
91
+ CLIENT_ADAPTER = NIO::WebSocket::Adapter::Client
92
+
93
+ # Resets this API to a fresh state
94
+ def reset
95
+ logger.info 'Resetting reactor subsystem'
96
+ Reactor.reset
97
+ end
98
+
99
+ # @!endgroup
100
+
101
+ private
102
+
103
+ # return an open socket given the url and options
104
+ def open_socket(url, options)
105
+ 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(?)
107
+ logger.debug "Opening Connection to #{uri.hostname} on port #{port}"
108
+ io = TCPSocket.new uri.hostname, port
109
+ return io unless uri.scheme == 'wss'
110
+ logger.debug "Upgrading Connection #{io} to ssl"
111
+ ssl = upgrade_to_ssl(io, options).connect
112
+ logger.info "Connection #{io} upgraded to #{ssl}"
113
+ ssl
114
+ end
115
+
116
+ def create_server(options)
117
+ options[:address] ? TCPServer.new(options[:address], options[:port]) : TCPServer.new(options[:port])
118
+ end
119
+
120
+ # supply a block to run after protocol negotiation
121
+ def accept_socket(server, options)
122
+ waiting = accept_nonblock server
123
+ if [:r, :w].include? waiting
124
+ logger.warn 'Expected to receive new connection, but the server is not quite ready'
125
+ return
126
+ end
127
+ logger.debug "Receiving new connection #{waiting} on port #{options[:port]}"
128
+ if options[:ssl_context]
129
+ logger.debug "Upgrading Connection #{waiting} to ssl"
130
+ ssl = upgrade_to_ssl(waiting, options)
131
+ try_accept_nonblock ssl do
132
+ logger.info "Incoming connection #{waiting} upgraded to #{ssl}"
133
+ yield ssl
134
+ end
135
+ else
136
+ yield waiting
137
+ end
138
+ end
139
+
140
+ def try_accept_nonblock(io)
141
+ waiting = accept_nonblock io
142
+ if [:r, :w].include? waiting
143
+ # Only happens on server side ssl negotiation
144
+ Reactor.queue_task do
145
+ monitor = Reactor.selector.register(io, :rw)
146
+ monitor.value = proc do
147
+ waiting = accept_nonblock io
148
+ unless [:r, :w].include? waiting
149
+ monitor.close
150
+ yield waiting
151
+ end
152
+ end
153
+ end
154
+ else
155
+ yield waiting
156
+ end
157
+ end
158
+
159
+ def accept_nonblock(io)
160
+ return io.accept_nonblock
161
+ rescue IO::WaitReadable
162
+ return :r
163
+ rescue IO::WaitWritable
164
+ return :w
165
+ end
166
+
167
+ def upgrade_to_ssl(io, options)
168
+ store = OpenSSL::X509::Store.new
169
+ store.set_default_paths
170
+ ctx = OpenSSL::SSL::SSLContext.new
171
+ { cert_store: store, verify_mode: OpenSSL::SSL::VERIFY_PEER }.merge(options[:ssl_context] || {}).each do |k, v|
172
+ ctx.send "#{k}=", v if ctx.respond_to? k
173
+ end
174
+ OpenSSL::SSL::SSLSocket.new(io, ctx)
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'nio/websocket/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'nio4r-websocket'
8
+ spec.version = NIO::WebSocket::VERSION
9
+ spec.authors = ['Sean Zachariasen']
10
+ spec.email = ['thewyzard@hotmail.com']
11
+
12
+ spec.summary = 'websocket-driver implementation built over nio4r'
13
+ spec.homepage = 'https://github.com/nexussw/nio4r-websocket'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
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.7'
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'
30
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nio4r-websocket
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Sean Zachariasen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-10-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nio4r
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.2.1
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.2.1
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: websocket-driver
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.7'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.7'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: simplecov
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ description:
104
+ email:
105
+ - thewyzard@hotmail.com
106
+ executables: []
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - ".gitignore"
111
+ - ".rspec"
112
+ - ".travis.yml"
113
+ - Gemfile
114
+ - LICENSE.txt
115
+ - README.md
116
+ - Rakefile
117
+ - Vagrantfile
118
+ - bin/console
119
+ - bin/setup
120
+ - lib/nio/websocket.rb
121
+ - lib/nio/websocket/adapter.rb
122
+ - lib/nio/websocket/adapter/client.rb
123
+ - lib/nio/websocket/adapter/server.rb
124
+ - lib/nio/websocket/reactor.rb
125
+ - lib/nio/websocket/version.rb
126
+ - nio4r-websocket.gemspec
127
+ homepage: https://github.com/nexussw/nio4r-websocket
128
+ licenses:
129
+ - MIT
130
+ metadata: {}
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 2.6.13
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: websocket-driver implementation built over nio4r
151
+ test_files: []