faye-websocket 0.4.7 → 0.5.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.

Potentially problematic release.


This version of faye-websocket might be problematic. Click here for more details.

Files changed (38) hide show
  1. data/CHANGELOG.md +81 -0
  2. data/README.md +408 -0
  3. data/examples/app.rb +4 -1
  4. data/examples/autobahn_client.rb +8 -6
  5. data/examples/client.rb +2 -1
  6. data/examples/config.ru +6 -9
  7. data/{spec → examples}/rainbows.conf +0 -0
  8. data/examples/server.rb +10 -1
  9. data/lib/faye/adapters/rainbows.rb +15 -16
  10. data/lib/faye/adapters/rainbows_client.rb +15 -16
  11. data/lib/faye/adapters/thin.rb +15 -16
  12. data/lib/faye/eventsource.rb +38 -46
  13. data/lib/faye/rack_stream.rb +70 -0
  14. data/lib/faye/websocket.rb +39 -162
  15. data/lib/faye/websocket/api.rb +70 -60
  16. data/lib/faye/websocket/api/event.rb +1 -1
  17. data/lib/faye/websocket/api/event_target.rb +35 -12
  18. data/lib/faye/websocket/client.rb +5 -38
  19. metadata +62 -45
  20. data/CHANGELOG.txt +0 -74
  21. data/README.rdoc +0 -366
  22. data/ext/faye_websocket_mask/FayeWebsocketMaskService.java +0 -61
  23. data/ext/faye_websocket_mask/extconf.rb +0 -5
  24. data/ext/faye_websocket_mask/faye_websocket_mask.c +0 -33
  25. data/lib/faye/websocket/draft75_parser.rb +0 -87
  26. data/lib/faye/websocket/draft76_parser.rb +0 -84
  27. data/lib/faye/websocket/hybi_parser.rb +0 -321
  28. data/lib/faye/websocket/hybi_parser/handshake.rb +0 -78
  29. data/lib/faye/websocket/hybi_parser/stream_reader.rb +0 -29
  30. data/lib/faye/websocket/utf8_match.rb +0 -8
  31. data/spec/faye/websocket/client_spec.rb +0 -162
  32. data/spec/faye/websocket/draft75_parser_examples.rb +0 -48
  33. data/spec/faye/websocket/draft75_parser_spec.rb +0 -27
  34. data/spec/faye/websocket/draft76_parser_spec.rb +0 -34
  35. data/spec/faye/websocket/hybi_parser_spec.rb +0 -149
  36. data/spec/server.crt +0 -15
  37. data/spec/server.key +0 -15
  38. data/spec/spec_helper.rb +0 -68
@@ -1,216 +1,93 @@
1
- # API and protocol references:
1
+ # API references:
2
2
  #
3
3
  # * http://dev.w3.org/html5/websockets/
4
4
  # * http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-eventtarget
5
5
  # * http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-event
6
- # * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
7
- # * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
8
- # * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
9
6
 
10
- require 'base64'
11
- require 'digest/md5'
12
- require 'digest/sha1'
13
7
  require 'forwardable'
14
- require 'net/http'
15
8
  require 'stringio'
16
9
  require 'uri'
17
10
  require 'eventmachine'
11
+ require 'websocket/driver'
18
12
 
19
13
  module Faye
20
14
  autoload :EventSource, File.expand_path('../eventsource', __FILE__)
15
+ autoload :RackStream, File.expand_path('../rack_stream', __FILE__)
21
16
 
22
17
  class WebSocket
23
18
  root = File.expand_path('../websocket', __FILE__)
24
- require root + '/../../faye_websocket_mask'
25
19
 
