iodine 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +41 -1
- data/lib/iodine/core.rb +25 -3
- data/lib/iodine/http.rb +20 -8
- data/lib/iodine/http/http1.rb +1 -6
- data/lib/iodine/http/http2.rb +5 -3
- data/lib/iodine/http/rack_support.rb +2 -2
- data/lib/iodine/http/request.rb +1 -1
- data/lib/iodine/http/response.rb +11 -9
- data/lib/iodine/http/session.rb +69 -69
- data/lib/iodine/http/websocket_client.rb +190 -191
- data/lib/iodine/http/websocket_handler.rb +48 -14
- data/lib/iodine/http/websockets.rb +23 -4
- data/lib/iodine/io.rb +12 -27
- data/lib/iodine/logging.rb +7 -1
- data/lib/iodine/protocol.rb +4 -1
- data/lib/iodine/timers.rb +4 -3
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +1 -1
- data/manual tests/hello_world +17 -20
- metadata +2 -2
@@ -7,216 +7,215 @@ module Iodine
|
|
7
7
|
#
|
8
8
|
# Use {Iodine::Http::WebsocketClient.connect} to initialize a client with all the callbacks needed.
|
9
9
|
class WebsocketClient
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
10
|
+
|
11
|
+
attr_accessor :response, :request
|
12
|
+
|
13
|
+
def initialize request
|
14
|
+
@response = nil
|
15
|
+
@request = request
|
16
|
+
params = request[:ws_client_params]
|
17
|
+
@on_message = params[:on_message]
|
18
|
+
raise "Websocket client must have an #on_message Proc or handler." unless @on_message && @on_message.respond_to?(:call)
|
19
|
+
@on_open = params[:on_open]
|
20
|
+
@on_close = params[:on_close]
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
23
|
+
def on event_name, &block
|
24
|
+
return false unless block
|
25
|
+
case event_name
|
26
|
+
when :message
|
27
|
+
@on_message = block
|
28
|
+
when :close
|
29
|
+
@on_close = block
|
30
|
+
when :open
|
31
|
+
raise 'The on_open even is invalid at this point.'
|
32
|
+
end
|
33
|
+
|
31
34
|
end
|
32
|
-
|
33
|
-
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
def on_message(data = nil, &block)
|
37
|
+
unless data
|
38
|
+
@on_message = block if block
|
39
|
+
return @on_message
|
40
|
+
end
|
41
|
+
instance_exec( data, &@on_message)
|
39
42
|
end
|
40
|
-
instance_exec( data, &@on_message)
|
41
|
-
end
|
42
43
|
|
43
|
-
|
44
|
-
unless protocol
|
44
|
+
def on_open(&block)
|
45
45
|
raise 'The on_open even is invalid at this point.' if block
|
46
|
-
|
47
|
-
|
46
|
+
@io = @request[:io]
|
47
|
+
Iodine::Http::Request.parse @request
|
48
|
+
instance_exec(&@on_open) if @on_open
|
48
49
|
end
|
49
|
-
@io = protocol
|
50
|
-
Iodine::Http::Request.parse @request
|
51
|
-
instance_exec(&@on_open) if @on_open
|
52
|
-
end
|
53
|
-
|
54
|
-
def on_close(&block)
|
55
|
-
@on_close = block if block
|
56
|
-
instance_exec(&@on_close) if @on_close
|
57
|
-
end
|
58
50
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
# raise 'Cannot send data when the connection is closed.' if closed?
|
64
|
-
@io << data
|
65
|
-
end
|
66
|
-
alias :write :<<
|
51
|
+
def on_close(&block)
|
52
|
+
@on_close = block if block
|
53
|
+
instance_exec(&@on_close) if @on_close
|
54
|
+
end
|
67
55
|
|
68
|
-
|
69
|
-
|
70
|
-
@
|
71
|
-
|
56
|
+
# Sends data through the socket. a shortcut for ws_client.response <<
|
57
|
+
#
|
58
|
+
# @return [true, false] Returns the true if the data was actually sent or nil if no data was sent.
|
59
|
+
def << data
|
60
|
+
raise 'Cannot send data when the connection is closed.' if closed?
|
61
|
+
@io << data
|
62
|
+
end
|
63
|
+
alias :write :<<
|
72
64
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
65
|
+
# closes the connection, if open
|
66
|
+
def close
|
67
|
+
@io.close if @io
|
68
|
+
end
|
77
69
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
70
|
+
# checks if the socket is open (if the websocket was terminated abnormally, this might returs true when it should be false).
|
71
|
+
def closed?
|
72
|
+
@io.io.closed? if @io && @io.io
|
73
|
+
end
|
82
74
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
# return a Hash with the HTTP cookies recieved during the HTTP's handshake.
|
88
|
-
def cookies
|
89
|
-
@request.cookies
|
90
|
-
end
|
75
|
+
# checks if this is an SSL websocket connection.
|
76
|
+
def ssl?
|
77
|
+
@request.ssl?
|
78
|
+
end
|
91
79
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
# url:: a String representing the URL of the websocket. i.e.: 'ws://foo.bar.com:80/ws/path'
|
96
|
-
# options:: a Hash with options to be used. The options will be used to define the connection's details (i.e. ssl etc') and the Websocket callbacks (i.e. on_open(ws), on_close(ws), on_message(ws))
|
97
|
-
# &block:: an optional block that accepts one parameter (data) and will be used as the `#on_message(data)`
|
98
|
-
#
|
99
|
-
# Acceptable options are:
|
100
|
-
# on_open:: the on_open callback. Must be an objects that answers `call(ws)`, usually a Proc.
|
101
|
-
# on_message:: the on_message callback. Must be an objects that answers `call(ws)`, usually a Proc.
|
102
|
-
# on_close:: the on_close callback. Must be an objects that answers `call(ws)`, usually a Proc.
|
103
|
-
# headers:: a Hash of custom HTTP headers to be sent with the request. Header data, including cookie headers, should be correctly encoded.
|
104
|
-
# cookies:: a Hash of cookies to be sent with the request. cookie data will be encoded before being sent.
|
105
|
-
# timeout:: the number of seconds to wait before the connection is established. Defaults to 5 seconds.
|
106
|
-
#
|
107
|
-
# The method will block until the connection is established or until 5 seconds have passed (the timeout). The method will either return a WebsocketClient instance object or raise an exception it the connection was unsuccessful.
|
108
|
-
#
|
109
|
-
# An on_message Proc must be defined, or the method will fail.
|
110
|
-
#
|
111
|
-
# The on_message Proc can be defined using the optional block:
|
112
|
-
#
|
113
|
-
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/") {|data| write data} #echo example
|
114
|
-
#
|
115
|
-
# OR, the on_message Proc can be defined using the options Hash:
|
116
|
-
#
|
117
|
-
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/", on_open: -> {}, on_message: -> {|data| write data })
|
118
|
-
#
|
119
|
-
# The #on_message(data), #on_open and #on_close methods will be executed within the context of the WebsocketClient
|
120
|
-
# object, and will have native acess to the Websocket response object.
|
121
|
-
#
|
122
|
-
# After the WebsocketClient had been created, it's possible to update the #on_message and #on_close methods:
|
123
|
-
#
|
124
|
-
# # updates #on_message
|
125
|
-
# wsclient.on_message do |data|
|
126
|
-
# response << "I'll disconnect on the next message!"
|
127
|
-
# # updates #on_message again.
|
128
|
-
# on_message {|data| disconnect }
|
129
|
-
# end
|
130
|
-
#
|
131
|
-
#
|
132
|
-
# !!please be aware that the Websockt Client will not attempt to verify SSL certificates,
|
133
|
-
# so that even SSL connections are vulnerable to a possible man in the middle attack.
|
134
|
-
#
|
135
|
-
# @return [Iodine::Http::WebsocketClient] this method returns the connected {Iodine::Http::WebsocketClient} or raises an exception if something went wrong (such as a connection timeout).
|
136
|
-
def self.connect url, options={}, &block
|
137
|
-
socket = nil
|
138
|
-
options = options.dup
|
139
|
-
options[:on_message] ||= block
|
140
|
-
raise "No #on_message handler defined! please pass a block or define an #on_message handler!" unless options[:on_message]
|
141
|
-
url = URI.parse(url) unless url.is_a?(URI)
|
142
|
-
|
143
|
-
ssl = url.scheme == "https" || url.scheme == "wss"
|
144
|
-
|
145
|
-
url.port ||= ssl ? 443 : 80
|
146
|
-
url.path = '/' if url.path.to_s.empty?
|
147
|
-
socket = TCPSocket.new(url.host, url.port)
|
148
|
-
if ssl
|
149
|
-
context = OpenSSL::SSL::SSLContext.new
|
150
|
-
context.cert_store = OpenSSL::X509::Store.new
|
151
|
-
context.cert_store.set_default_paths
|
152
|
-
context.set_params verify_mode: (options[:verify_mode] || OpenSSL::SSL::VERIFY_NONE) # OpenSSL::SSL::VERIFY_PEER #OpenSSL::SSL::VERIFY_NONE
|
153
|
-
ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
|
154
|
-
ssl.sync_close = true
|
155
|
-
ssl.connect
|
80
|
+
# return the HTTP's handshake data, including any cookies sent by the server.
|
81
|
+
def request
|
82
|
+
@request
|
156
83
|
end
|
157
|
-
#
|
158
|
-
|
159
|
-
|
160
|
-
options[:headers].each {|k, v| custom_headers << "#{k.to_s}: #{v.to_s}\r\n"} if options[:headers].is_a?(Hash)
|
161
|
-
options[:cookies].each {|k, v| raise 'Illegal cookie name' if k.to_s.match(/[\x00-\x20\(\)<>@,;:\\\"\/\[\]\?\=\{\}\s]/); custom_headers << "Cookie: #{ k }=#{ Iodine::Http::Request.encode_url v }\r\n"} if options[:cookies].is_a?(Hash)
|
162
|
-
|
163
|
-
# send protocol upgrade request
|
164
|
-
websocket_key = [(Array.new(16) {rand 255} .pack 'c*' )].pack('m0*')
|
165
|
-
(ssl || socket).write "GET #{url.path}#{url.query.to_s.empty? ? '' : ('?' + url.query)} HTTP/1.1\r\nHost: #{url.host}#{url.port ? (':'+url.port.to_s) : ''}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nOrigin: #{options[:ssl_client] ? 'https' : 'http'}://#{url.host}\r\nSec-WebSocket-Key: #{websocket_key}\r\nSec-WebSocket-Version: 13\r\n#{custom_headers}\r\n"
|
166
|
-
# wait for answer - make sure we don't over-read
|
167
|
-
# (a websocket message might be sent immidiately after connection is established)
|
168
|
-
reply = ''
|
169
|
-
reply.force_encoding(::Encoding::ASCII_8BIT)
|
170
|
-
stop_time = Time.now + (options[:timeout] || 5)
|
171
|
-
stop_reply = "\r\n\r\n"
|
172
|
-
sleep 0.2
|
173
|
-
until reply[-4..-1] == stop_reply
|
174
|
-
begin
|
175
|
-
reply << ( ssl ? ssl.read_nonblock(1) : socket.recv_nonblock(1) )
|
176
|
-
rescue Errno::EWOULDBLOCK => e
|
177
|
-
raise "Websocket client handshake timed out (HTTP reply not recieved)\n\n Got Only: #{reply}" if Time.now >= stop_time
|
178
|
-
IO.select [socket], nil, nil, (options[:timeout] || 5)
|
179
|
-
retry
|
180
|
-
end
|
181
|
-
raise "Connection failed" if socket.closed?
|
84
|
+
# return a Hash with the HTTP cookies recieved during the HTTP's handshake.
|
85
|
+
def cookies
|
86
|
+
@request.cookies
|
182
87
|
end
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
#
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
88
|
+
|
89
|
+
# Create a simple Websocket Client(!).
|
90
|
+
#
|
91
|
+
# This method accepts two parameters:
|
92
|
+
# url:: a String representing the URL of the websocket. i.e.: 'ws://foo.bar.com:80/ws/path'
|
93
|
+
# options:: a Hash with options to be used. The options will be used to define the connection's details (i.e. ssl etc') and the Websocket callbacks (i.e. on_open(ws), on_close(ws), on_message(ws))
|
94
|
+
# &block:: an optional block that accepts one parameter (data) and will be used as the `#on_message(data)`
|
95
|
+
#
|
96
|
+
# Acceptable options are:
|
97
|
+
# on_open:: the on_open callback. Must be an objects that answers `call(ws)`, usually a Proc.
|
98
|
+
# on_message:: the on_message callback. Must be an objects that answers `call(ws)`, usually a Proc.
|
99
|
+
# on_close:: the on_close callback. Must be an objects that answers `call(ws)`, usually a Proc.
|
100
|
+
# headers:: a Hash of custom HTTP headers to be sent with the request. Header data, including cookie headers, should be correctly encoded.
|
101
|
+
# cookies:: a Hash of cookies to be sent with the request. cookie data will be encoded before being sent.
|
102
|
+
# timeout:: the number of seconds to wait before the connection is established. Defaults to 5 seconds.
|
103
|
+
#
|
104
|
+
# The method will block until the connection is established or until 5 seconds have passed (the timeout). The method will either return a WebsocketClient instance object or raise an exception it the connection was unsuccessful.
|
105
|
+
#
|
106
|
+
# An on_message Proc must be defined, or the method will fail.
|
107
|
+
#
|
108
|
+
# The on_message Proc can be defined using the optional block:
|
109
|
+
#
|
110
|
+
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/") {|data| write data} #echo example
|
111
|
+
#
|
112
|
+
# OR, the on_message Proc can be defined using the options Hash:
|
113
|
+
#
|
114
|
+
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/", on_open: -> {}, on_message: -> {|data| write data })
|
115
|
+
#
|
116
|
+
# The #on_message(data), #on_open and #on_close methods will be executed within the context of the WebsocketClient
|
117
|
+
# object, and will have native acess to the Websocket response object.
|
118
|
+
#
|
119
|
+
# After the WebsocketClient had been created, it's possible to update the #on_message and #on_close methods:
|
120
|
+
#
|
121
|
+
# # updates #on_message
|
122
|
+
# wsclient.on_message do |data|
|
123
|
+
# response << "I'll disconnect on the next message!"
|
124
|
+
# # updates #on_message again.
|
125
|
+
# on_message {|data| disconnect }
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
#
|
129
|
+
# !!please be aware that the Websockt Client will not attempt to verify SSL certificates,
|
130
|
+
# so that even SSL connections are vulnerable to a possible man in the middle attack.
|
131
|
+
#
|
132
|
+
# @return [Iodine::Http::WebsocketClient] this method returns the connected {Iodine::Http::WebsocketClient} or raises an exception if something went wrong (such as a connection timeout).
|
133
|
+
def self.connect url, options={}, &block
|
134
|
+
socket = nil
|
135
|
+
options = options.dup
|
136
|
+
options[:on_message] ||= block
|
137
|
+
raise "No #on_message handler defined! please pass a block or define an #on_message handler!" unless options[:on_message]
|
138
|
+
url = URI.parse(url) unless url.is_a?(URI)
|
139
|
+
|
140
|
+
ssl = url.scheme == "https" || url.scheme == "wss"
|
141
|
+
|
142
|
+
url.port ||= ssl ? 443 : 80
|
143
|
+
url.path = '/' if url.path.to_s.empty?
|
144
|
+
socket = TCPSocket.new(url.host, url.port)
|
145
|
+
if ssl
|
146
|
+
context = OpenSSL::SSL::SSLContext.new
|
147
|
+
context.cert_store = OpenSSL::X509::Store.new
|
148
|
+
context.cert_store.set_default_paths
|
149
|
+
context.set_params verify_mode: (options[:verify_mode] || OpenSSL::SSL::VERIFY_NONE) # OpenSSL::SSL::VERIFY_PEER #OpenSSL::SSL::VERIFY_NONE
|
150
|
+
ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
|
151
|
+
ssl.sync_close = true
|
152
|
+
ssl.connect
|
153
|
+
end
|
154
|
+
# prep custom headers
|
155
|
+
custom_headers = ''
|
156
|
+
custom_headers = options[:headers] if options[:headers].is_a?(String)
|
157
|
+
options[:headers].each {|k, v| custom_headers << "#{k.to_s}: #{v.to_s}\r\n"} if options[:headers].is_a?(Hash)
|
158
|
+
options[:cookies].each {|k, v| raise 'Illegal cookie name' if k.to_s.match(/[\x00-\x20\(\)<>@,;:\\\"\/\[\]\?\=\{\}\s]/); custom_headers << "Cookie: #{ k }=#{ Iodine::Http::Request.encode_url v }\r\n"} if options[:cookies].is_a?(Hash)
|
159
|
+
|
160
|
+
# send protocol upgrade request
|
161
|
+
websocket_key = [(Array.new(16) {rand 255} .pack 'c*' )].pack('m0*')
|
162
|
+
(ssl || socket).write "GET #{url.path}#{url.query.to_s.empty? ? '' : ('?' + url.query)} HTTP/1.1\r\nHost: #{url.host}#{url.port ? (':'+url.port.to_s) : ''}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nOrigin: #{options[:ssl_client] ? 'https' : 'http'}://#{url.host}\r\nSec-WebSocket-Key: #{websocket_key}\r\nSec-WebSocket-Version: 13\r\n#{custom_headers}\r\n"
|
163
|
+
# wait for answer - make sure we don't over-read
|
164
|
+
# (a websocket message might be sent immidiately after connection is established)
|
165
|
+
reply = ''
|
166
|
+
reply.force_encoding(::Encoding::ASCII_8BIT)
|
167
|
+
stop_time = Time.now + (options[:timeout] || 5)
|
168
|
+
stop_reply = "\r\n\r\n"
|
169
|
+
sleep 0.2
|
170
|
+
until reply[-4..-1] == stop_reply
|
171
|
+
begin
|
172
|
+
reply << ( ssl ? ssl.read_nonblock(1) : socket.recv_nonblock(1) )
|
173
|
+
rescue Errno::EWOULDBLOCK => e
|
174
|
+
raise "Websocket client handshake timed out (HTTP reply not recieved)\n\n Got Only: #{reply}" if Time.now >= stop_time
|
175
|
+
IO.select [socket], nil, nil, (options[:timeout] || 5)
|
176
|
+
retry
|
207
177
|
end
|
178
|
+
raise "Connection failed" if socket.closed?
|
208
179
|
end
|
209
|
-
|
210
|
-
|
180
|
+
# review reply
|
181
|
+
raise "Connection Refused. Reply was:\r\n #{reply}" unless reply.lines[0].match(/^HTTP\/[\d\.]+ 101/i)
|
182
|
+
raise 'Websocket Key Authentication failed.' unless reply.match(/^Sec-WebSocket-Accept:[\s]*([^\s]*)/i) && reply.match(/^Sec-WebSocket-Accept:[\s]*([^\s]*)/i)[1] == Digest::SHA1.base64digest(websocket_key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
183
|
+
# read the body's data and parse any incoming data.
|
184
|
+
request = Iodine::Http::Request.new
|
185
|
+
request[:method] = 'GET'
|
186
|
+
request['host'] = "#{url.host}:#{url.port}"
|
187
|
+
request[:query] = url.path
|
188
|
+
request[:version] = '1.1'
|
189
|
+
reply = StringIO.new reply
|
190
|
+
reply.gets
|
191
|
+
|
192
|
+
until reply.eof?
|
193
|
+
until request[:headers_complete] || (l = reply.gets).nil?
|
194
|
+
if l.include? ':'
|
195
|
+
l = l.strip.split(/:[\s]?/, 2)
|
196
|
+
l[0].strip! ; l[0].downcase!;
|
197
|
+
request[l[0]] ? (request[l[0]].is_a?(Array) ? (request[l[0]] << l[1]) : request[l[0]] = [request[l[0]], l[1] ]) : (request[l[0]] = l[1])
|
198
|
+
elsif l =~ /^[\r]?\n/
|
199
|
+
request[:headers_complete] = true
|
200
|
+
else
|
201
|
+
#protocol error
|
202
|
+
raise 'Protocol Error, closing connection.'
|
203
|
+
return close
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
reply.string.clear
|
211
208
|
|
212
|
-
|
209
|
+
request[:ws_client_params] = options
|
210
|
+
client = self.new(request)
|
211
|
+
Iodine::Http::Websockets.new( ( ssl || socket), client, request )
|
213
212
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
213
|
+
return client
|
214
|
+
|
215
|
+
rescue => e
|
216
|
+
(ssl || socket).tap {|io| next if io.nil?; io.close unless io.closed?}
|
217
|
+
raise e
|
218
|
+
end
|
220
219
|
end
|
221
220
|
end
|
222
221
|
end
|
@@ -1,38 +1,72 @@
|
|
1
|
-
require 'iodine'
|
2
|
-
require 'stringio'
|
3
|
-
require 'time'
|
4
|
-
require 'json'
|
5
|
-
require 'yaml'
|
6
|
-
require 'uri'
|
7
|
-
require 'tmpdir'
|
8
|
-
require 'zlib'
|
9
|
-
|
10
|
-
# require 'securerandom'
|
11
1
|
|
12
2
|
module Iodine
|
13
3
|
class Http < Iodine::Protocol
|
14
|
-
|
4
|
+
|
5
|
+
# This class is a good demonstration for creating a Websocket handler with the Iodine API.
|
6
|
+
#
|
7
|
+
# Iodine is Object Oriented and for this reason the Websocket handler is expected to
|
8
|
+
# retain the information it needs - either through initialization, or through the `on_open(protocol)` callback.
|
9
|
+
class WebsocketHandler
|
10
|
+
# The original Http request
|
11
|
+
attr_reader :request
|
12
|
+
# The Http response, also allowing for websocket data
|
13
|
+
attr_reader :response
|
14
|
+
# this is called while still communicating over Http (during the upgrade process).
|
15
15
|
def initialize request, response
|
16
16
|
@request = request
|
17
17
|
@response = response
|
18
|
-
on_open
|
19
18
|
end
|
19
|
+
# initialize the protocol data once the connection had opened.
|
20
20
|
def on_open
|
21
21
|
end
|
22
|
+
# Accept data using this callback - this is a required callback.
|
22
23
|
def on_message data
|
23
|
-
@response << "You >> #{data}"
|
24
24
|
end
|
25
|
+
# Accept unicasts or broadcasts using this callback.
|
26
|
+
def on_broadcast data
|
27
|
+
end
|
28
|
+
# cleanup, if needed, using this callback.
|
25
29
|
def on_close
|
26
30
|
end
|
27
31
|
|
28
32
|
# This method allows the class itself to act as the Websocket handler, usable with:
|
29
33
|
# Iodine::Http.on_websocket Iodine::Http::WebsocketEchoDemo
|
30
34
|
def self.call request, response
|
31
|
-
return false if request[:path] =~ /refuse/i
|
32
35
|
self.new request, response
|
33
36
|
end
|
34
37
|
|
35
38
|
protected
|
39
|
+
|
40
|
+
### some helper methods
|
41
|
+
|
42
|
+
# Write data to the client, using Websockets encoded frames.
|
43
|
+
def write data
|
44
|
+
# We leverage the fact that the Http response can be used to send Websocket data.
|
45
|
+
#
|
46
|
+
# you can also use Websocket#send_data or it's alias Websocket#<<
|
47
|
+
# do NOT use Websocket#write (which writes the data directly, bypassing the protocol).
|
48
|
+
@response << data
|
49
|
+
end
|
50
|
+
|
51
|
+
# Send messages directly to a specific Websocket.
|
52
|
+
#
|
53
|
+
# This implementation is limited to a single process on a single server.
|
54
|
+
# Consider using Redis for a scalable implementation.
|
55
|
+
def unicast id, data
|
56
|
+
# @request[:io] contains the Websocket Protocol
|
57
|
+
@request[:io].unicast id, data
|
58
|
+
end
|
59
|
+
# Broadcast to all Websockets, except self.
|
60
|
+
#
|
61
|
+
# This implementation is limited to a single process on a single server.
|
62
|
+
# Consider using Redis for a scalable implementation.
|
63
|
+
def broadcast data
|
64
|
+
@request[:io].broadcast data
|
65
|
+
end
|
66
|
+
# Closes the connection
|
67
|
+
def close
|
68
|
+
@request[:io].go_away
|
69
|
+
end
|
36
70
|
end
|
37
71
|
end
|
38
72
|
end
|