Class: Iodine::Http::Websockets

Inherits:
Protocol
  • Object
show all
Defined in:
lib/iodine/http/websockets.rb

Instance Attribute Summary

Attributes inherited from Protocol

#io

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Protocol

#call, #close, #closed?, each, #id, #read, #set_timeout, #ssl?, #timeout?, #write

Constructor Details

#initialize(io, handler, request, ws_extentions = nil) ⇒ Websockets

initialize the websocket protocol.



5
6
7
8
9
10
# File 'lib/iodine/http/websockets.rb', line 5

def initialize io, handler, request, ws_extentions = nil
	@handler = handler
	@ws_extentions = ws_extentions
	request[:io] = self
	super(io)
end

Class Method Details

.broadcast(data, ignore_io = nil) ⇒ Object

Broadcasts data to ALL the websocket connections EXCEPT the once specified (if specified).

Data broadcasted will be recived by the websocket handler it’s #on_broadcast(ws) method (if exists).

Accepts:

data

One object of data. Usually a Hash, Array, String or a JSON formatted object.

ignore_io (optional)

The IO to be ignored by the broadcast. Usually the broadcaster’s IO.



105
106
107
108
109
110
111
112
113
# File 'lib/iodine/http/websockets.rb', line 105

def self.broadcast data, ignore_io = nil
	if ignore_io
		ig_id = ignore_io.object_id
		each {|io| Iodine.run io, data, &broadcast_proc unless io.object_id == ig_id}
	else
		each {|io| Iodine.run io, data, &broadcast_proc }
	end
	true
end

.default_timeoutObject

Gets the new connection timeout in seconds. Whenever this timeout is reached, a ping will be sent. Defaults to 40 (seconds).



171
172
173
# File 'lib/iodine/http/websockets.rb', line 171

def self.default_timeout
	@default_timeout
end

.default_timeout=(val) ⇒ Object

Sets the new connection timeout in seconds. Whenever this timeout is reached, a ping will be sent. Defaults to 40 (seconds).



175
176
177
# File 'lib/iodine/http/websockets.rb', line 175

def self.default_timeout= val
	@default_timeout = val
end

.handshake(request, response, handler) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/iodine/http/websockets.rb', line 138