26
- def self.jruby?
27
- defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
28
- end
29
-
30
- def self.rbx?
31
- defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
32
- end
33
-
34
- if jruby?
35
- require 'jruby'
36
- com.jcoglan.faye.FayeWebsocketMaskService.new.basicLoad(JRuby.runtime)
37
- end
38
-
39
- unless WebSocketMask.respond_to?(:mask)
40
- def WebSocketMask.mask(payload, mask)
41
- @instance ||= new
42
- @instance.mask(payload, mask)
43
- end
44
- end
45
-
46
- unless String.instance_methods.include?(:force_encoding)
47
- require root + '/utf8_match'
48
- end
49
-
50
- autoload :Adapter, root + '/adapter'
51
- autoload :API, root + '/api'
52
- autoload :Client, root + '/client'
53
- autoload :Draft75Parser, root + '/draft75_parser'
54
- autoload :Draft76Parser, root + '/draft76_parser'
55
- autoload :HybiParser, root + '/hybi_parser'
20
+ autoload :Adapter, root + '/adapter'
21
+ autoload :API, root + '/api'
22
+ autoload :Client, root + '/client'
56
23
 
57
24
  ADAPTERS = {
58
- 'thin' => :Thin,
25
+ 'goliath' => :Goliath,
59
26
  'rainbows' => :Rainbows,
60
- 'goliath' => :Goliath
27
+ 'thin' => :Thin
61
28
  }
62
29
 
63
- def self.load_adapter(backend)
64
- const = Kernel.const_get(ADAPTERS[backend]) rescue nil
65
- require(backend) unless const
66
- require File.expand_path("../adapters/#{backend}", __FILE__)
67
- end
68
-
69
- def self.utf8_string(string)
70
- string = string.pack('C*') if Array === string
71
- string.respond_to?(:force_encoding) ?
72
- string.force_encoding('UTF-8') :
73
- string
30
+ def self.determine_url(env)
31
+ secure = Rack::Request.new(env).ssl?
32
+ scheme = secure ? 'wss:' : 'ws:'
33
+ "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
74
34
  end
75
35
 
76
- def self.encode(string, validate_encoding = false)
77
- if Array === string
78
- string = utf8_string(string)
79
- return nil if validate_encoding and !valid_utf8?(string)
80
- end
81
- utf8_string(string)
36
+ def self.ensure_reactor_running
37
+ Thread.new { EventMachine.run } unless EventMachine.reactor_running?
38
+ Thread.pass until EventMachine.reactor_running?
82
39
  end
83
40
 
84
- def self.valid_utf8?(byte_array)
85
- string = utf8_string(byte_array)
86
- if defined?(UTF8_MATCH)
87
- UTF8_MATCH =~ string ? true : false
88
- else
89
- string.valid_encoding?
90
- end
41
+ def self.load_adapter(backend)
42
+ const = Kernel.const_get(ADAPTERS[backend]) rescue nil
43
+ require(backend) unless const
44
+ path = File.expand_path("../adapters/#{backend}.rb", __FILE__)
45
+ require(path) if File.file?(path)
91
46
  end
92
47
 
93
48
  def self.websocket?(env)
94
- env['REQUEST_METHOD'] == 'GET' and
95
- env['HTTP_CONNECTION'] and
96
- env['HTTP_CONNECTION'].split(/\s*,\s*/).include?('Upgrade') and
97
- env['HTTP_UPGRADE'].downcase == 'websocket'
98
- end
99
-
100
- def self.parser(env)
101
- if env['HTTP_SEC_WEBSOCKET_VERSION']
102
- HybiParser
103
- elsif env['HTTP_SEC_WEBSOCKET_KEY1']
104
- Draft76Parser
105
- else
106
- Draft75Parser
107
- end
108
- end
109
-
110
- def self.determine_url(env)
111
- secure = if env.has_key?('HTTP_X_FORWARDED_PROTO')
112
- env['HTTP_X_FORWARDED_PROTO'] == 'https'
113
- else
114
- env['HTTP_ORIGIN'] =~ /^https:/i
115
- end
116
-
117
- scheme = secure ? 'wss:' : 'ws:'
118
- "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
49
+ ::WebSocket::Driver.websocket?(env)
119
50
  end
