websocket-eventmachine-server 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +5 -0
- data/README.md +89 -0
- data/Rakefile +16 -0
- data/autobahn.json +13 -0
- data/examples/autobahn_server.rb +24 -0
- data/examples/echo_server.rb +45 -0
- data/lib/websocket-eventmachine-server.rb +1 -0
- data/lib/websocket/eventmachine/server.rb +229 -0
- data/lib/websocket/eventmachine/server/version.rb +7 -0
- data/websocket-eventmachine-server.gemspec +23 -0
- metadata +105 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# WebSocket Server for Ruby
|
2
|
+
|
3
|
+
WebSocket-EventMachine-Server is Ruby WebSocket server based on EventMachine.
|
4
|
+
|
5
|
+
## Why another WebSocket server?
|
6
|
+
|
7
|
+
There are multiple Ruby WebSocket servers, each with different quirks and errors. Most commonly used em-websocket is unfortunately slow and have multiple bugs(see Autobahn tests below). This library was created to fix most of them.
|
8
|
+
|
9
|
+
[Autobahn tests](http://imanel.github.com/websocket-ruby/autobahn/server)
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
``` bash
|
14
|
+
gem install websocket-eventmachine-server
|
15
|
+
```
|
16
|
+
|
17
|
+
or in Gemfile
|
18
|
+
|
19
|
+
``` ruby
|
20
|
+
gem 'websocket-eventmachine-server'
|
21
|
+
```
|
22
|
+
|
23
|
+
## Simple server example
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
EventMachine.run {
|
27
|
+
|
28
|
+
WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 8080) do |ws|
|
29
|
+
ws.onopen {
|
30
|
+
puts "WebSocket connection open"
|
31
|
+
|
32
|
+
# publish message to the client
|
33
|
+
ws.send "Hello Client"
|
34
|
+
}
|
35
|
+
|
36
|
+
ws.onclose { puts "Connection closed" }
|
37
|
+
ws.onmessage { |msg|
|
38
|
+
puts "Recieved message: #{msg}"
|
39
|
+
ws.send "Pong: #{msg}"
|
40
|
+
}
|
41
|
+
end
|
42
|
+
}
|
43
|
+
```
|
44
|
+
|
45
|
+
## Secure server
|
46
|
+
|
47
|
+
It is possible to accept secure wss:// connections by passing :secure => true when opening the connection. Safari 5 does not currently support prompting on untrusted SSL certificates therefore using signed certificates is highly recommended. Pass a :tls_options hash containing keys as described in http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000296
|
48
|
+
|
49
|
+
For example,
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
WebSocket::EventMachine::Server.start({
|
53
|
+
:host => "0.0.0.0",
|
54
|
+
:port => 443,
|
55
|
+
:secure => true,
|
56
|
+
:tls_options => {
|
57
|
+
:private_key_file => "/private/key",
|
58
|
+
:cert_chain_file => "/ssl/certificate"
|
59
|
+
}
|
60
|
+
}) do |ws|
|
61
|
+
...
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
## Running behind an SSL Proxy/Terminator, like Stunnel
|
66
|
+
|
67
|
+
The :secure_proxy => true option makes it possible to run correctly when behind a secure SSL proxy/terminator like [Stunnel](http://www.stunnel.org/). When setting :secure_proxy => true, any reponse from the em-websocket which contains the websocket url will use the wss:// url scheme. None of the traffic is encrypted.
|
68
|
+
|
69
|
+
This option is necessary when using websockets with an SSL proxy/terminator on Safari 5.1.x or earlier, and also on Safari in iOS 5.x and earlier. Most versions of Chrome, Safari 5.2, and Safari in iOS 6 do not appear to have this problem.
|
70
|
+
|
71
|
+
For example,
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
WebSocket::EventMachine::Server.start({
|
75
|
+
:host => "0.0.0.0",
|
76
|
+
:port => 8080,
|
77
|
+
:secure_proxy => true
|
78
|
+
}) do |ws|
|
79
|
+
...
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
## Migrating from EM-WebSocket
|
84
|
+
|
85
|
+
This library is compatible with EM-WebSocket, so only thing you need to change is running server - you need to change from EM-WebSocket to WebSocket::EventMachine::Server in your application and everything will be working.
|
86
|
+
|
87
|
+
## License
|
88
|
+
|
89
|
+
The MIT License - Copyright (c) 2012 Bernard Potocki
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
# require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
# RSpec::Core::RakeTask.new do |t|
|
7
|
+
# t.rspec_opts = ["-c", "-f progress"]
|
8
|
+
# t.pattern = 'spec/**/*_spec.rb'
|
9
|
+
# end
|
10
|
+
|
11
|
+
# task :default => :spec
|
12
|
+
|
13
|
+
desc "Run autobahn tests for server"
|
14
|
+
task :autobahn do
|
15
|
+
system('wstest --mode=fuzzingclient --spec=autobahn.json')
|
16
|
+
end
|
data/autobahn.json
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
{
|
2
|
+
"options": {"failByDrop": false},
|
3
|
+
"outdir": "./autobahn",
|
4
|
+
|
5
|
+
"servers": [
|
6
|
+
{"agent": "WebSocket-EventMachine-Server<br>(1.0.0)", "url": "ws://localhost:9001", "options": {"version": 18}},
|
7
|
+
{"agent": "EM-WebSocket<br>(0.3.8)", "url": "ws://localhost:8080", "options": {"version": 18}}
|
8
|
+
],
|
9
|
+
|
10
|
+
"cases": ["*"],
|
11
|
+
"exclude-cases": [],
|
12
|
+
"exclude-agent-cases": {}
|
13
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path('../../lib/websocket-eventmachine-server', __FILE__)
|
2
|
+
|
3
|
+
EM.epoll
|
4
|
+
EM.run do
|
5
|
+
|
6
|
+
trap("TERM") { stop }
|
7
|
+
trap("INT") { stop }
|
8
|
+
|
9
|
+
WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 9001) do |ws|
|
10
|
+
|
11
|
+
ws.onmessage do |msg, type|
|
12
|
+
ws.send msg, :type => type
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
puts "Server started at port 9001"
|
18
|
+
|
19
|
+
def stop
|
20
|
+
puts "Terminating WebSocket Server"
|
21
|
+
EventMachine.stop
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path('../../lib/websocket-eventmachine-server', __FILE__)
|
2
|
+
|
3
|
+
EM.epoll
|
4
|
+
EM.run do
|
5
|
+
|
6
|
+
trap("TERM") { stop }
|
7
|
+
trap("INT") { stop }
|
8
|
+
|
9
|
+
WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 9001) do |ws|
|
10
|
+
|
11
|
+
ws.onopen do
|
12
|
+
puts "Client connected"
|
13
|
+
end
|
14
|
+
|
15
|
+
ws.onmessage do |msg, type|
|
16
|
+
puts "Received message: #{msg}"
|
17
|
+
ws.send msg, :type => type
|
18
|
+
end
|
19
|
+
|
20
|
+
ws.onclose do
|
21
|
+
puts "Client disconnected"
|
22
|
+
end
|
23
|
+
|
24
|
+
ws.onerror do |e|
|
25
|
+
puts "Error: #{e}"
|
26
|
+
end
|
27
|
+
|
28
|
+
ws.onping do |msg|
|
29
|
+
puts "Receied ping: #{msg}"
|
30
|
+
end
|
31
|
+
|
32
|
+
ws.onpong do |msg|
|
33
|
+
puts "Received pong: #{msg}"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
puts "Server started at port 9001"
|
39
|
+
|
40
|
+
def stop
|
41
|
+
puts "Terminating WebSocket Server"
|
42
|
+
EventMachine.stop
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path('../websocket/eventmachine/server', __FILE__)
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'websocket'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
module WebSocket
|
5
|
+
module EventMachine
|
6
|
+
|
7
|
+
# WebSocket Server (using EventMachine)
|
8
|
+
# @example
|
9
|
+
# WebSocket::EventMachine::Server.start(:host => "0.0.0.0", :port => 8080) do |ws|
|
10
|
+
# ws.onopen { ws.send "Hello Client!"}
|
11
|
+
# ws.onmessage { |msg| ws.send "Pong: #{msg}" }
|
12
|
+
# ws.onclose { puts "WebSocket closed" }
|
13
|
+
# ws.onerror { |e| puts "Error: #{e}" }
|
14
|
+
# end
|
15
|
+
class Server < ::EventMachine::Connection
|
16
|
+
|
17
|
+
###########
|
18
|
+
### API ###
|
19
|
+
###########
|
20
|
+
|
21
|
+
# Start server
|
22
|
+
# @param options [Hash] The request arguments
|
23
|
+
# @option args [String] :host The host IP/DNS name
|
24
|
+
# @option args [Integer] :port The port to connect too(default = 80)
|
25
|
+
def self.start(options, &block)
|
26
|
+
::EventMachine::start_server(options[:host], options[:port], self, options) do |c|
|
27
|
+
block.call(c)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Initialize connection
|
32
|
+
# @param args [Hash] Arguments for server
|
33
|
+
# @option args [Boolean] :debug Should server log debug data?
|
34
|
+
# @option args [Boolean] :secure If true then server will run over SSL
|
35
|
+
# @option args [Boolean] :secure_proxy If true then server will use wss protocol but will not encrypt connection. Usefull for sll proxies.
|
36
|
+
# @option args [Hash] :tls_options Options for SSL if secure = true
|
37
|
+
def initialize(args)
|
38
|
+
@debug = !!args[:debug]
|
39
|
+
@secure = !!args[:secure]
|
40
|
+
@secure_proxy = args[:secure_proxy] || @secure
|
41
|
+
@tls_options = args[:tls_options] || {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Called when connection is opened.
|
45
|
+
# No parameters are passed to block
|
46
|
+
def onopen(&blk); @onopen = blk; end
|
47
|
+
|
48
|
+
# Called when connection is closed.
|
49
|
+
# No parameters are passed to block
|
50
|
+
def onclose(&blk); @onclose = blk; end
|
51
|
+
|
52
|
+
# Called when error occurs.
|
53
|
+
# One parameter passed to block:
|
54
|
+
# error - string with error message
|
55
|
+
def onerror(&blk); @onerror = blk; end
|
56
|
+
|
57
|
+
# Called when message is received from server.
|
58
|
+
# Two parameters passed to block:
|
59
|
+
# message - string with message sent to server
|
60
|
+
# type - type of message. Valid values are :text and :binary
|
61
|
+
def onmessage(&blk); @onmessage = blk; end
|
62
|
+
|
63
|
+
# Called when ping message is received from server.
|
64
|
+
# One parameter passed to block:
|
65
|
+
# message - string with ping message
|
66
|
+
def onping(&blk); @onping = blk; end
|
67
|
+
|
68
|
+
# Called when pond message is received from server.
|
69
|
+
# One parameter passed to block:
|
70
|
+
# message - string with pong message
|
71
|
+
def onpong(&blk); @onpong = blk; end
|
72
|
+
|
73
|
+
# Send data to client
|
74
|
+
# @param data [String] Data to send
|
75
|
+
# @param args [Hash] Arguments for send
|
76
|
+
# @option args [String] :type Type of frame to send - available types are "text", "binary", "ping", "pong" and "close"
|
77
|
+
# @option args [Integer] :code Code for close frame
|
78
|
+
# @return [Boolean] true if data was send, otherwise call on_error if needed
|
79
|
+
def send(data, args = {})
|
80
|
+
type = args[:type] || :text
|
81
|
+
unless type == :plain
|
82
|
+
frame = WebSocket::Frame::Outgoing::Server.new args.merge(:version => @handshake.version, :data => data)
|
83
|
+
if !frame.supported?
|
84
|
+
trigger_onerror("Frame type '#{type}' is not supported in protocol version #{@handshake.version}")
|
85
|
+
return false
|
86
|
+
elsif !frame.require_sending?
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
data = frame.to_s
|
90
|
+
end
|
91
|
+
debug "Sending raw: ", data
|
92
|
+
send_data(data)
|
93
|
+
true
|
94
|
+
end
|
95
|
+
|
96
|
+
# Close connection
|
97
|
+
# @return [Boolean] true if connection is closed immediately, false if waiting for server to close connection
|
98
|
+
def close(code = 1000, data = nil)
|
99
|
+
if @state == :open
|
100
|
+
@state = :closing
|
101
|
+
return false if send(data, :type => :close, :code => code)
|
102
|
+
else
|
103
|
+
send(data, :type => :close) if @state == :closing
|
104
|
+
@state = :closed
|
105
|
+
end
|
106
|
+
close_connection_after_writing
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
# Send ping message to client
|
111
|
+
# @return [Boolean] false if protocol version is not supporting ping requests
|
112
|
+
def ping(data = '')
|
113
|
+
send(data, :type => :ping)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Send pong message to client
|
117
|
+
# @return [Boolean] false if protocol version is not supporting pong requests
|
118
|
+
def pong(data = '')
|
119
|
+
send(data, :type => :pong)
|
120
|
+
end
|
121
|
+
|
122
|
+
############################
|
123
|
+
### EventMachine methods ###
|
124
|
+
############################
|
125
|
+
|
126
|
+
# Eventmachine internal
|
127
|
+
# @private
|
128
|
+
def post_init
|
129
|
+
@state = :connecting
|
130
|
+
@handshake = WebSocket::Handshake::Server.new(:secure => @secure_proxy)
|
131
|
+
start_tls(@tls_options) if @secure
|
132
|
+
end
|
133
|
+
|
134
|
+
# Eventmachine internal
|
135
|
+
# @private
|
136
|
+
def receive_data(data)
|
137
|
+
debug "Received raw: ", data
|
138
|
+
case @state
|
139
|
+
when :connecting then handle_connecting(data)
|
140
|
+
when :open then handle_open(data)
|
141
|
+
when :closing then handle_closing(data)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Eventmachine internal
|
146
|
+
# @private
|
147
|
+
def unbind
|
148
|
+
unless @state == :closed
|
149
|
+
@state = :closed
|
150
|
+
close
|
151
|
+
trigger_onclose('')
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
#######################
|
156
|
+
### Private methods ###
|
157
|
+
#######################
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
['onopen'].each do |m|
|
162
|
+
define_method "trigger_#{m}" do
|
163
|
+
callback = instance_variable_get("@#{m}")
|
164
|
+
callback.call if callback
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
['onerror', 'onping', 'onpong', 'onclose'].each do |m|
|
169
|
+
define_method "trigger_#{m}" do |data|
|
170
|
+
callback = instance_variable_get("@#{m}")
|
171
|
+
callback.call(data) if callback
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def trigger_onmessage(data, type)
|
176
|
+
@onmessage.call(data, type) if @onmessage
|
177
|
+
end
|
178
|
+
|
179
|
+
def handle_connecting(data)
|
180
|
+
@handshake << data
|
181
|
+
return unless @handshake.finished?
|
182
|
+
if @handshake.valid?
|
183
|
+
send(@handshake.to_s, :type => :plain) if @handshake.should_respond?
|
184
|
+
@frame = WebSocket::Frame::Incoming::Server.new(:version => @handshake.version)
|
185
|
+
@state = :open
|
186
|
+
trigger_onopen
|
187
|
+
handle_open(@handshake.leftovers) if @handshake.leftovers
|
188
|
+
else
|
189
|
+
trigger_onerror(@handshake.error)
|
190
|
+
close
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def handle_open(data)
|
195
|
+
@frame << data
|
196
|
+
while frame = @frame.next
|
197
|
+
case frame.type
|
198
|
+
when :close
|
199
|
+
@state = :closing
|
200
|
+
close
|
201
|
+
trigger_onclose(frame.to_s)
|
202
|
+
when :ping
|
203
|
+
pong(frame.to_s)
|
204
|
+
trigger_onping(frame.to_s)
|
205
|
+
when :pong
|
206
|
+
trigger_onpong(frame.to_s)
|
207
|
+
when :text
|
208
|
+
trigger_onmessage(frame.to_s, :text)
|
209
|
+
when :binary
|
210
|
+
trigger_onmessage(frame.to_s, :binary)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
unbind if @frame.error?
|
214
|
+
end
|
215
|
+
|
216
|
+
def handle_closing(data)
|
217
|
+
@state = :closed
|
218
|
+
close
|
219
|
+
trigger_onclose
|
220
|
+
end
|
221
|
+
|
222
|
+
def debug(description, data)
|
223
|
+
return unless @debug
|
224
|
+
puts(description + data.bytes.to_a.collect{|b| '\x' + b.to_s(16).rjust(2, '0')}.join) unless @state == :connecting
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "websocket/eventmachine/server/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "websocket-eventmachine-server"
|
7
|
+
s.version = WebSocket::EventMachine::Server::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Bernard Potocki"]
|
10
|
+
s.email = ["bernard.potocki@imanel.org"]
|
11
|
+
s.homepage = "http://github.com/imanel/websocket-eventmachine-server"
|
12
|
+
s.summary = %q{WebSocket server for Ruby}
|
13
|
+
s.description = %q{WebSocket server for Ruby}
|
14
|
+
|
15
|
+
s.add_dependency 'websocket', '~> 1.0'
|
16
|
+
s.add_dependency 'websocket-native', '~> 1.0'
|
17
|
+
s.add_dependency 'eventmachine', '~> 1.0'
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: websocket-eventmachine-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bernard Potocki
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: websocket
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: websocket-native
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: eventmachine
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
description: WebSocket server for Ruby
|
63
|
+
email:
|
64
|
+
- bernard.potocki@imanel.org
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- CHANGELOG.md
|
71
|
+
- Gemfile
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- autobahn.json
|
75
|
+
- examples/autobahn_server.rb
|
76
|
+
- examples/echo_server.rb
|
77
|
+
- lib/websocket-eventmachine-server.rb
|
78
|
+
- lib/websocket/eventmachine/server.rb
|
79
|
+
- lib/websocket/eventmachine/server/version.rb
|
80
|
+
- websocket-eventmachine-server.gemspec
|
81
|
+
homepage: http://github.com/imanel/websocket-eventmachine-server
|
82
|
+
licenses: []
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.8.24
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: WebSocket server for Ruby
|
105
|
+
test_files: []
|