_bushido-faye-websocket 0.4.4

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.
Files changed (40) hide show
  1. data/CHANGELOG.txt +56 -0
  2. data/README.rdoc +366 -0
  3. data/examples/app.rb +50 -0
  4. data/examples/autobahn_client.rb +44 -0
  5. data/examples/client.rb +30 -0
  6. data/examples/config.ru +17 -0
  7. data/examples/haproxy.conf +21 -0
  8. data/examples/server.rb +44 -0
  9. data/examples/sse.html +39 -0
  10. data/examples/ws.html +44 -0
  11. data/ext/faye_websocket_mask/FayeWebsocketMaskService.java +61 -0
  12. data/ext/faye_websocket_mask/extconf.rb +5 -0
  13. data/ext/faye_websocket_mask/faye_websocket_mask.c +33 -0
  14. data/lib/faye/adapters/goliath.rb +47 -0
  15. data/lib/faye/adapters/rainbows.rb +32 -0
  16. data/lib/faye/adapters/rainbows_client.rb +70 -0
  17. data/lib/faye/adapters/thin.rb +62 -0
  18. data/lib/faye/eventsource.rb +124 -0
  19. data/lib/faye/websocket.rb +216 -0
  20. data/lib/faye/websocket/adapter.rb +21 -0
  21. data/lib/faye/websocket/api.rb +96 -0
  22. data/lib/faye/websocket/api/event.rb +33 -0
  23. data/lib/faye/websocket/api/event_target.rb +34 -0
  24. data/lib/faye/websocket/client.rb +84 -0
  25. data/lib/faye/websocket/draft75_parser.rb +87 -0
  26. data/lib/faye/websocket/draft76_parser.rb +84 -0
  27. data/lib/faye/websocket/hybi_parser.rb +320 -0
  28. data/lib/faye/websocket/hybi_parser/handshake.rb +78 -0
  29. data/lib/faye/websocket/hybi_parser/stream_reader.rb +29 -0
  30. data/lib/faye/websocket/utf8_match.rb +8 -0
  31. data/spec/faye/websocket/client_spec.rb +179 -0
  32. data/spec/faye/websocket/draft75_parser_examples.rb +48 -0
  33. data/spec/faye/websocket/draft75_parser_spec.rb +27 -0
  34. data/spec/faye/websocket/draft76_parser_spec.rb +34 -0
  35. data/spec/faye/websocket/hybi_parser_spec.rb +156 -0
  36. data/spec/rainbows.conf +3 -0
  37. data/spec/server.crt +15 -0
  38. data/spec/server.key +15 -0
  39. data/spec/spec_helper.rb +68 -0
  40. metadata +158 -0