120
51
 
121
- extend Forwardable
122
- def_delegators :@parser, :version
123
-
124
52
  attr_reader :env
125
53
  include API
126
54
 
127
- def initialize(env, supported_protos = nil, options = {})
55
+ def initialize(env, protocols = nil, options = {})
56
+ WebSocket.ensure_reactor_running
57
+
128
58
  @env = env
59
+ @url = WebSocket.determine_url(@env)
60
+ @driver = ::WebSocket::Driver.rack(self, :protocols => protocols)
129
61
  @stream = Stream.new(self)
130
62
  @ping = options[:ping]
131
63
  @ping_id = 0
132
64
 
133
- @url = WebSocket.determine_url(@env)
134
- @ready_state = CONNECTING
135
- @buffered_amount = 0
136
-
137
- @parser = WebSocket.parser(@env).new(self, :protocols => supported_protos)
138
-
139
- @send_buffer = []
140
- EventMachine.next_tick { open }
141
-
142
- @callback = @env['async.callback']
143
- @callback.call([101, {}, @stream])
144
- @stream.write(@parser.handshake_response)
145
-
146
- @ready_state = OPEN if @parser.open?
147
-
148
- if @ping
149
- @ping_timer = EventMachine.add_periodic_timer(@ping) do
150
- @ping_id += 1
151
- ping(@ping_id.to_s)
152
- end
65
+ if callback = @env['async.callback']
66
+ callback.call([101, {}, @stream])
153
67
  end
154
- end
155
68
 
156
- def ping(message = '', &callback)
157
- return false unless @parser.respond_to?(:ping)
158
- @parser.ping(message, &callback)
69
+ super()
70
+ @driver.start
159
71
  end
160
72
 
161
- def protocol
162
- @parser.protocol || ''
73
+ def write(data)
74
+ @stream.write(data)
163
75
  end
164
76
 
165
77
  def rack_response
166
78
  [ -1, {}, [] ]
167
79
  end
168
80
 
169
- private
170
-
171
- def parse(data)
172
- response = @parser.parse(data)
173
- return unless response
174
- @stream.write(response)
175
- open
176
- end
177
- end
178
-
179
- class WebSocket::Stream
180
- include EventMachine::Deferrable
181
-
182
- extend Forwardable
183
- def_delegators :@connection, :close_connection, :close_connection_after_writing
184
-
185
- def initialize(web_socket)
186
- @web_socket = web_socket
187
- @connection = web_socket.env['em.connection']
188
- @stream_send = web_socket.env['stream.send']
189
-
190
- @connection.socket_stream = self if @connection.respond_to?(:socket_stream)
191
- end
192
-
193
- def each(&callback)
194
- @stream_send ||= callback
195
- end
196
-
197
- def fail
198
- @web_socket.close(1006, '', false)
199
- end
81
+ class Stream < RackStream
82
+ def fail
83
+ @socket_object.__send__(:finalize, '', 1006)
84
+ end
200
85
 
201
- def receive(data)
202
- @web_socket.__send__(:parse, data)
86
+ def receive(data)
87
+ @socket_object.__send__(:parse, data)
88
+ end
203
89
  end
204
90
 
205
- def write(data)
206
- return unless @stream_send
207
- @stream_send.call(data) rescue nil
208
- end
209
91
  end
210
92
  end
211
93
 
212
- Faye::WebSocket::ADAPTERS.each do |name, const|
213
- klass = Kernel.const_get(const) rescue nil
214
- Faye::WebSocket.load_adapter(name) if klass
215
- end
216
-
@@ -1,96 +1,106 @@
1
+ require File.expand_path('../api/event_target', __FILE__)
2
+ require File.expand_path('../api/event', __FILE__)
3
+
1
4
  module Faye
2
5
  class WebSocket
3
6
 
4
7
  module API
