ed2-precompiled_websocket-driver 0.8.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 +7 -0
- data/CHANGELOG.md +169 -0
- data/LICENSE.md +12 -0
- data/README.md +394 -0
- data/ext/websocket-driver/WebsocketMaskService.java +57 -0
- data/ext/websocket-driver/extconf.rb +4 -0
- data/ext/websocket-driver/websocket_mask.c +32 -0
- data/lib/websocket/driver/client.rb +139 -0
- data/lib/websocket/driver/draft75.rb +102 -0
- data/lib/websocket/driver/draft76.rb +99 -0
- data/lib/websocket/driver/event_emitter.rb +54 -0
- data/lib/websocket/driver/headers.rb +45 -0
- data/lib/websocket/driver/hybi/frame.rb +20 -0
- data/lib/websocket/driver/hybi/message.rb +31 -0
- data/lib/websocket/driver/hybi.rb +426 -0
- data/lib/websocket/driver/proxy.rb +66 -0
- data/lib/websocket/driver/server.rb +80 -0
- data/lib/websocket/driver/stream_reader.rb +55 -0
- data/lib/websocket/driver.rb +248 -0
- data/lib/websocket/http/headers.rb +112 -0
- data/lib/websocket/http/request.rb +45 -0
- data/lib/websocket/http/response.rb +29 -0
- data/lib/websocket/http.rb +15 -0
- data/lib/websocket/mask.rb +14 -0
- data/lib/websocket/websocket_mask.rb +9 -0
- metadata +153 -0
@@ -0,0 +1,248 @@
|
|
1
|
+
# Protocol references:
|
2
|
+
#
|
3
|
+
# * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
|
4
|
+
# * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
5
|
+
# * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
|
6
|
+
|
7
|
+
require 'base64'
|
8
|
+
require 'digest/md5'
|
9
|
+
require 'digest/sha1'
|
10
|
+
require 'securerandom'
|
11
|
+
require 'set'
|
12
|
+
require 'stringio'
|
13
|
+
require 'uri'
|
14
|
+
require 'websocket/extensions'
|
15
|
+
|
16
|
+
module WebSocket
|
17
|
+
autoload :HTTP, File.expand_path('../http', __FILE__)
|
18
|
+
|
19
|
+
class Driver
|
20
|
+
|
21
|
+
root = File.expand_path('../driver', __FILE__)
|
22
|
+
|
23
|
+
begin
|
24
|
+
# Load C native extension
|
25
|
+
require 'websocket_mask'
|
26
|
+
rescue LoadError
|
27
|
+
# Fall back to pure-Ruby implementation
|
28
|
+
require 'websocket/mask'
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
if RUBY_PLATFORM =~ /java/
|
33
|
+
require 'jruby'
|
34
|
+
com.jcoglan.websocket.WebsocketMaskService.new.basicLoad(JRuby.runtime)
|
35
|
+
end
|
36
|
+
|
37
|
+
unless Mask.respond_to?(:mask)
|
38
|
+
def Mask.mask(payload, mask)
|
39
|
+
@instance ||= new
|
40
|
+
@instance.mask(payload, mask)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
MAX_LENGTH = 0x3ffffff
|
45
|
+
PORTS = { 'ws' => 80, 'wss' => 443 }
|
46
|
+
STATES = [:connecting, :open, :closing, :closed]
|
47
|
+
|
48
|
+
ConnectEvent = Struct.new(nil)
|
49
|
+
OpenEvent = Struct.new(nil)
|
50
|
+
MessageEvent = Struct.new(:data)
|
51
|
+
PingEvent = Struct.new(:data)
|
52
|
+
PongEvent = Struct.new(:data)
|
53
|
+
CloseEvent = Struct.new(:code, :reason)
|
54
|
+
|
55
|
+
ProtocolError = Class.new(StandardError)
|
56
|
+
URIError = Class.new(ArgumentError)
|
57
|
+
ConfigurationError = Class.new(ArgumentError)
|
58
|
+
|
59
|
+
autoload :Client, root + '/client'
|
60
|
+
autoload :Draft75, root + '/draft75'
|
61
|
+
autoload :Draft76, root + '/draft76'
|
62
|
+
autoload :EventEmitter, root + '/event_emitter'
|
63
|
+
autoload :Headers, root + '/headers'
|
64
|
+
autoload :Hybi, root + '/hybi'
|
65
|
+
autoload :Proxy, root + '/proxy'
|
66
|
+
autoload :Server, root + '/server'
|
67
|
+
autoload :StreamReader, root + '/stream_reader'
|
68
|
+
|
69
|
+
include EventEmitter
|
70
|
+
attr_reader :protocol, :ready_state
|
71
|
+
|
72
|
+
def initialize(socket, options = {})
|
73
|
+
super()
|
74
|
+
Driver.validate_options(options, [:max_length, :masking, :require_masking, :protocols, :binary_data_format])
|
75
|
+
|
76
|
+
@socket = socket
|
77
|
+
@reader = StreamReader.new
|
78
|
+
@options = options
|
79
|
+
@max_length = options[:max_length] || MAX_LENGTH
|
80
|
+
@headers = Headers.new
|
81
|
+
@queue = []
|
82
|
+
@ready_state = 0
|
83
|
+
|
84
|
+
@binary_data_format = options[:binary_data_format] || :string
|
85
|
+
end
|
86
|
+
|
87
|
+
def state
|
88
|
+
return nil unless @ready_state >= 0
|
89
|
+
STATES[@ready_state]
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_extension(extension)
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
96
|
+
def set_header(name, value)
|
97
|
+
return false unless @ready_state <= 0
|
98
|
+
@headers[name] = value
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def start
|
103
|
+
return false unless @ready_state == 0
|
104
|
+
|
105
|
+
unless Driver.websocket?(@socket.env)
|
106
|
+
return fail_handshake(ProtocolError.new('Not a WebSocket request'))
|
107
|
+
end
|
108
|
+
|
109
|
+
begin
|
110
|
+
response = handshake_response
|
111
|
+
rescue => error
|
112
|
+
return fail_handshake(error)
|
113
|
+
end
|
114
|
+
|
115
|
+
@socket.write(response)
|
116
|
+
open unless @stage == -1
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
def text(message)
|
121
|
+
message = Driver.encode(message, Encoding::UTF_8)
|
122
|
+
frame(message, :text)
|
123
|
+
end
|
124
|
+
|
125
|
+
def binary(message)
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
def ping(*args)
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
133
|
+
def pong(*args)
|
134
|
+
false
|
135
|
+
end
|
136
|
+
|
137
|
+
def close(reason = nil, code = nil)
|
138
|
+
return false unless @ready_state == 1
|
139
|
+
@ready_state = 3
|
140
|
+
emit(:close, CloseEvent.new(nil, nil))
|
141
|
+
true
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def fail_handshake(error)
|
147
|
+
headers = Headers.new
|
148
|
+
headers['Content-Type'] = 'text/plain'
|
149
|
+
headers['Content-Length'] = error.message.bytesize
|
150
|
+
|
151
|
+
headers = ['HTTP/1.1 400 Bad Request', headers.to_s, error.message]
|
152
|
+
@socket.write(headers.join("\r\n"))
|
153
|
+
fail(:protocol_error, error.message)
|
154
|
+
|
155
|
+
false
|
156
|
+
end
|
157
|
+
|
158
|
+
def fail(type, message)
|
159
|
+
@ready_state = 2
|
160
|
+
emit(:error, ProtocolError.new(message))
|
161
|
+
close
|
162
|
+
end
|
163
|
+
|
164
|
+
def open
|
165
|
+
@ready_state = 1
|
166
|
+
@queue.each { |message| frame(*message) }
|
167
|
+
@queue = []
|
168
|
+
emit(:open, OpenEvent.new)
|
169
|
+
end
|
170
|
+
|
171
|
+
def queue(message)
|
172
|
+
@queue << message
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.client(socket, options = {})
|
177
|
+
Client.new(socket, options.merge(:masking => true))
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.server(socket, options = {})
|
181
|
+
Server.new(socket, options.merge(:require_masking => true))
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.rack(socket, options = {})
|
185
|
+
env = socket.env
|
186
|
+
version = env['HTTP_SEC_WEBSOCKET_VERSION']
|
187
|
+
key = env['HTTP_SEC_WEBSOCKET_KEY']
|
188
|
+
key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
|
189
|
+
key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
|
190
|
+
|
191
|
+
if version or key
|
192
|
+
Hybi.new(socket, options.merge(:require_masking => true))
|
193
|
+
elsif key1 or key2
|
194
|
+
Draft76.new(socket, options)
|
195
|
+
else
|
196
|
+
Draft75.new(socket, options)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.encode(data, encoding = nil)
|
201
|
+
if Array === data
|
202
|
+
data = data.pack('C*')
|
203
|
+
encoding ||= Encoding::BINARY
|
204
|
+
end
|
205
|
+
|
206
|
+
return data if encoding.nil? or data.encoding == encoding
|
207
|
+
|
208
|
+
if data.encoding == Encoding::BINARY
|
209
|
+
data = data.dup if data.frozen?
|
210
|
+
data.force_encoding(encoding)
|
211
|
+
else
|
212
|
+
data.encode(encoding)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.host_header(uri)
|
217
|
+
host = uri.host
|
218
|
+
if uri.port and uri.port != PORTS[uri.scheme]
|
219
|
+
host += ":#{uri.port}"
|
220
|
+
end
|
221
|
+
host
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.validate_options(options, valid_keys)
|
225
|
+
options.keys.each do |key|
|
226
|
+
unless valid_keys.include?(key)
|
227
|
+
raise ConfigurationError, "Unrecognized option: #{ key.inspect }"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
if options[:binary_data_format]
|
232
|
+
unless [:array, :string].include?(options[:binary_data_format])
|
233
|
+
raise ConfigurationError, "Invalid :binary_data_format: #{options[:binary_data_format].inspect}"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.websocket?(env)
|
239
|
+
connection = env['HTTP_CONNECTION'] || ''
|
240
|
+
upgrade = env['HTTP_UPGRADE'] || ''
|
241
|
+
|
242
|
+
env['REQUEST_METHOD'] == 'GET' and
|
243
|
+
connection.downcase.split(/ *, */).include?('upgrade') and
|
244
|
+
upgrade.downcase == 'websocket'
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module HTTP
|
3
|
+
|
4
|
+
module Headers
|
5
|
+
MAX_LINE_LENGTH = 4096
|
6
|
+
CR = 0x0D
|
7
|
+
LF = 0x0A
|
8
|
+
|
9
|
+
# RFC 2616 grammar rules:
|
10
|
+
#
|
11
|
+
# CHAR = <any US-ASCII character (octets 0 - 127)>
|
12
|
+
#
|
13
|
+
# CTL = <any US-ASCII control character
|
14
|
+
# (octets 0 - 31) and DEL (127)>
|
15
|
+
#
|
16
|
+
# SP = <US-ASCII SP, space (32)>
|
17
|
+
#
|
18
|
+
# HT = <US-ASCII HT, horizontal-tab (9)>
|
19
|
+
#
|
20
|
+
# token = 1*<any CHAR except CTLs or separators>
|
21
|
+
#
|
22
|
+
# separators = "(" | ")" | "<" | ">" | "@"
|
23
|
+
# | "," | ";" | ":" | "\" | <">
|
24
|
+
# | "/" | "[" | "]" | "?" | "="
|
25
|
+
# | "{" | "}" | SP | HT
|
26
|
+
#
|
27
|
+
# Or, as redefined in RFC 7230:
|
28
|
+
#
|
29
|
+
# token = 1*tchar
|
30
|
+
#
|
31
|
+
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
|
32
|
+
# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
|
33
|
+
# / DIGIT / ALPHA
|
34
|
+
# ; any VCHAR, except delimiters
|
35
|
+
|
36
|
+
HEADER_LINE = /^([!#\$%&'\*\+\-\.\^_`\|~0-9a-z]+):\s*([\x20-\x7e]*?)\s*$/i
|
37
|
+
|
38
|
+
attr_reader :headers
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@buffer = []
|
42
|
+
@env = {}
|
43
|
+
@headers = {}
|
44
|
+
@stage = 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def complete?
|
48
|
+
@stage == 2
|
49
|
+
end
|
50
|
+
|
51
|
+
def error?
|
52
|
+
@stage == -1
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse(chunk)
|
56
|
+
chunk.each_byte do |octet|
|
57
|
+
if octet == LF and @stage < 2
|
58
|
+
@buffer.pop if @buffer.last == CR
|
59
|
+
if @buffer.empty?
|
60
|
+
complete if @stage == 1
|
61
|
+
else
|
62
|
+
result = case @stage
|
63
|
+
when 0 then start_line(string_buffer)
|
64
|
+
when 1 then header_line(string_buffer)
|
65
|
+
end
|
66
|
+
|
67
|
+
if result
|
68
|
+
@stage = 1
|
69
|
+
else
|
70
|
+
error
|
71
|
+
end
|
72
|
+
end
|
73
|
+
@buffer = []
|
74
|
+
else
|
75
|
+
@buffer << octet if @stage >= 0
|
76
|
+
error if @stage < 2 and @buffer.size > MAX_LINE_LENGTH
|
77
|
+
end
|
78
|
+
end
|
79
|
+
@env['rack.input'] = StringIO.new(string_buffer)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def complete
|
85
|
+
@stage = 2
|
86
|
+
end
|
87
|
+
|
88
|
+
def error
|
89
|
+
@stage = -1
|
90
|
+
end
|
91
|
+
|
92
|
+
def header_line(line)
|
93
|
+
return false unless parsed = line.scan(HEADER_LINE).first
|
94
|
+
|
95
|
+
key = HTTP.normalize_header(parsed[0])
|
96
|
+
value = parsed[1].strip
|
97
|
+
|
98
|
+
if @headers.has_key?(key)
|
99
|
+
@headers[key] << ', ' << value
|
100
|
+
else
|
101
|
+
@headers[key] = value
|
102
|
+
end
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def string_buffer
|
107
|
+
@buffer.pack('C*')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module HTTP
|
3
|
+
|
4
|
+
class Request
|
5
|
+
include Headers
|
6
|
+
|
7
|
+
REQUEST_LINE = /^(OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT) ([\x21-\x7e]+) (HTTP\/[0-9]+\.[0-9]+)$/
|
8
|
+
REQUEST_TARGET = /^(.*?)(\?(.*))?$/
|
9
|
+
RESERVED_HEADERS = %w[content-length content-type]
|
10
|
+
|
11
|
+
attr_reader :env
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def start_line(line)
|
16
|
+
return false unless parsed = line.scan(REQUEST_LINE).first
|
17
|
+
|
18
|
+
target = parsed[1].scan(REQUEST_TARGET).first
|
19
|
+
|
20
|
+
@env = {
|
21
|
+
'REQUEST_METHOD' => parsed[0],
|
22
|
+
'SCRIPT_NAME' => '',
|
23
|
+
'PATH_INFO' => target[0],
|
24
|
+
'QUERY_STRING' => target[2] || ''
|
25
|
+
}
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def complete
|
30
|
+
super
|
31
|
+
@headers.each do |name, value|
|
32
|
+
rack_name = name.upcase.gsub(/-/, '_')
|
33
|
+
rack_name = "HTTP_#{ rack_name }" unless RESERVED_HEADERS.include?(name)
|
34
|
+
@env[rack_name] = value
|
35
|
+
end
|
36
|
+
if host = @env['HTTP_HOST']
|
37
|
+
uri = URI.parse("http://#{ host }")
|
38
|
+
@env['SERVER_NAME'] = uri.host
|
39
|
+
@env['SERVER_PORT'] = uri.port.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module HTTP
|
3
|
+
|
4
|
+
class Response
|
5
|
+
include Headers
|
6
|
+
|
7
|
+
STATUS_LINE = /^(HTTP\/[0-9]+\.[0-9]+) ([0-9]{3}) ([\x20-\x7e]*)$/
|
8
|
+
|
9
|
+
attr_reader :code
|
10
|
+
|
11
|
+
def [](name)
|
12
|
+
@headers[HTTP.normalize_header(name)]
|
13
|
+
end
|
14
|
+
|
15
|
+
def body
|
16
|
+
@buffer.pack('C*')
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def start_line(line)
|
22
|
+
return false unless parsed = line.scan(STATUS_LINE).first
|
23
|
+
@code = parsed[1].to_i
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module HTTP
|
3
|
+
|
4
|
+
root = File.expand_path('../http', __FILE__)
|
5
|
+
|
6
|
+
autoload :Headers, root + '/headers'
|
7
|
+
autoload :Request, root + '/request'
|
8
|
+
autoload :Response, root + '/response'
|
9
|
+
|
10
|
+
def self.normalize_header(name)
|
11
|
+
name.to_s.strip.downcase.gsub(/^http_/, '').gsub(/_/, '-')
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module Mask
|
3
|
+
def self.mask(payload, mask)
|
4
|
+
return payload if mask.nil? || payload.empty?
|
5
|
+
|
6
|
+
payload.tap do |result|
|
7
|
+
payload.bytesize.times do |i|
|
8
|
+
result.setbyte(i, payload.getbyte(i) ^ mask.getbyte(i % 4))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ed2-precompiled_websocket-driver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Coglan
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: base64
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: websocket-extensions
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.1.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.1.0
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: eventmachine
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: permessage_deflate
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rake-compiler
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: rspec
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
email: jcoglan@gmail.com
|
97
|
+
executables: []
|
98
|
+
extensions:
|
99
|
+
- ext/websocket-driver/extconf.rb
|
100
|
+
extra_rdoc_files:
|
101
|
+
- README.md
|
102
|
+
files:
|
103
|
+
- CHANGELOG.md
|
104
|
+
- LICENSE.md
|
105
|
+
- README.md
|
106
|
+
- ext/websocket-driver/WebsocketMaskService.java
|
107
|
+
- ext/websocket-driver/extconf.rb
|
108
|
+
- ext/websocket-driver/websocket_mask.c
|
109
|
+
- lib/websocket/driver.rb
|
110
|
+
- lib/websocket/driver/client.rb
|
111
|
+
- lib/websocket/driver/draft75.rb
|
112
|
+
- lib/websocket/driver/draft76.rb
|
113
|
+
- lib/websocket/driver/event_emitter.rb
|
114
|
+
- lib/websocket/driver/headers.rb
|
115
|
+
- lib/websocket/driver/hybi.rb
|
116
|
+
- lib/websocket/driver/hybi/frame.rb
|
117
|
+
- lib/websocket/driver/hybi/message.rb
|
118
|
+
- lib/websocket/driver/proxy.rb
|
119
|
+
- lib/websocket/driver/server.rb
|
120
|
+
- lib/websocket/driver/stream_reader.rb
|
121
|
+
- lib/websocket/http.rb
|
122
|
+
- lib/websocket/http/headers.rb
|
123
|
+
- lib/websocket/http/request.rb
|
124
|
+
- lib/websocket/http/response.rb
|
125
|
+
- lib/websocket/mask.rb
|
126
|
+
- lib/websocket/websocket_mask.rb
|
127
|
+
homepage: https://github.com/faye/websocket-driver-ruby
|
128
|
+
licenses:
|
129
|
+
- Apache-2.0
|
130
|
+
metadata:
|
131
|
+
changelog_uri: https://github.com/faye/websocket-driver-ruby/blob/main/CHANGELOG.md
|
132
|
+
rdoc_options:
|
133
|
+
- "--main"
|
134
|
+
- README.md
|
135
|
+
- "--markup"
|
136
|
+
- markdown
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubygems_version: 3.6.7
|
151
|
+
specification_version: 4
|
152
|
+
summary: WebSocket protocol handler with pluggable I/O
|
153
|
+
test_files: []
|