@@ -0,0 +1,33 @@
1
+ module Faye::WebSocket::API
2
+ class Event
3
+
4
+ attr_reader :type, :bubbles, :cancelable
5
+ attr_accessor :target, :current_target, :event_phase, :data
6
+
7
+ CAPTURING_PHASE = 1
8
+ AT_TARGET = 2
9
+ BUBBLING_PHASE = 3
10
+
11
+ def initialize(event_type, options = {})
12
+ @type = event_type
13
+ metaclass = (class << self ; self ; end)
14
+ options.each do |key, value|
15
+ metaclass.__send__(:define_method, key) { value }
16
+ end
17
+ end
18
+
19
+ def init_event(event_type, can_bubble, cancelable)
20
+ @type = event_type
21
+ @bubbles = can_bubble
22
+ @cancelable = cancelable
23
+ end
24
+
25
+ def stop_propagation
26
+ end
27
+
28
+ def prevent_default
29
+ end
30
+
31
+ end
32
+ end
33
+
@@ -0,0 +1,34 @@
1
+ module Faye::WebSocket::API
2
+ module EventTarget
3
+
4
+ attr_accessor :onopen, :onmessage, :onerror, :onclose
5
+
6
+ def add_event_listener(event_type, listener, use_capture = false)
7
+ @listeners ||= {}
8
+ list = @listeners[event_type] ||= []
9
+ list << listener
10
+ end
11
+
12
+ def remove_event_listener(event_type, listener, use_capture = false)
13
+ return unless @listeners and @listeners[event_type]
14
+ return @listeners.delete(event_type) unless listener
15
+
16
+ @listeners[event_type].delete_if(&listener.method(:==))
17
+ end
18
+
19
+ def dispatch_event(event)
20
+ event.target = event.current_target = self
21
+ event.event_phase = Event::AT_TARGET
22
+
23
+ callback = __send__("on#{ event.type }")
24
+ callback.call(event) if callback
25
+
26
+ return unless @listeners and @listeners[event.type]
27
+ @listeners[event.type].each do |listener|
28
+ listener.call(event)
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+
@@ -0,0 +1,84 @@
1
+ module Faye
2
+ class WebSocket
3
+
4
+ class Client
5
+ include API
6
+ attr_reader :protocol, :uri
7
+
8
+ def initialize(url, protocols = nil)
9
+ @parser = HybiParser.new(self, :masking => true, :protocols => protocols)
10
+ @url = url
11
+ @uri = URI.parse(url)
12
+
13
+ @protocol = ''
14
+ @ready_state = CONNECTING
15
+ @buffered_amount = 0
16
+
17
+ port = @uri.port || (@uri.scheme == 'wss' ? 443 : 80)
18
+
19
+ EventMachine.connect(@uri.host, port, Connection) do |conn|
20
+ @stream = conn
21
+ conn.parent = self
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def on_connect
28
+ @stream.start_tls if @uri.scheme == 'wss'
29
+ @handshake = @parser.create_handshake
30
+ @message = []
31
+ @stream.write(@handshake.request_data)
32
+ end
33
+
34
+ def receive_data(data)
35
+ data = WebSocket.encode(data)
36
+
37
+ case @ready_state
38
+ when CONNECTING then
39
+ @message += @handshake.parse(data)
40
+ return unless @handshake.complete?
41
+
42
+ if @handshake.valid?
43
+ @protocol = @handshake.protocol || ''
44
+ @ready_state = OPEN
45
+ event = Event.new('open')
46
+ event.init_event('open', false, false)
47
+ dispatch_event(event)
48
+
49
+ receive_data(@message)
50
+ else
51
+ @ready_state = CLOSED
52
+ event = Event.new('close', :code => 1006, :reason => '')
53
+ event.init_event('close', false, false)
54
+ dispatch_event(event)
55
+ end
56
+
57
+ when OPEN, CLOSING then
58
+ @parser.parse(data)
59
+ end
60
+ end
61
+
62
+ module Connection
63
+ attr_accessor :parent
64
+
65
+ def connection_completed
66
+ parent.__send__(:on_connect)
67
+ end
68
+
69
+ def receive_data(data)
70
+ parent.__send__(:receive_data, data)
71
+ end
72
+
73
+ def unbind
74
+ parent.close(1006, '', false)
75
+ end
76
+
77
+ def write(data)
78
+ send_data(data) rescue nil
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,87 @@
1
+ module Faye
2
+ class WebSocket
3
+
4
+ class Draft75Parser
5
+ attr_reader :protocol
6
+
7
+ def initialize(web_socket, options = {})
8
+ @socket = web_socket
9
+ @stage = 0
10
+ end
11
+
12
+ def version
13
+ 'hixie-75'
14
+ end
15
+
16
+ def handshake_response
17
+ upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
18
+ upgrade << "Upgrade: WebSocket\r\n"
19
+ upgrade << "Connection: Upgrade\r\n"
20
+ upgrade << "WebSocket-Origin: #{@socket.env['HTTP_ORIGIN']}\r\n"
21
+ upgrade << "WebSocket-Location: #{@socket.url}\r\n"
22
+ upgrade << "\r\n"
23
+ upgrade
24
+ end
25
+
26
+ def open?
27
+ true
28
+ end
29
+
30
+ def parse(buffer)
31
+ buffer.each_byte do |data|
32
+ case @stage
33
+ when 0 then
34
+ parse_leading_byte(data)
35
+
36
+ when 1 then
37
+ value = (data & 0x7F)
38
+ @length = value + 128 * @length
39
+
40
+ if @closing and @length.zero?
41
+ @socket.close(nil, nil, false)
42
+ elsif (0x80 & data) != 0x80
43
+ if @length.zero?
44
+ @socket.receive('')
45
+ @stage = 0
46
+ else
47
+ @buffer = []
48
+ @stage = 2
49
+ end
50
+ end
51
+
52
+ when 2 then
53
+ if data == 0xFF
54
+ @socket.receive(WebSocket.encode(@buffer))
55
+ @stage = 0
56
+ else
57
+ @buffer << data
58
+ if @length and @buffer.size == @length
59
+ @stage = 0
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ nil
66
+ end
67
+
68
+ def parse_leading_byte(data)
69
+ if (0x80 & data) == 0x80
70
+ @length = 0
71
+ @stage = 1
72
+ else
73
+ @length = nil
74
+ @buffer = []
75
+ @stage = 2
76
+ end
77
+ end
78
+
79
+ def frame(data, type = nil, error_type = nil)
80
+ return WebSocket.encode(data) if Array === data
81
+ ["\x00", data, "\xFF"].map(&WebSocket.method(:encode)) * ''
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+
@@ -0,0 +1,84 @@
1
+ module Faye
2
+ class WebSocket
3
+
4
+ class Draft76Parser < Draft75Parser
5
+ def version
6
+ 'hixie-76'
7
+ end
8
+
9
+ def handshake_response
10
+ env = @socket.env
11
+ signature = handshake_signature(env['rack.input'].read)
12
+
13
+ upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
14
+ upgrade << "Upgrade: WebSocket\r\n"
15
+ upgrade << "Connection: Upgrade\r\n"
16
+ upgrade << "Sec-WebSocket-Origin: #{env['HTTP_ORIGIN']}\r\n"
17
+ upgrade << "Sec-WebSocket-Location: #{@socket.url}\r\n"
18
+ upgrade << "\r\n"
19
+ upgrade << signature if signature
20
+ upgrade
21
+ end
22
+
23
+ def handshake_signature(head)
24
+ return nil if head.empty?
25
+ env = @socket.env
26
+
27
+ key1 = env['HTTP_SEC_WEBSOCKET_KEY1']
28
+ value1 = number_from_key(key1) / spaces_in_key(key1)
29
+
30
+ key2 = env['HTTP_SEC_WEBSOCKET_KEY2']
31
+ value2 = number_from_key(key2) / spaces_in_key(key2)
32
+
33
+ @handshake_complete = true
34
+
35
+ Digest::MD5.digest(big_endian(value1) +
36
+ big_endian(value2) +
37
+ head)
38
+ end
39
+
40
+ def open?
41
+ !!@handshake_complete
42
+ end
43
+
44
+ def parse(data)
45
+ return super if @handshake_complete
46
+ handshake_signature(data)
47
+ end
48
+
49
+ def close(code = nil, reason = nil, &callback)
50
+ return if @closed
51
+ @socket.send([0xFF, 0x00]) if @closing
52
+ @closed = true
53
+ callback.call if callback
54
+ end
55
+
56
+ private
57
+
58
+ def parse_leading_byte(data)
59
+ return super unless data == 0xFF
60
+ @closing = true
61
+ @length = 0
62
+ @stage = 1
63
+ end
64
+
65
+ def number_from_key(key)
66
+ key.scan(/[0-9]/).join('').to_i(10)
67
+ end
68
+
69
+ def spaces_in_key(key)
70
+ key.scan(/ /).size
71
+ end
72
+
73
+ def big_endian(number)
74
+ string = ''
75
+ [24,16,8,0].each do |offset|
76
+ string << (number >> offset & 0xFF).chr
77
+ end
78
+ string
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+
@@ -0,0 +1,320 @@
1
+ module Faye
2
+ class WebSocket
3
+
4
+ class HybiParser
5
+ root = File.expand_path('../hybi_parser', __FILE__)
6
+ autoload :Handshake, root + '/handshake'
7
+ autoload :StreamReader, root + '/stream_reader'
8
+
9
+ BYTE = 0b11111111
10
+ FIN = MASK = 0b10000000
11
+ RSV1 = 0b01000000
12
+ RSV2 = 0b00100000
13
+ RSV3 = 0b00010000
14
+ OPCODE = 0b00001111
15
+ LENGTH = 0b01111111
16
+
17
+ OPCODES = {
18
+ :continuation => 0,
19
+ :text => 1,
20
+ :binary => 2,
21
+ :close => 8,
22
+ :ping => 9,
23
+ :pong => 10
24
+ }
25
+
26
+ FRAGMENTED_OPCODES = OPCODES.values_at(:continuation, :text, :binary)
27
+ OPENING_OPCODES = OPCODES.values_at(:text, :binary)
28
+
29
+ ERRORS = {
30
+ :normal_closure => 1000,
31
+ :going_away => 1001,
32
+ :protocol_error => 1002,
33
+ :unacceptable => 1003,
34
+ :encoding_error => 1007,
35
+ :policy_violation => 1008,
36
+ :too_large => 1009,
37
+ :extension_error => 1010
38
+ }
39
+
40
+ ERROR_CODES = ERRORS.values
41
+
42
+ attr_reader :protocol
43
+
44
+ def initialize(web_socket, options = {})
45
+ reset
46
+ @socket = web_socket
47
+ @reader = StreamReader.new
48
+ @stage = 0
49
+ @masking = options[:masking]
50
+ @protocols = options[:protocols]
51
+ @protocols = @protocols.split(/\s*,\s*/) if String === @protocols
52
+
53
+ @ping_callbacks = {}
54
+ end
55
+
56
+ def version
57
+ "hybi-#{@socket.env['HTTP_SEC_WEBSOCKET_VERSION']}"
58
+ end
59
+
60
+ def handshake_response
61
+ sec_key = @socket.env['HTTP_SEC_WEBSOCKET_KEY']
62
+ return '' unless String === sec_key
63
+
64
+ accept = Base64.encode64(Digest::SHA1.digest(sec_key + Handshake::GUID)).strip
65
+ protos = @socket.env['HTTP_SEC_WEBSOCKET_PROTOCOL']
66
+ supported = @protocols
67
+ proto = nil
68
+
69
+ headers = [
70
+ "HTTP/1.1 101 Switching Protocols",
71
+ "Upgrade: websocket",
72
+ "Connection: Upgrade",
73
+ "Sec-WebSocket-Accept: #{accept}"
74
+ ]
75
+
76
+ if protos and supported
77
+ protos = protos.split(/\s*,\s*/) if String === protos
78
+ proto = protos.find { |p| supported.include?(p) }
79
+ if proto
80
+ @protocol = proto
81
+ headers << "Sec-WebSocket-Protocol: #{proto}"
82
+ end
83
+ end
84
+
85
+ (headers + ['','']).join("\r\n")
86
+ end
87
+
88
+ def create_handshake
89
+ Handshake.new(@socket.uri, @protocols)
90
+ end
91
+
92
+ def open?
93
+ true
94
+ end
95
+
96
+ def parse(data)
97
+ @reader.put(data.bytes.to_a)
98
+ buffer = true
99
+ while buffer
100
+ case @stage
101
+ when 0 then
102
+ buffer = @reader.read(1)
103
+ parse_opcode(buffer[0]) if buffer
104
+
105
+ when 1 then
106
+ buffer = @reader.read(1)
107
+ parse_length(buffer[0]) if buffer
108
+
109
+ when 2 then
110
+ buffer = @reader.read(@length_size)
111
+ parse_extended_length(buffer) if buffer
112
+
113
+ when 3 then
114
+ buffer = @reader.read(4)
115
+ if buffer
116
+ @mask = buffer
117
+ @stage = 4
118
+ end
119
+
120
+ when 4 then
121
+ buffer = @reader.read(@length)
122
+ if buffer
123
+ @payload = buffer
124
+ emit_frame
125
+ @stage = 0
126
+ end
127
+ end
128
+ end
129
+
130
+ nil
131
+ end
132
+
133
+ def frame(data, type = nil, code = nil)
134
+ return nil if @closed
135
+
136
+ is_text = (String === data)
137
+ opcode = OPCODES[type || (is_text ? :text : :binary)]
138
+ buffer = data.respond_to?(:bytes) ? data.bytes.to_a : data
139
+ insert = code ? 2 : 0
140
+ length = buffer.size + insert
141
+ header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10)
142
+ offset = header + (@masking ? 4 : 0)
143
+ masked = @masking ? MASK : 0
144
+ frame = Array.new(offset)
145
+
146
+ frame[0] = FIN | opcode
147
+
148
+ if length <= 125
149
+ frame[1] = masked | length
150
+ elsif length <= 65535
151
+ frame[1] = masked | 126
152
+ frame[2] = (length >> 8) & BYTE
153
+ frame[3] = length & BYTE
154
+ else
155
+ frame[1] = masked | 127
156
+ frame[2] = (length >> 56) & BYTE
157
+ frame[3] = (length >> 48) & BYTE
158
+ frame[4] = (length >> 40) & BYTE
159
+ frame[5] = (length >> 32) & BYTE
160
+ frame[6] = (length >> 24) & BYTE
161
+ frame[7] = (length >> 16) & BYTE
162
+ frame[8] = (length >> 8) & BYTE
163
+ frame[9] = length & BYTE
164
+ end
165
+
166
+ if code
167
+ buffer = [(code >> 8) & BYTE, code & BYTE] + buffer
168
+ end
169
+
170
+ if @masking
171
+ mask = [rand(256), rand(256), rand(256), rand(256)]
172
+ frame[header...offset] = mask
173
+ buffer = WebSocketMask.mask(buffer, mask)
174
+ end
175
+
176
+ frame.concat(buffer)
177
+
178
+ WebSocket.encode(frame)
179
+ end
180
+
181
+ def ping(message = '', &callback)
182
+ @ping_callbacks[message] = callback if callback
183
+ @socket.send(message, :ping)
184
+ end
185
+
186
+ def close(code = nil, reason = nil, &callback)
187
+ return if @closed
188
+ @closing_callback ||= callback
189
+ @socket.send(reason || '', :close, code || ERRORS[:normal_closure])
190
+ @closed = true
191
+ end
192
+
193
+ private
194
+
195
+ def parse_opcode(data)
196
+ if [RSV1, RSV2, RSV3].any? { |rsv| (data & rsv) == rsv }
197
+ return @socket.close(ERRORS[:protocol_error], nil, false)
198
+ end
199
+
200
+ @final = (data & FIN) == FIN
201
+ @opcode = (data & OPCODE)
202
+ @mask = []
203
+ @payload = []
204
+
205
+ unless OPCODES.values.include?(@opcode)
206
+ return @socket.close(ERRORS[:protocol_error], nil, false)
207
+ end
208
+
209
+ unless FRAGMENTED_OPCODES.include?(@opcode) or @final
210
+ return @socket.close(ERRORS[:protocol_error], nil, false)
211
+ end
212
+
213
+ if @mode and OPENING_OPCODES.include?(@opcode)
214
+ return @socket.close(ERRORS[:protocol_error], nil, false)
215
+ end
216
+
217
+ @stage = 1
218
+ end
219
+
220
+ def parse_length(data)
221
+ @masked = (data & MASK) == MASK
222
+ @length = (data & LENGTH)
223
+
224
+ if @length <= 125
225
+ @stage = @masked ? 3 : 4
226
+ else
227
+ @length_size = (@length == 126) ? 2 : 8
228
+ @stage = 2
229
+ end
230
+ end
231
+
232
+ def parse_extended_length(buffer)
233
+ @length = integer(buffer)
234
+ @stage = @masked ? 3 : 4
235
+ end
236
+
237
+ def emit_frame
238
+ payload = @masked ? WebSocketMask.mask(@payload, @mask) : @payload
239
+
240
+ case @opcode
241
+ when OPCODES[:continuation] then
242
+ return @socket.close(ERRORS[:protocol_error], nil, false) unless @mode
243
+ @buffer.concat(payload)
244
+ if @final
245
+ message = @buffer
246
+ message = WebSocket.encode(message, true) if @mode == :text
247
+ reset
248
+ if message
249
+ @socket.receive(message)
250
+ else
251
+ @socket.close(ERRORS[:encoding_error], nil, false)
252
+ end
253
+ end
254
+
255
+ when OPCODES[:text] then
256
+ if @final
257
+ message = WebSocket.encode(payload, true)
258
+ if message
259
+ @socket.receive(message)
260
+ else
261
+ @socket.close(ERRORS[:encoding_error], nil, false)
262
+ end
263
+ else
264
+ @mode = :text
265
+ @buffer.concat(payload)
266
+ end
267
+
268
+ when OPCODES[:binary] then
269
+ if @final
270
+ @socket.receive(payload)
271
+ else
272
+ @mode = :binary
273
+ @buffer.concat(payload)
274
+ end
275
+
276
+ when OPCODES[:close] then
277
+ code = (payload.size >= 2) ? 256 * payload[0] + payload[1] : nil
278
+
279
+ unless (payload.size == 0) or
280
+ (code && code >= 3000 && code < 5000) or
281
+ ERROR_CODES.include?(code)
282
+ code = ERRORS[:protocol_error]
283
+ end
284
+
285
+ if payload.size > 125 or not WebSocket.valid_utf8?(payload[2..-1] || [])
286
+ code = ERRORS[:protocol_error]
287
+ end
288
+
289
+ reason = (payload.size > 2) ? WebSocket.encode(payload[2..-1], true) : nil
290
+ @socket.close(code, reason, false)
291
+ @closing_callback.call if @closing_callback
292
+
293
+ when OPCODES[:ping] then
294
+ return @socket.close(ERRORS[:protocol_error], nil, false) if payload.size > 125
295
+ @socket.send(payload, :pong)
296
+
297
+ when OPCODES[:pong] then
298
+ message = WebSocket.encode(payload, true)
299
+ callback = @ping_callbacks[message]
300
+ @ping_callbacks.delete(message)
301
+ callback.call if callback
302
+ end
303
+ end
304
+
305
+ def reset
306
+ @buffer = []
307
+ @mode = nil
308
+ end
309
+
310
+ def integer(bytes)
311
+ number = 0
312
+ bytes.each_with_index do |data, i|
313
+ number += data << (8 * (bytes.size - 1 - i))
314
+ end
315
+ number
316
+ end
317
+ end
318
+
319
+ end
320
+ end