5
- module ReadyStates
6
- CONNECTING = 0
7
- OPEN = 1
8
- CLOSING = 2
9
- CLOSED = 3
10
- end
8
+ CONNECTING = 0
9
+ OPEN = 1
10
+ CLOSING = 2
11
+ CLOSED = 3
11
12
 
12
- class IllegalStateError < StandardError
13
- end
14
-
15
- require File.expand_path('../api/event_target', __FILE__)
16
- require File.expand_path('../api/event', __FILE__)
17
13
  include EventTarget
18
- include ReadyStates
14
+
15
+ extend Forwardable
16
+ def_delegators :@driver, :version
19
17
 
20
18
  attr_reader :url, :ready_state, :buffered_amount
21
19
 
22
- private
20
+ def initialize
21
+ super
23
22
 
24
- def open
25
- return if @parser and not @parser.open?
26
- @ready_state = OPEN
23
+ @ready_state = CONNECTING
24
+ @buffered_amount = 0
25
+
26
+ @driver.on(:open) { |e| open }
27
+ @driver.on(:message) { |e| receive_message(e.data) }
28
+ @driver.on(:close) { |e| finalize(e.reason, e.code) }
27
29
 
28
- buffer = @send_buffer || []
29
- while message = buffer.shift
30
- send(*message)
30
+ @driver.on(:error) do |error|
31
+ event = Event.new('error')
32
+ event.init_event('error', false, false)
33
+ dispatch_event(event)
34
+ end
35
+
36
+ if @ping
37
+ @ping_timer = EventMachine.add_periodic_timer(@ping) do
38
+ @ping_id += 1
39
+ ping(@ping_id.to_s)
40
+ end
31
41
  end
42
+ end
43
+
44
+ private
32
45
 
46
+ def open
47
+ return unless @ready_state == CONNECTING
48
+ @ready_state = OPEN
33
49
  event = Event.new('open')
34
50
  event.init_event('open', false, false)
35
51
  dispatch_event(event)
36
52
  end
37
53
 
38
- public
39
-
40
- def receive(data)
41
- return false unless @ready_state == OPEN
54
+ def receive_message(data)
55
+ return unless @ready_state == OPEN
42
56
  event = Event.new('message')
43
57
  event.init_event('message', false, false)
44
58
  event.data = data
45
59
  dispatch_event(event)
46
60
  end
47
61
 
48
- def send(data, type = nil, error_type = nil)
49
- if @ready_state == CONNECTING
50
- if @send_buffer
51
- @send_buffer << [data, type, error_type]
52
- return true
53
- else
54
- raise IllegalStateError, 'Cannot call send(), socket is not open yet'
55
- end
56
- end
62
+ def finalize(reason = nil, code = nil)
63
+ return if @ready_state == CLOSED
64
+ @ready_state = CLOSED
65
+ EventMachine.cancel_timer(@ping_timer) if @ping_timer
66
+ @stream.close_connection_after_writing
67
+ event = Event.new('close', :code => code || 1000, :reason => reason || '')
68
+ event.init_event('close', false, false)
69
+ dispatch_event(event)
70
+ end
57
71
 
58
- return false if @ready_state == CLOSED
72
+ def parse(data)
73
+ @driver.parse(data)
74
+ end
59
75
 
60
- data = data.to_s unless Array === data
76
+ public
61
77
 
62
- data = WebSocket.encode(data) if String === data
63
- frame = @parser.frame(data, type, error_type)
64
- @stream.write(frame) if frame
78
+ def write(data)
79
+ @stream.write(data)
65
80
  end
66
81
 
67
- def close(code = nil, reason = nil, ack = true)
68
- return if @ready_state == CLOSED
69
- return if @ready_state == CLOSING && ack
70
-
71
- finalize = lambda do
72
- @ready_state = CLOSED
73
- EventMachine.cancel_timer(@ping_timer) if @ping_timer
74
- @stream.close_connection_after_writing
75
- event = Event.new('close', :code => code || 1000, :reason => reason || '')
76
- event.init_event('close', false, false)
77
- dispatch_event(event)
82
+ def send(message)
83
+ return false if @ready_state > OPEN
84
+ case message
85
+ when Numeric then @driver.text(message.to_s)
86
+ when String then @driver.text(message)
87
+ when Array then @driver.binary(message)
88
+ else false
78
89
  end
