websocket-driver 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|