nio4r-websocket 0.6.0

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