90
+ end
79
91
 
80
- return finalize.call if @ready_state == CONNECTING
92
+ def ping(message = '', &callback)
93
+ return false if @ready_state > OPEN
94
+ @driver.ping(message, &callback)
95
+ end
81
96
 
82
- @ready_state = CLOSING
97
+ def close
98
+ @ready_state = CLOSING if @ready_state == OPEN
99
+ @driver.close
100
+ end
83
101
 
84
- if ack
85
- if @parser.respond_to?(:close)
86
- @parser.close(code, reason, &finalize)
87
- else
88
- finalize.call
89
- end
90
- else
91
- @parser.close(code, reason) if @parser.respond_to?(:close)
92
- finalize.call
93
- end
102
+ def protocol
103
+ @driver.protocol || ''
94
104
  end
95
105
  end
96
106
 
@@ -2,7 +2,7 @@ module Faye::WebSocket::API
2
2
  class Event
3
3
 
4
4
  attr_reader :type, :bubbles, :cancelable
5
- attr_accessor :target, :current_target, :event_phase, :data
5
+ attr_accessor :target, :current_target, :event_phase, :data, :message
6
6
 
7
7
  CAPTURING_PHASE = 1
8
8
  AT_TARGET = 2
@@ -1,31 +1,54 @@
1
1
  module Faye::WebSocket::API
2
2
  module EventTarget
3
3
 
4
- attr_accessor :onopen, :onmessage, :onerror, :onclose
4
+ include ::WebSocket::Driver::EventEmitter
5
+ events = %w[open message error close]
6
+
7
+ events.each do |event_type|
8
+ define_method "on#{event_type}=" do |handler|
9
+ EventMachine.next_tick do
10
+ flush(event_type, &handler)
11
+ instance_variable_set("@on#{event_type}", handler)
12
+ end
13
+ end
14
+ end
5
15
 
6
16
  def add_event_listener(event_type, listener, use_capture = false)
7
- @listeners ||= {}
8
- list = @listeners[event_type] ||= []
9
- list << listener
17
+ add_listener(event_type, &listener)
10
18
  end
11
19
 
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
20
+ def add_listener(event_type, &listener)
21
+ EventMachine.next_tick do
22
+ flush(event_type, &listener)
23
+ super(event_type, &listener)
24
+ end
25
+ end
15
26
 
16
- @listeners[event_type].delete_if(&listener.method(:==))
27
+ def remove_event_listener(event_type, listener, use_capture = false)
28
+ remove_listener(event_type, &listener)
17
29
  end
18
30
 
19
31
  def dispatch_event(event)
20
32
  event.target = event.current_target = self
21
33
  event.event_phase = Event::AT_TARGET
22
34
 
23
- callback = __send__("on#{ event.type }")
35
+ callback = instance_variable_get("@on#{ event.type }")
36
+ count = listener_count(event.type)
37
+
38
+ unless callback or count > 0
39
+ @buffers ||= Hash.new { |k,v| k[v] = [] }
40
+ @buffers[event.type].push(event)
41
+ end
42
+
24
43
  callback.call(event) if callback
44
+ emit(event.type, event)
45
+ end
46
+
47
+ private
25
48
 
26
- return unless @listeners and @listeners[event.type]
27
- @listeners[event.type].each do |listener|
28
- listener.call(event)
49
+ def flush(event_type, &callback)
50
+ if buffer = @buffers && @buffers.delete(event_type.to_s)
51
+ buffer.each { |event| callback.call(event) }
29
52
  end
30
53
  end
31
54