websocket-driver 0.1.0 → 0.2.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/CHANGELOG.md +9 -0
- data/README.md +65 -3
- data/examples/tcp_server.rb +27 -0
- data/lib/websocket/driver.rb +21 -5
- data/lib/websocket/driver/client.rb +16 -21
- data/lib/websocket/driver/draft75.rb +1 -0
- data/lib/websocket/driver/draft76.rb +1 -0
- data/lib/websocket/driver/headers.rb +42 -0
- data/lib/websocket/driver/hybi.rb +1 -1
- data/lib/websocket/driver/server.rb +73 -0
- data/lib/websocket/http.rb +16 -0
- data/lib/websocket/http/headers.rb +77 -0
- data/lib/websocket/http/request.rb +46 -0
- data/lib/websocket/http/response.rb +30 -0
- metadata +30 -7
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -61,7 +61,7 @@ Server-side sockets require one additional method:
|
|
61
61
|
* `REQUEST_METHOD`, the request's HTTP verb
|
62
62
|
|
63
63
|
|
64
|
-
### Server-side
|
64
|
+
### Server-side with Rack
|
65
65
|
|
66
66
|
To handle a server-side WebSocket connection, you need to check whether the
|
67
67
|
request is a WebSocket handshake, and if so create a protocol driver for it.
|
@@ -127,6 +127,53 @@ end
|
|
127
127
|
The driver API is described in full below.
|
128
128
|
|
129
129
|
|
130
|
+
### Server-side with TCP
|
131
|
+
|
132
|
+
You can also handle WebSocket connections in a bare TCP server, if you're not
|
133
|
+
using Rack and don't want to implement HTTP parsing yourself. For this, your
|
134
|
+
socket object only needs a `write` method.
|
135
|
+
|
136
|
+
The driver will emit a `:connect` event when a request is received, and at this
|
137
|
+
point you can detect whether it's a WebSocket and handle it as such. Here's an
|
138
|
+
example using an EventMachine TCP server.
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
module Connection
|
142
|
+
def initialize
|
143
|
+
@driver = WebSocket::Driver.server(self)
|
144
|
+
|
145
|
+
@driver.on(:connect) do
|
146
|
+
if WebSocket::Driver.websocket?(@driver.env)
|
147
|
+
@driver.start
|
148
|
+
else
|
149
|
+
# handle other HTTP requests
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
@driver.on(:message) { |e| @driver.text(e.data) }
|
154
|
+
@driver.on(:close) { |e| close_connection_after_writing }
|
155
|
+
end
|
156
|
+
|
157
|
+
def receive_data(data)
|
158
|
+
@driver.parse(data)
|
159
|
+
end
|
160
|
+
|
161
|
+
def write(data)
|
162
|
+
send_data(data)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
EM.run {
|
167
|
+
EM.start_server('127.0.0.1', 4180, Connection)
|
168
|
+
}
|
169
|
+
```
|
170
|
+
|
171
|
+
In the `:connect` event, `@driver.env` is a Rack env representing the request.
|
172
|
+
If the request has a body, it will be in the `@driver.env['rack.input']`
|
173
|
+
stream, but only as much of the body as you have so far routed to it using the
|
174
|
+
`parse` method.
|
175
|
+
|
176
|
+
|
130
177
|
### Client-side
|
131
178
|
|
132
179
|
Similarly, to implement a WebSocket client you need an object with `url` and
|
@@ -139,6 +186,12 @@ driver = WebSocket::Driver.client(socket)
|
|
139
186
|
After this you use the driver API as described below to process incoming data
|
140
187
|
and send outgoing data.
|
141
188
|
|
189
|
+
Client drivers have two additional methods for reading the HTTP data that was
|
190
|
+
sent back by the server:
|
191
|
+
|
192
|
+
* `driver.status` - the integer value of the HTTP status code
|
193
|
+
* `driver.headers` - a hash-like object containing the response headers
|
194
|
+
|
142
195
|
|
143
196
|
### Driver API
|
144
197
|
|
@@ -146,12 +199,15 @@ Drivers are created using one of the following methods:
|
|
146
199
|
|
147
200
|
```ruby
|
148
201
|
driver = WebSocket::Driver.rack(socket, options)
|
202
|
+
driver = WebSocket::Driver.server(socket, options)
|
149
203
|
driver = WebSocket::Driver.client(socket, options)
|
150
204
|
```
|
151
205
|
|
152
206
|
The `rack` method returns a driver chosen using the socket's `env`. The
|
153
|
-
`
|
154
|
-
|
207
|
+
`server` method returns a driver that will parse an HTTP request and then
|
208
|
+
decide which driver to use for it using the `rack` method. The `client` method
|
209
|
+
always returns a driver for the RFC version of the protocol with masking
|
210
|
+
enabled on outgoing frames.
|
155
211
|
|
156
212
|
The `options` argument is optional, and is a hash. It may contain the following
|
157
213
|
keys:
|
@@ -188,6 +244,12 @@ describing the error.
|
|
188
244
|
Sets the callback block to execute when the socket becomes closed. The `event`
|
189
245
|
object has `code` and `reason` attributes.
|
190
246
|
|
247
|
+
#### `driver.set_header(name, value)`
|
248
|
+
|
249
|
+
Sets a custom header to be sent as part of the handshake response, either from
|
250
|
+
the server or from the client. Must be called before `start`, since this is
|
251
|
+
when the headers are serialized and sent.
|
252
|
+
|
191
253
|
#### `driver.start`
|
192
254
|
|
193
255
|
Initiates the protocol by sending the handshake - either the response for a
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'websocket/driver'
|
5
|
+
|
6
|
+
module Connection
|
7
|
+
def initialize
|
8
|
+
@driver = WebSocket::Driver.server(self)
|
9
|
+
|
10
|
+
@driver.on(:connect) { |e| @driver.start if WebSocket::Driver.websocket? @driver.env }
|
11
|
+
@driver.on(:message) { |e| @driver.frame(e.data) }
|
12
|
+
@driver.on(:close) { |e| close_connection_after_writing }
|
13
|
+
end
|
14
|
+
|
15
|
+
def receive_data(data)
|
16
|
+
@driver.parse(data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def write(data)
|
20
|
+
send_data(data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
EM.run {
|
25
|
+
EM.start_server('127.0.0.1', ARGV[0], Connection)
|
26
|
+
}
|
27
|
+
|
data/lib/websocket/driver.rb
CHANGED
@@ -7,11 +7,13 @@
|
|
7
7
|
require 'base64'
|
8
8
|
require 'digest/md5'
|
9
9
|
require 'digest/sha1'
|
10
|
-
require '
|
10
|
+
require 'set'
|
11
11
|
require 'stringio'
|
12
12
|
require 'uri'
|
13
13
|
|
14
14
|
module WebSocket
|
15
|
+
autoload :HTTP, File.expand_path('../http', __FILE__)
|
16
|
+
|
15
17
|
class Driver
|
16
18
|
|
17
19
|
root = File.expand_path('../driver', __FILE__)
|
@@ -35,17 +37,20 @@ module WebSocket
|
|
35
37
|
|
36
38
|
STATES = [:connecting, :open, :closing, :closed]
|
37
39
|
|
38
|
-
class
|
40
|
+
class ConnectEvent < Struct.new(nil) ; end
|
41
|
+
class OpenEvent < Struct.new(nil) ; end
|
39
42
|
class MessageEvent < Struct.new(:data) ; end
|
40
|
-
class CloseEvent
|
43
|
+
class CloseEvent < Struct.new(:code, :reason) ; end
|
41
44
|
|
42
45
|
class ProtocolError < StandardError ; end
|
43
46
|
|
44
|
-
autoload :
|
47
|
+
autoload :Client, root + '/client'
|
45
48
|
autoload :Draft75, root + '/draft75'
|
46
49
|
autoload :Draft76, root + '/draft76'
|
50
|
+
autoload :EventEmitter, root + '/event_emitter'
|
51
|
+
autoload :Headers, root + '/headers'
|
47
52
|
autoload :Hybi, root + '/hybi'
|
48
|
-
autoload :
|
53
|
+
autoload :Server, root + '/server'
|
49
54
|
|
50
55
|
include EventEmitter
|
51
56
|
attr_reader :protocol, :ready_state
|
@@ -55,6 +60,7 @@ module WebSocket
|
|
55
60
|
|
56
61
|
@socket = socket
|
57
62
|
@options = options
|
63
|
+
@headers = Headers.new
|
58
64
|
@queue = []
|
59
65
|
@ready_state = 0
|
60
66
|
end
|
@@ -64,6 +70,12 @@ module WebSocket
|
|
64
70
|
STATES[@ready_state]
|
65
71
|
end
|
66
72
|
|
73
|
+
def set_header(name, value)
|
74
|
+
return false unless @ready_state <= 0
|
75
|
+
@headers[name] = value
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
67
79
|
def start
|
68
80
|
return false unless @ready_state == 0
|
69
81
|
@socket.write(handshake_response)
|
@@ -116,6 +128,10 @@ module WebSocket
|
|
116
128
|
Client.new(socket, options.merge(:masking => true))
|
117
129
|
end
|
118
130
|
|
131
|
+
def self.server(socket, options = {})
|
132
|
+
Server.new(socket, options.merge(:require_masking => true))
|
133
|
+
end
|
134
|
+
|
119
135
|
def self.rack(socket, options = {})
|
120
136
|
env = socket.env
|
121
137
|
if env['HTTP_SEC_WEBSOCKET_VERSION']
|
@@ -6,12 +6,15 @@ module WebSocket
|
|
6
6
|
Base64.encode64((1..16).map { rand(255).chr } * '').strip
|
7
7
|
end
|
8
8
|
|
9
|
+
attr_reader :status, :headers
|
10
|
+
|
9
11
|
def initialize(socket, options = {})
|
10
12
|
super
|
11
13
|
|
12
14
|
@ready_state = -1
|
13
15
|
@key = Client.generate_key
|
14
16
|
@accept = Hybi.generate_accept(@key)
|
17
|
+
@http = HTTP::Response.new
|
15
18
|
end
|
16
19
|
|
17
20
|
def version
|
@@ -27,17 +30,10 @@ module WebSocket
|
|
27
30
|
|
28
31
|
def parse(buffer)
|
29
32
|
return super if @ready_state > 0
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@buffer << data
|
35
|
-
validate_handshake if @buffer[-4..-1] == [0x0D, 0x0A, 0x0D, 0x0A]
|
36
|
-
when 1 then
|
37
|
-
message << data
|
38
|
-
end
|
39
|
-
end
|
40
|
-
parse(message) if @ready_state == 1
|
33
|
+
@http.parse(buffer)
|
34
|
+
return fail_handshake('Invalid HTTP response') if @http.error?
|
35
|
+
validate_handshake if @http.complete?
|
36
|
+
parse(@http.body) if @ready_state == 1
|
41
37
|
end
|
42
38
|
|
43
39
|
private
|
@@ -60,7 +56,7 @@ module WebSocket
|
|
60
56
|
headers << "Sec-WebSocket-Protocol: #{@protocols * ', '}"
|
61
57
|
end
|
62
58
|
|
63
|
-
(headers + [
|
59
|
+
(headers + [@headers.to_s, '']).join("\r\n")
|
64
60
|
end
|
65
61
|
|
66
62
|
def fail_handshake(message)
|
@@ -71,18 +67,17 @@ module WebSocket
|
|
71
67
|
end
|
72
68
|
|
73
69
|
def validate_handshake
|
74
|
-
|
75
|
-
@
|
76
|
-
response = Net::HTTPResponse.read_new(Net::BufferedIO.new(StringIO.new(data)))
|
70
|
+
@status = @http.code
|
71
|
+
@headers = Headers.new(@http.headers)
|
77
72
|
|
78
|
-
unless
|
79
|
-
return fail_handshake("Unexpected response code: #{
|
73
|
+
unless @http.code == 101
|
74
|
+
return fail_handshake("Unexpected response code: #{@http.code}")
|
80
75
|
end
|
81
76
|
|
82
|
-
upgrade =
|
83
|
-
connection =
|
84
|
-
accept =
|
85
|
-
protocol =
|
77
|
+
upgrade = @http['Upgrade'] || ''
|
78
|
+
connection = @http['Connection'] || ''
|
79
|
+
accept = @http['Sec-WebSocket-Accept'] || ''
|
80
|
+
protocol = @http['Sec-WebSocket-Protocol'] || ''
|
86
81
|
|
87
82
|
if upgrade == ''
|
88
83
|
return fail_handshake("'Upgrade' header is missing")
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
|
4
|
+
class Headers
|
5
|
+
ALLOWED_DUPLICATES = %w[set-cookie set-cookie2 warning www-authenticate]
|
6
|
+
|
7
|
+
def initialize(received = {})
|
8
|
+
@raw = received
|
9
|
+
@sent = Set.new
|
10
|
+
@lines = []
|
11
|
+
|
12
|
+
@received = {}
|
13
|
+
@raw.each { |k,v| @received[HTTP.normalize_header(k)] = v }
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](name)
|
17
|
+
@received[HTTP.normalize_header(name)]
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(name, value)
|
21
|
+
return if value.nil?
|
22
|
+
key = HTTP.normalize_header(name)
|
23
|
+
return unless @sent.add?(key) or ALLOWED_DUPLICATES.include?(key)
|
24
|
+
@lines << "#{name.strip}: #{value.to_s.strip}\r\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
@raw.inspect
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_h
|
32
|
+
@raw.dup
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@lines.join('')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module WebSocket
|
2
|
+
class Driver
|
3
|
+
|
4
|
+
class Server < Driver
|
5
|
+
EVENTS = %w[open message error close]
|
6
|
+
|
7
|
+
def initialize(socket, options = {})
|
8
|
+
super
|
9
|
+
@http = HTTP::Request.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def env
|
13
|
+
@http.complete? ? @http.env : nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def url
|
17
|
+
return nil unless e = env
|
18
|
+
|
19
|
+
url = "ws://#{e['HTTP_HOST']}"
|
20
|
+
url << e['PATH_INFO']
|
21
|
+
url << "?#{e['QUERY_STRING']}" unless e['QUERY_STRING'] == ''
|
22
|
+
url
|
23
|
+
end
|
24
|
+
|
25
|
+
%w[set_header start state frame text binary ping close].each do |method|
|
26
|
+
define_method(method) do |*args|
|
27
|
+
if @delegate
|
28
|
+
@delegate.__send__(method, *args)
|
29
|
+
else
|
30
|
+
@queue << [method, args]
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse(buffer)
|
37
|
+
return @delegate.parse(buffer) if @delegate
|
38
|
+
|
39
|
+
@http.parse(buffer)
|
40
|
+
return fail_request('Invalid HTTP request') if @http.error?
|
41
|
+
return unless @http.complete?
|
42
|
+
|
43
|
+
@delegate = Driver.rack(self, @options)
|
44
|
+
@delegate.on(:open) { open }
|
45
|
+
EVENTS.each do |event|
|
46
|
+
@delegate.on(event) { |e| emit(event, e) }
|
47
|
+
end
|
48
|
+
|
49
|
+
emit(:connect, ConnectEvent.new)
|
50
|
+
end
|
51
|
+
|
52
|
+
def write(data)
|
53
|
+
@socket.write(data)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def fail_request
|
59
|
+
emit(:error, ProtocolError.new(message))
|
60
|
+
emit(:close, CloseEvent.new(Hybi::ERRORS[:protocol_error], message))
|
61
|
+
end
|
62
|
+
|
63
|
+
def open
|
64
|
+
@queue.each do |message|
|
65
|
+
@delegate.__send__(message[0], *message[1])
|
66
|
+
end
|
67
|
+
@queue = []
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,16 @@
|
|
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
|
16
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module HTTP
|
3
|
+
|
4
|
+
module Headers
|
5
|
+
MAX_LINE_LENGTH = 4096
|
6
|
+
CR = 0x0D
|
7
|
+
LF = 0x0A
|
8
|
+
|
9
|
+
HEADER_LINE = /^([!#\$%&'\*\+\-\.\^_`\|~0-9a-z]+):\s*((?:\t|[\x20-\x7e])*?)\s*$/i
|
10
|
+
|
11
|
+
attr_reader :headers
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@buffer = []
|
15
|
+
@headers = {}
|
16
|
+
@stage = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def complete?
|
20
|
+
@stage == 2
|
21
|
+
end
|
22
|
+
|
23
|
+
def error?
|
24
|
+
@stage == -1
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse(data)
|
28
|
+
data.each_byte do |byte|
|
29
|
+
if byte == LF and @stage < 2
|
30
|
+
@buffer.pop if @buffer.last == CR
|
31
|
+
if @buffer.empty?
|
32
|
+
complete if @stage == 1
|
33
|
+
else
|
34
|
+
result = case @stage
|
35
|
+
when 0 then start_line(string_buffer)
|
36
|
+
when 1 then header_line(string_buffer)
|
37
|
+
end
|
38
|
+
|
39
|
+
if result
|
40
|
+
@stage = 1
|
41
|
+
else
|
42
|
+
error
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@buffer = []
|
46
|
+
else
|
47
|
+
@buffer << byte if @stage >= 0
|
48
|
+
error if @stage < 2 and @buffer.size > MAX_LINE_LENGTH
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@env['rack.input'] = StringIO.new(string_buffer) if @env
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def complete
|
57
|
+
@stage = 2
|
58
|
+
end
|
59
|
+
|
60
|
+
def error
|
61
|
+
@stage = -1
|
62
|
+
end
|
63
|
+
|
64
|
+
def header_line(line)
|
65
|
+
return false unless parsed = line.scan(HEADER_LINE).first
|
66
|
+
@headers[HTTP.normalize_header(parsed[0])] = parsed[1].strip
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
def string_buffer
|
71
|
+
@buffer.pack('C*')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module WebSocket
|
2
|
+
module HTTP
|
3
|
+
|
4
|
+
class Request
|
5
|
+
include Headers
|
6
|
+
|
7
|
+
REQUEST_LINE = /^([A-Z]+) +([\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
|
46
|
+
|
@@ -0,0 +1,30 @@
|
|
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}) +(.*)$/
|
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
|
30
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: websocket-driver
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: rake-compiler
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,14 +72,21 @@ files:
|
|
56
72
|
- ext/websocket_mask/websocket_mask.c
|
57
73
|
- ext/websocket_mask/WebsocketMaskService.java
|
58
74
|
- ext/websocket_mask/extconf.rb
|
75
|
+
- examples/tcp_server.rb
|
76
|
+
- lib/websocket/http/request.rb
|
77
|
+
- lib/websocket/http/headers.rb
|
78
|
+
- lib/websocket/http/response.rb
|
79
|
+
- lib/websocket/driver.rb
|
80
|
+
- lib/websocket/http.rb
|
81
|
+
- lib/websocket/driver/hybi/stream_reader.rb
|
82
|
+
- lib/websocket/driver/utf8_match.rb
|
83
|
+
- lib/websocket/driver/headers.rb
|
84
|
+
- lib/websocket/driver/hybi.rb
|
85
|
+
- lib/websocket/driver/server.rb
|
59
86
|
- lib/websocket/driver/client.rb
|
60
87
|
- lib/websocket/driver/draft75.rb
|
61
|
-
- lib/websocket/driver/draft76.rb
|
62
88
|
- lib/websocket/driver/event_emitter.rb
|
63
|
-
- lib/websocket/driver/
|
64
|
-
- lib/websocket/driver/hybi.rb
|
65
|
-
- lib/websocket/driver/utf8_match.rb
|
66
|
-
- lib/websocket/driver.rb
|
89
|
+
- lib/websocket/driver/draft76.rb
|
67
90
|
homepage: http://github.com/faye/websocket-driver-ruby
|
68
91
|
licenses: []
|
69
92
|
post_install_message:
|