websocket-eventmachine-server 1.0.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.
- 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: []
|