thin-em-websocket-gmalette 0.0.1
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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +26 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/examples/Gemfile +1 -0
- data/examples/config.ru +38 -0
- data/lib/thin-em-websocket.rb +282 -0
- data/lib/thin-em-websocket/version.rb +5 -0
- data/thin-em-websocket.gemspec +21 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3e56114ee23b9f4aba27e061eb7f9c850c225510
|
4
|
+
data.tar.gz: f15eb765178e22d51559af04e72f6cdd5c8bb51f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 945be7f0b09249e287eed6be7adeaca7b6f0f6165f6397a1c5d21f0f752e0a20993bd1e308cfb107291b559df194590f3d6d1ffd57fa7ceb045275273d158517
|
7
|
+
data.tar.gz: 1b94b5351db6402683a36ee0280f967bf4e1e4e1c6d7da99501a95e056802298393325d65c98dc9ed56ba2ea06c549b85e5f25e56263849e449d5f8bd125c0a9
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2012 Sam Saffron
|
2
|
+
|
3
|
+
portions from em-websocket
|
4
|
+
Copyright (c) 2009 Ilya Grigorik
|
5
|
+
|
6
|
+
|
7
|
+
MIT License
|
8
|
+
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
a copy of this software and associated documentation files (the
|
11
|
+
"Software"), to deal in the Software without restriction, including
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
the following conditions:
|
16
|
+
|
17
|
+
The above copyright notice and this permission notice shall be
|
18
|
+
included in all copies or substantial portions of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Thin::Em::Websocket
|
2
|
+
|
3
|
+
Thin support for the em-websocket gem
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'thin-em-websocket'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install thin-em-websocket
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/examples/Gemfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
gem 'thin-em-websocket', :path => '../'
|
data/examples/config.ru
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'thin-em-websocket'
|
2
|
+
|
3
|
+
echo = lambda do |env|
|
4
|
+
connection = env['em.connection']
|
5
|
+
if connection && connection.websocket?
|
6
|
+
puts "upgrading web socket"
|
7
|
+
begin
|
8
|
+
connection.upgrade_websocket
|
9
|
+
rescue => e
|
10
|
+
p "#{e}"
|
11
|
+
e.backtrace.each do |b|
|
12
|
+
p b
|
13
|
+
end
|
14
|
+
end
|
15
|
+
connection.onmessage { |m|
|
16
|
+
puts "GOT #{m}"
|
17
|
+
connection.send(m)
|
18
|
+
puts "SEND #{m}"
|
19
|
+
}
|
20
|
+
return Thin::Connection::AsyncResponse
|
21
|
+
end
|
22
|
+
|
23
|
+
[200, {"Content-Type" => "text/html"}, [<<-HTML
|
24
|
+
<html>
|
25
|
+
<body>
|
26
|
+
<script>
|
27
|
+
socket = new WebSocket("ws://" + document.location.hostname + ":" + (document.location.port || 80) + "/ws");
|
28
|
+
socket.onerror = function(m){ alert("error: " + m); };
|
29
|
+
socket.onopen = function() { alert("connected"); socket.send("hello world") };
|
30
|
+
socket.onmessage = function(msg) { alert(msg.data); }
|
31
|
+
</script>
|
32
|
+
</body>
|
33
|
+
</html>
|
34
|
+
HTML
|
35
|
+
]]
|
36
|
+
end
|
37
|
+
|
38
|
+
run echo
|
@@ -0,0 +1,282 @@
|
|
1
|
+
# much of the code here is lifted from https://github.com/igrigorik/em-websocket/blob/master/lib/em-websocket/connection.rb
|
2
|
+
|
3
|
+
require "thin-em-websocket/version"
|
4
|
+
require "em-websocket"
|
5
|
+
|
6
|
+
module ThinEM
|
7
|
+
module Websocket
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# connection SHIM, so we only override minimal amounts of thin
|
12
|
+
class ThinEM::Websocket::Connection
|
13
|
+
attr_writer :max_frame_size
|
14
|
+
|
15
|
+
# define WebSocket callbacks
|
16
|
+
def onopen(&blk); @onopen = blk; end
|
17
|
+
def onclose(&blk); @onclose = blk; end
|
18
|
+
def onerror(&blk); @onerror = blk; end
|
19
|
+
def onmessage(&blk); @onmessage = blk; end
|
20
|
+
def onping(&blk); @onping = blk; end
|
21
|
+
def onpong(&blk); @onpong = blk; end
|
22
|
+
|
23
|
+
def trigger_on_message(msg)
|
24
|
+
@onmessage.call(msg) if @onmessage
|
25
|
+
end
|
26
|
+
def trigger_on_open
|
27
|
+
@onopen.call if @onopen
|
28
|
+
end
|
29
|
+
def trigger_on_close
|
30
|
+
@onclose.call if @onclose
|
31
|
+
end
|
32
|
+
def trigger_on_ping(data)
|
33
|
+
@onping.call(data) if @onping
|
34
|
+
end
|
35
|
+
def trigger_on_pong(data)
|
36
|
+
@onpong.call(data) if @onpong
|
37
|
+
end
|
38
|
+
def trigger_on_error(reason)
|
39
|
+
return false unless @onerror
|
40
|
+
@onerror.call(reason)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(connection)
|
45
|
+
@connection = connection
|
46
|
+
@logger = Class.new do
|
47
|
+
def error(m); end
|
48
|
+
def warn(m); end
|
49
|
+
def debug(m); end
|
50
|
+
def info(m); end
|
51
|
+
end.new
|
52
|
+
end
|
53
|
+
|
54
|
+
def logger=(logger)
|
55
|
+
@logger = logger
|
56
|
+
end
|
57
|
+
|
58
|
+
def websocket?
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def upgrade_websocket
|
63
|
+
return if @handler
|
64
|
+
@handler = EM::WebSocket::HandlerFactory.build(self, @connection.ws_buffer, false, nil)
|
65
|
+
unless @handler
|
66
|
+
# see: https://github.com/learnboost/socket.io/commit/9982232032771574ceb68e2bccee4e43fd5af887#diff-0
|
67
|
+
# hixie-76 behind HAProxy gets a bit messy, we need to send the header first to unblock the stream
|
68
|
+
if !@sent_upgrade && @connection.ws_buffer =~ /sec-websocket-key1/i
|
69
|
+
@logger.info("WebSocket: attempting hixie 76 hack")
|
70
|
+
|
71
|
+
fake_buffer = @connection.ws_buffer.dup
|
72
|
+
fake_buffer << "12345678"
|
73
|
+
(header, remains) = fake_buffer.split("\r\n\r\n", 2)
|
74
|
+
fake_handler = EM::WebSocket::HandlerFactory.build(self, fake_buffer, false, nil)
|
75
|
+
|
76
|
+
@handshake_76_without_verify = fake_handler.handshake[0..-17]
|
77
|
+
send_data(@handshake_76_without_verify)
|
78
|
+
@sent_upgrade = true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
@connection.set_comm_inactivity_timeout(0) if @handler
|
82
|
+
@handler.run if @handler
|
83
|
+
end
|
84
|
+
|
85
|
+
def upgraded?
|
86
|
+
!@handler.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
def pending_upgrade?
|
90
|
+
@handler.nil? && @sent_upgrade
|
91
|
+
end
|
92
|
+
|
93
|
+
# Cache encodings since it's moderately expensive to look them up each time
|
94
|
+
ENCODING_SUPPORTED = "string".respond_to?(:force_encoding)
|
95
|
+
UTF8 = Encoding.find("UTF-8") if ENCODING_SUPPORTED
|
96
|
+
BINARY = Encoding.find("BINARY") if ENCODING_SUPPORTED
|
97
|
+
|
98
|
+
|
99
|
+
def send_data(data)
|
100
|
+
if @sent_upgrade && !@upgrade_stripped
|
101
|
+
# strip it
|
102
|
+
raise EventMachine::WebSocket::HandshakeError if @handshake_76_without_verify != data[0..@handshake_76_without_verify.length-1]
|
103
|
+
data = data[@handshake_76_without_verify.length..-1]
|
104
|
+
@upgrade_stripped = true
|
105
|
+
end
|
106
|
+
@connection.send_data(data)
|
107
|
+
end
|
108
|
+
|
109
|
+
def receive_data(data)
|
110
|
+
begin
|
111
|
+
@logger.info("Got Socket Data (l: #{data.length})")
|
112
|
+
@handler.receive_data(data)
|
113
|
+
rescue EventMachine::WebSocket::HandshakeError => e
|
114
|
+
@logger.warn("Web Socket Failed to handshake")
|
115
|
+
trigger_on_error(e)
|
116
|
+
# Errors during the handshake require the connection to be aborted
|
117
|
+
abort
|
118
|
+
rescue EventMachine::WebSocket::WSProtocolError => e
|
119
|
+
@logger.warn("Web Socket Protocol Error")
|
120
|
+
trigger_on_error(e)
|
121
|
+
close_websocket_private(e.code)
|
122
|
+
rescue => e
|
123
|
+
# These are application errors - raise unless onerror defined
|
124
|
+
trigger_on_error(e) || raise(e)
|
125
|
+
# There is no code defined for application errors, so use 3000
|
126
|
+
# (which is reserved for frameworks)
|
127
|
+
close_websocket_private(3000)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Send a WebSocket text frame.
|
132
|
+
#
|
133
|
+
# A WebSocketError may be raised if the connection is in an opening or a
|
134
|
+
# closing state, or if the passed in data is not valid UTF-8
|
135
|
+
#
|
136
|
+
def send(data)
|
137
|
+
# If we're using Ruby 1.9, be pedantic about encodings
|
138
|
+
if ENCODING_SUPPORTED
|
139
|
+
# Also accept ascii only data in other encodings for convenience
|
140
|
+
unless (data.encoding == UTF8 && data.valid_encoding?) || data.ascii_only?
|
141
|
+
raise WebSocketError, "Data sent to WebSocket must be valid UTF-8 but was #{data.encoding} (valid: #{data.valid_encoding?})"
|
142
|
+
end
|
143
|
+
# This labels the encoding as binary so that it can be combined with
|
144
|
+
# the BINARY framing
|
145
|
+
data.force_encoding(BINARY)
|
146
|
+
else
|
147
|
+
# TODO: Check that data is valid UTF-8
|
148
|
+
end
|
149
|
+
|
150
|
+
if @handler
|
151
|
+
@handler.send_text_frame(data)
|
152
|
+
else
|
153
|
+
raise WebSocketError, "Cannot send data before onopen callback"
|
154
|
+
end
|
155
|
+
|
156
|
+
# Revert data back to the original encoding (which we assume is UTF-8)
|
157
|
+
# Doing this to avoid duping the string - there may be a better way
|
158
|
+
data.force_encoding(UTF8) if ENCODING_SUPPORTED
|
159
|
+
return nil
|
160
|
+
end
|
161
|
+
|
162
|
+
# Send a ping to the client. The client must respond with a pong.
|
163
|
+
#
|
164
|
+
# In the case that the client is running a WebSocket draft < 01, false
|
165
|
+
# is returned since ping & pong are not supported
|
166
|
+
#
|
167
|
+
def ping(body = '')
|
168
|
+
if @handler
|
169
|
+
@handler.pingable? ? @handler.send_frame(:ping, body) && true : false
|
170
|
+
else
|
171
|
+
raise WebSocketError, "Cannot ping before onopen callback"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Send an unsolicited pong message, as allowed by the protocol. The
|
176
|
+
# client is not expected to respond to this message.
|
177
|
+
#
|
178
|
+
# em-websocket automatically takes care of sending pong replies to
|
179
|
+
# incoming ping messages, as the protocol demands.
|
180
|
+
#
|
181
|
+
def pong(body = '')
|
182
|
+
if @handler
|
183
|
+
@handler.pingable? ? @handler.send_frame(:pong, body) && true : false
|
184
|
+
else
|
185
|
+
raise WebSocketError, "Cannot ping before onopen callback"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Test whether the connection is pingable (i.e. the WebSocket draft in
|
190
|
+
# use is >= 01)
|
191
|
+
def pingable?
|
192
|
+
if @handler
|
193
|
+
@handler.pingable?
|
194
|
+
else
|
195
|
+
raise WebSocketError, "Cannot test whether pingable before onopen callback"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
def state
|
201
|
+
@handler ? @handler.state : :handshake
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns the maximum frame size which this connection is configured to
|
205
|
+
# accept. This can be set globally or on a per connection basis, and
|
206
|
+
# defaults to a value of 10MB if not set.
|
207
|
+
#
|
208
|
+
# The behaviour when a too large frame is received varies by protocol,
|
209
|
+
# but in the newest protocols the connection will be closed with the
|
210
|
+
# correct close code (1009) immediately after receiving the frame header
|
211
|
+
#
|
212
|
+
def max_frame_size
|
213
|
+
@max_frame_size || EventMachine::WebSocket.max_frame_size
|
214
|
+
end
|
215
|
+
|
216
|
+
def close_connection_after_writing()
|
217
|
+
@connection.close_connection_after_writing()
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
|
222
|
+
# As definited in draft 06 7.2.2, some failures require that the server
|
223
|
+
# abort the websocket connection rather than close cleanly
|
224
|
+
def abort
|
225
|
+
@connection.close_connection
|
226
|
+
end
|
227
|
+
|
228
|
+
def close_websocket_private(code, body = nil)
|
229
|
+
if @handler
|
230
|
+
@handler.close_websocket(code, body)
|
231
|
+
else
|
232
|
+
# The handshake hasn't completed - should be safe to terminate
|
233
|
+
abort
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
class Thin::Connection
|
240
|
+
# based off https://github.com/faye/faye-websocket-ruby/blob/master/lib/faye/adapters/thin.rb
|
241
|
+
# and code in em-websocket
|
242
|
+
|
243
|
+
alias :thin_process :process
|
244
|
+
alias :thin_receive_data :receive_data
|
245
|
+
|
246
|
+
attr_reader :ws_buffer
|
247
|
+
|
248
|
+
|
249
|
+
def process
|
250
|
+
if websocket? && !@request.env['em.connection']
|
251
|
+
@socket_connection = ThinEM::Websocket::Connection.new(self)
|
252
|
+
@request.env['em.connection'] = @socket_connection
|
253
|
+
@response.persistent!
|
254
|
+
end
|
255
|
+
thin_process
|
256
|
+
end
|
257
|
+
|
258
|
+
def receive_data(data)
|
259
|
+
if @socket_connection && @socket_connection.upgraded?
|
260
|
+
@socket_connection.receive_data(data)
|
261
|
+
else
|
262
|
+
@ws_buffer ||= ""
|
263
|
+
@ws_buffer << data unless @ws_buffer == false
|
264
|
+
@ws_buffer = false if @ws_buffer.length > 10000 # some sane cutoff so we dont have too much data in memory
|
265
|
+
@socket_connection.upgrade_websocket if @socket_connection && @socket_connection.pending_upgrade?
|
266
|
+
thin_receive_data(data)
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
def websocket?
|
272
|
+
return @websocket unless @websocket == nil
|
273
|
+
env = @request.env
|
274
|
+
@websocket =
|
275
|
+
env['REQUEST_METHOD'] == 'GET' and
|
276
|
+
env['HTTP_CONNECTION'] and
|
277
|
+
env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and
|
278
|
+
env['HTTP_UPGRADE'].downcase == 'websocket'
|
279
|
+
end
|
280
|
+
|
281
|
+
|
282
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/thin-em-websocket/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Sam Saffron"]
|
6
|
+
gem.email = ["sam.saffron@gmail.com"]
|
7
|
+
gem.description = %q{thin support for em-websocket}
|
8
|
+
gem.summary = %q{thin support for em-websocket}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "thin-em-websocket-gmalette"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = ThinEM::Websocket::VERSION
|
17
|
+
gem.add_dependency('em-websocket', '>= 0.3.8')
|
18
|
+
gem.add_dependency('thin', '>= 1.4.1')
|
19
|
+
|
20
|
+
gem.add_development_dependency('rake')
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thin-em-websocket-gmalette
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sam Saffron
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: em-websocket
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.8
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.3.8
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thin
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.4.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.4.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: thin support for em-websocket
|
56
|
+
email:
|
57
|
+
- sam.saffron@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- examples/Gemfile
|
68
|
+
- examples/config.ru
|
69
|
+
- lib/thin-em-websocket.rb
|
70
|
+
- lib/thin-em-websocket/version.rb
|
71
|
+
- thin-em-websocket.gemspec
|
72
|
+
homepage: ''
|
73
|
+
licenses: []
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.2.2
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: thin support for em-websocket
|
95
|
+
test_files: []
|