def self.handshake request, response, handler
	# review handshake (version, extentions)
	# should consider adopting the websocket gem for handshake and framing:
	# https://github.com/imanel/websocket-ruby
	# http://www.rubydoc.info/github/imanel/websocket-ruby
	return refuse response unless handler || handler == true
	io = request[:io]
	response.keep_alive = true
	response.status = 101
	response['upgrade'.freeze] = 'websocket'.freeze
	response['content-length'.freeze] = '0'.freeze
	response['connection'.freeze] = 'Upgrade'.freeze
	response['sec-websocket-version'.freeze] = '13'.freeze
	# Note that the client is only offering to use any advertised extensions
	# and MUST NOT use them unless the server indicates that it wishes to use the extension.
	ws_extentions = []
	ext = []
	request['sec-websocket-extensions'.freeze].to_s.split(/[\s]*[,][\s]*/).each {|ex| ex = ex.split(/[\s]*;[\s]*/); ( ( tmp = SUPPORTED_EXTENTIONS[ ex[0] ].call(ex[1..-1]) ) && (ws_extentions << tmp) && (ext << tmp.name) ) if SUPPORTED_EXTENTIONS[ ex[0] ] }
	ext.compact!
	if ext.any?
		response['sec-websocket-extensions'.freeze] = ext.join(', ')
	else
		ws_extentions = nil
	end
	response['Sec-WebSocket-Accept'.freeze] = Digest::SHA1.base64digest(request['sec-websocket-key'.freeze] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'.freeze)
	response.session
	# Iodine.log "#{@request[:client_ip]} [#{Time.now.utc}] - #{@connection.object_id} Upgraded HTTP to WebSockets.\n"
	response.finish
	self.new(io.io, handler, request, ws_extentions)
	return true
end

.message_size_limitObject

Gets the message byte size limit for a Websocket message. Defaults to 0 (no limit)



188
189
190
# File 'lib/iodine/http/websockets.rb', line 188

def self.message_size_limit
	@message_size_limit ||= 0
end

.message_size_limit=(val) ⇒ Object

Sets the message byte size limit for a Websocket message. Defaults to 0 (no limit)

Although memory will be allocated for the latest TCP/IP frame, this allows the websocket to disconnect if the incoming expected message size exceeds the allowed maximum size.

If the sessage size limit is exceeded, the disconnection will be immidiate as an attack will be assumed. The protocol’s normal disconnect sequesnce will be discarded.



184
185
186
# File 'lib/iodine/http/websockets.rb', line 184

def self.message_size_limit=val
	@message_size_limit = val
end

.unicast(id, data) ⇒ true, false

Unicast data to a specific websocket connection (ONLY the connection specified).

Data broadcasted will be recived by the websocket handler it’s #on_broadcast(ws) method (if exists). Accepts:

uuid

the UUID of the websocket connection recipient.

data

the data to be sent.

Returns:

  • (true, false)

    Returns true if the object was found and the unicast was sent (the task will be executed asynchronously once the unicast was sent).



128
129
130
131
132
# File 'lib/iodine/http/websockets.rb', line 128

def self.unicast id, data
	return false unless id && data
	each {|io| next unless io.id == id; Iodine.run io, data, &broadcast_proc; return true}
	false
end

Instance Method Details

#broadcast(data) ⇒ Object

Broadcasts the data to all the listening websockets, except self. See broadcast



116
117
118
# File 'lib/iodine/http/websockets.rb', line 116

def broadcast data
	self.class.broadcast data, self
end

#go_awayObject

a politer disconnection.



35
36
37
38
# File 'lib/iodine/http/websockets.rb', line 35

def go_away
	write CLOSE_FRAME
	close
end

#on_broadcast(data) ⇒ Object

handle broadcasts.



22
23
24
# File 'lib/iodine/http/websockets.rb', line 22

def on_broadcast data
	@handler.on_broadcast(data) if @handler.respond_to? :on_broadcast
end

#on_closeObject

cleanup after closing.



26
27
28
29
30
31
32
# File 'lib/iodine/http/websockets.rb', line 26

def on_close
	@handler.on_close if @handler.respond_to? :on_close
	if @ws_extentions
		@ws_extentions.each { |ex| ex.close }
		@ws_extentions.clear
	end
end

#on_message(data) ⇒ Object

parse and handle messages.



18
19
20
# File 'lib/iodine/http/websockets.rb', line 18

def on_message data
	extract_message StringIO.new(data)
end

#on_openObject

continue to initialize the websocket protocol.



12
13
14
15
16
# File 'lib/iodine/http/websockets.rb', line 12

def on_open
	@parser = {body: '', stage: 0, step: 0, mask_key: [], len_bytes: []}
	set_timeout = self.class.default_timeout
	@handler.on_open if @handler.respond_to? :on_open
end

#on_shutdownObject

a politer disconnection during shutdown.



41
42
43
# File 'lib/iodine/http/websockets.rb', line 41

def on_shutdown
	go_away
end

#pingObject

Sends a ping.



88
89
90
# File 'lib/iodine/http/websockets.rb', line 88

def ping
	write PING_FRAME
end

#pongObject

Sends an empty pong.



92
93
94
# File 'lib/iodine/http/websockets.rb', line 92

def pong
	write PONG_FRAME
end

#send_data(data, op_code = nil, fin = true, ext = 0) ⇒ Object Also known as: <<

Sends the data as one (or more) Websocket frames.

Use THIS method to send data using the Websocket protocol. Using Protocol#write will bypass the Websocket data framing and send the raw data, breaking the connection.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/iodine/http/websockets.rb', line 56

def send_data data, op_code = nil, fin = true, ext = 0
	return false if !data || data.empty?
	return false if @io.closed?
	data = data.dup # needed?
	unless op_code # apply extenetions to the message as a whole
		op_code = (data.encoding == ::Encoding::UTF_8 ? 1 : 2) 
		@ws_extentions.each { |ex| ext |= ex.edit_message data } if @ws_extentions
	end
	byte_size = data.bytesize
	if byte_size > (FRAME_SIZE_LIMIT+2)
		sections = byte_size/FRAME_SIZE_LIMIT + (byte_size%FRAME_SIZE_LIMIT ? 1 : 0)
		send_data( data.slice!( 0...FRAME_SIZE_LIMIT ), op_code, data.empty?, ext) && (ext = op_code = 0) until data.empty?
		return true # avoid sending an empty frame.
	end
	@ws_extentions.each { |ex| ext |= ex.edit_frame data } if @ws_extentions
	header = ( (fin ? 0b10000000 : 0) | (op_code & 0b00001111) | ext).chr.force_encoding(::Encoding::ASCII_8BIT)

	if byte_size < 125
		header << byte_size.chr
	elsif byte_size.bit_length <= 16					
		header << 126.chr
		header << [byte_size].pack('S>'.freeze)
	else
		header << 127.chr
		header << [byte_size].pack('Q>'.freeze)
	end
	write header
	write(data) && true
end

#send_response(response, finish = false) ⇒ Object Also known as: stream_response

allow Http responses to be used for sending Websocket data.



46
47
48
49
# File 'lib/iodine/http/websockets.rb', line 46

def send_response response, finish = false
	body = response.extract_body
	send_data body
end

#unicast(id, data) ⇒ true, false

Returns Unicasts the data to the requested connection. returns ‘true` if the requested connection id was found on this server. See unicast.

Returns:

  • (true, false)

    Unicasts the data to the requested connection. returns ‘true` if the requested connection id was found on this server. See unicast



134
135
136
# File 'lib/iodine/http/websockets.rb', line 134

def unicast id, data
	self.class.unicast id, data
end