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.
- data/CHANGELOG.md +81 -0
- data/README.md +408 -0
- data/examples/app.rb +4 -1
- data/examples/autobahn_client.rb +8 -6
- data/examples/client.rb +2 -1
- data/examples/config.ru +6 -9
- data/{spec → examples}/rainbows.conf +0 -0
- data/examples/server.rb +10 -1
- data/lib/faye/adapters/rainbows.rb +15 -16
- data/lib/faye/adapters/rainbows_client.rb +15 -16
- data/lib/faye/adapters/thin.rb +15 -16
- data/lib/faye/eventsource.rb +38 -46
- data/lib/faye/rack_stream.rb +70 -0
- data/lib/faye/websocket.rb +39 -162
- data/lib/faye/websocket/api.rb +70 -60
- data/lib/faye/websocket/api/event.rb +1 -1
- data/lib/faye/websocket/api/event_target.rb +35 -12
- data/lib/faye/websocket/client.rb +5 -38
- metadata +62 -45
- data/CHANGELOG.txt +0 -74
- data/README.rdoc +0 -366
- data/ext/faye_websocket_mask/FayeWebsocketMaskService.java +0 -61
- data/ext/faye_websocket_mask/extconf.rb +0 -5
- data/ext/faye_websocket_mask/faye_websocket_mask.c +0 -33
- data/lib/faye/websocket/draft75_parser.rb +0 -87
- data/lib/faye/websocket/draft76_parser.rb +0 -84
- data/lib/faye/websocket/hybi_parser.rb +0 -321
- data/lib/faye/websocket/hybi_parser/handshake.rb +0 -78
- data/lib/faye/websocket/hybi_parser/stream_reader.rb +0 -29
- data/lib/faye/websocket/utf8_match.rb +0 -8
- data/spec/faye/websocket/client_spec.rb +0 -162
- data/spec/faye/websocket/draft75_parser_examples.rb +0 -48
- data/spec/faye/websocket/draft75_parser_spec.rb +0 -27
- data/spec/faye/websocket/draft76_parser_spec.rb +0 -34
- data/spec/faye/websocket/hybi_parser_spec.rb +0 -149
- data/spec/server.crt +0 -15
- data/spec/server.key +0 -15
- data/spec/spec_helper.rb +0 -68
data/lib/faye/websocket.rb
CHANGED
@@ -1,216 +1,93 @@
|
|
1
|
-
# API
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
'
|
25
|
+
'goliath' => :Goliath,
|
59
26
|
'rainbows' => :Rainbows,
|
60
|
-
'
|
27
|
+
'thin' => :Thin
|
61
28
|
}
|
62
29
|
|
63
|
-
def self.
|
64
|
-
|
65
|
-
|
66
|
-
|
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.
|
77
|
-
|
78
|
-
|
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.
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
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,
|
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
|
-
|
134
|
-
|
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
|
-
|
157
|
-
|
158
|
-
@parser.ping(message, &callback)
|
69
|
+
super()
|
70
|
+
@driver.start
|
159
71
|
end
|
160
72
|
|
161
|
-
def
|
162
|
-
@
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
202
|
-
|
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
|
-
|
data/lib/faye/websocket/api.rb
CHANGED
@@ -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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
14
|
+
|
15
|
+
extend Forwardable
|
16
|
+
def_delegators :@driver, :version
|
19
17
|
|
20
18
|
attr_reader :url, :ready_state, :buffered_amount
|
21
19
|
|
22
|
-
|
20
|
+
def initialize
|
21
|
+
super
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
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
|
49
|
-
if @ready_state ==
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
72
|
+
def parse(data)
|
73
|
+
@driver.parse(data)
|
74
|
+
end
|
59
75
|
|
60
|
-
|
76
|
+
public
|
61
77
|
|
62
|
-
|
63
|
-
|
64
|
-
@stream.write(frame) if frame
|
78
|
+
def write(data)
|
79
|
+
@stream.write(data)
|
65
80
|
end
|
66
81
|
|
67
|
-
def
|
68
|
-
return if @ready_state
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
92
|
+
def ping(message = '', &callback)
|
93
|
+
return false if @ready_state > OPEN
|
94
|
+
@driver.ping(message, &callback)
|
95
|
+
end
|
81
96
|
|
82
|
-
|
97
|
+
def close
|
98
|
+
@ready_state = CLOSING if @ready_state == OPEN
|
99
|
+
@driver.close
|
100
|
+
end
|
83
101
|
|
84
|
-
|
85
|
-
|
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
|
|
@@ -1,31 +1,54 @@
|
|
1
1
|
module Faye::WebSocket::API
|
2
2
|
module EventTarget
|
3
3
|
|
4
|
-
|
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
|
-
|
8
|
-
list = @listeners[event_type] ||= []
|
9
|
-
list << listener
|
17
|
+
add_listener(event_type, &listener)
|
10
18
|
end
|
11
19
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
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
|
-
|
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 =
|
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
|
-
|
27
|
-
@
|
28
|
-
|
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
|
|