Module: GRHttp::Base::WSHandler

Defined in:
lib/grhttp/ws_handler.rb

Overview

The GReactor’s WebSocket handler used by the GRHttp server.

Class Method Summary collapse

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.


162
163
164
165
166
167
168
169
170
# File 'lib/grhttp/ws_handler.rb', line 162

def broadcast data, ignore_io = nil
	if ignore_io
		ig_id = ignore_io.object_id
		GReactor.each {|io| GReactor.queue [io, data], DO_BROADCAST_PROC unless io.object_id == ig_id}
	else
		GReactor.each {|io| GReactor.queue [io, data], DO_BROADCAST_PROC }
	end
	true
end

.call(io) ⇒ Object

This method is called by the reactor. By default, this method reads the data from the IO and calls the `#on_message data` method.

This method is called within a lock on the connection (Mutex) - craeful from double locking.


14
15
16
17
18
19
# File 'lib/grhttp/ws_handler.rb', line 14

def call io
	extract_message io, a = StringIO.new(io.read.to_s)
	a.string.clear
	a.close

end

.close_frameObject

byte_size = data.bytesize if byte_size < 125 frame << byte_size.chr elsif byte_size.bit_length <= 16 frame << 126.chr frame << [byte_size].pack('S>') else frame << 127.chr frame << [byte_size].pack('Q>') end frame.force_encoding(data.encoding) frame << data frame.force_encoding(::Encoding::ASCII_8BIT) frame end


149
150
151
# File 'lib/grhttp/ws_handler.rb', line 149

def close_frame
	CLOSE_FRAME
end

.http_handshake(request, response, handler) ⇒ Object

perform the HTTP handshake for WebSockets. send a 400 Bad Request error if handshake fails.


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/grhttp/ws_handler.rb', line 42

def http_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]
	io[:keep_alive] = true
	response.status = 101
	response['upgrade'] = 'websocket'
	response['content-length'] = '0'
	response['connection'] = 'Upgrade'
	response['sec-websocket-version'] = '13'
	# 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.
	io[:ws_extentions] = []
	request['sec-websocket-extensions'].to_s.split(/[\s]*[,][\s]*/).each {|ex| ex = ex.split(/[\s]*;[\s]*/); io[:ws_extentions] << ex if SUPPORTED_EXTENTIONS[ ex[0] ]}
	response['sec-websocket-extensions'] = io[:ws_extentions].map {|e| e[0] } .join (',')
	response.headers.delete 'sec-websocket-extensions' if response['sec-websocket-extensions'].empty?
	response['Sec-WebSocket-Accept'] = Digest::SHA1.base64digest(request['sec-websocket-key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
	response.finish
	# GReactor.log_raw "#{@request[:client_ip]} [#{Time.now.utc}] - #{@connection.object_id} Upgraded HTTP to WebSockets.\n"
	request.cookies.set_response nil
	io[:ws_parser] = parser_hash
	io[:ws_extentions].freeze
	io[:websocket_handler] = handler
	io.params[:handler] = self
	io.timeout = 60
	handler.on_open(WSEvent.new(io, nil)) if handler.respond_to? :on_open
	return true
end

.is_valid_request?(request, response) ⇒ Boolean

Returns:

  • (Boolean)

74
75
76
77
# File 'lib/grhttp/ws_handler.rb', line 74

def is_valid_request? request, response
	return true if request.upgrade?
	refuse response
end

.message_size_limitObject

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


37
38
39
# File 'lib/grhttp/ws_handler.rb', line 37

def message_size_limit
	@message_size_limit
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.


33
34
35
# File 'lib/grhttp/ws_handler.rb', line 33

def message_size_limit=val
	@message_size_limit = val
end

.on_disconnect(io) ⇒ Object

This method is called by the reactor. By default, this method reads the data from the IO and calls the `#on_message data` method.


22
23
24
25
# File 'lib/grhttp/ws_handler.rb', line 22

def on_disconnect io
	h = io[:websocket_handler]
	h.on_close(WSEvent.new(io, nil)) if h.respond_to? :on_close
end

.send_data(io, data, op_code = nil, fin = true) ⇒ Object

sends the data as one (or more) Websocket frames


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/grhttp/ws_handler.rb', line 80

def send_data io, data, op_code = nil, fin = true
	return false if !data || data.empty?
	op_code ||= (data.encoding == ::Encoding::UTF_8 ? 1 : 2)
	byte_size = data.bytesize
	if byte_size > (FRAME_SIZE_LIMIT+2)
		data = data.dup
		sections = byte_size/FRAME_SIZE_LIMIT + (byte_size%FRAME_SIZE_LIMIT ? 1 : 0)
		send_data( io, data.slice!( 0...FRAME_SIZE_LIMIT ), op_code, data.empty?) && op_code = 0 until data.empty?
		# sections.times { |i| send_data io, data.slice!( 0...FRAME_SIZE_LIMIT ), op_code, (i==sections) }
	end
	# apply extenetions to the frame
	ext = 0
	# # ext |= call each io.protocol.extenetions with data #changes data and returns flags to be set
	# io[:ws_extentions].each { |ex| ext |= WSProtocol::SUPPORTED_EXTENTIONS[ex[0]][2].call data, ex[1..-1]}
	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>')
	else
		header << 127.chr
		header << [byte_size].pack('Q>')
	end
	io.write header
	io.write(data) && true
end

.unicast(uuid, 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).


180
181
182
183
184
# File 'lib/grhttp/ws_handler.rb', line 180

def unicast uuid, data
	return false unless uuid && data
	GReactor.each {|io| next unless io[:uuid] == uuid; GReactor.queue [io, data], DO_BROADCAST_PROC; return true}
	false
end