faye-websocket 0.4.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

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