iodine 0.0.1 → 0.0.2

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 iodine might be problematic. Click here for more details.

@@ -0,0 +1,224 @@
1
+
2
+ module Iodine
3
+ class Http < Iodine::Protocol
4
+ # Create a simple Websocket Client(!).
5
+ #
6
+ # This should be done from within an Iodine task, or the callbacks will not be called.
7
+ #
8
+ # Use {Iodine::Http::WebsocketClient.connect} to initialize a client with all the callbacks needed.
9
+ class WebsocketClient
10
+ attr_accessor :response, :request
11
+
12
+ def initialize request
13
+ @response = nil
14
+ @request = request
15
+ params = request[:ws_client_params]
16
+ @on_message = params[:on_message]
17
+ raise "Websocket client must have an #on_message Proc or handler." unless @on_message && @on_message.respond_to?(:call)
18
+ @on_open = params[:on_open]
19
+ @on_close = params[:on_close]
20
+ end
21
+
22
+ def on event_name, &block
23
+ return false unless block
24
+ case event_name
25
+ when :message
26
+ @on_message = block
27
+ when :close
28
+ @on_close = block
29
+ when :open
30
+ raise 'The on_open even is invalid at this point.'
31
+ end
32
+
33
+ end
34
+
35
+ def on_message(data = nil, &block)
36
+ unless data
37
+ @on_message = block if block
38
+ return @on_message
39
+ end
40
+ instance_exec( data, &@on_message)
41
+ end
42
+
43
+ def on_open(protocol = nil, &block)
44
+ unless protocol
45
+ raise 'The on_open even is invalid at this point.' if block
46
+ # @on_open = block if block
47
+ return @on_open
48
+ 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
+
59
+ # Sends data through the socket. a shortcut for ws_client.response <<
60
+ #
61
+ # @return [true, false] Returns the true if the data was actually sent or nil if no data was sent.
62
+ def << data
63
+ # raise 'Cannot send data when the connection is closed.' if closed?
64
+ @io << data
65
+ end
66
+ alias :write :<<
67
+
68
+ # closes the connection, if open
69
+ def close
70
+ @io.close if @io
71
+ end
72
+
73
+ # checks if the socket is open (if the websocket was terminated abnormally, this might returs true when it should be false).
74
+ def closed?
75
+ @io.io.closed?
76
+ end
77
+
78
+ # checks if this is an SSL websocket connection.
79
+ def ssl?
80
+ @request.ssl?
81
+ end
82
+
83
+ # return the HTTP's handshake data, including any cookies sent by the server.
84
+ def request
85
+ @request
86
+ end
87
+ # return a Hash with the HTTP cookies recieved during the HTTP's handshake.
88
+ def cookies
89
+ @request.cookies
90
+ end
91
+
92
+ # Create a simple Websocket Client(!).
93
+ #
94
+ # This method accepts two parameters:
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
156
+ end
157
+ # prep custom headers
158
+ custom_headers = ''
159
+ custom_headers = options[:headers] if options[:headers].is_a?(String)
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?
182
+ end
183
+ # review reply
184
+ raise "Connection Refused. Reply was:\r\n #{reply}" unless reply.lines[0].match(/^HTTP\/[\d\.]+ 101/i)
185
+ 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')
186
+ # read the body's data and parse any incoming data.
187
+ request = Iodine::Http::Request.new
188
+ request[:method] = 'GET'
189
+ request['host'] = "#{url.host}:#{url.port}"
190
+ request[:query] = url.path
191
+ request[:version] = '1.1'
192
+ reply = StringIO.new reply
193
+ reply.gets
194
+
195
+ until reply.eof?
196
+ until request[:headers_complete] || (l = reply.gets).nil?
197
+ if l.include? ':'
198
+ l = l.strip.split(/:[\s]?/, 2)
199
+ l[0].strip! ; l[0].downcase!;
200
+ 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])
201
+ elsif l =~ /^[\r]?\n/
202
+ request[:headers_complete] = true
203
+ else
204
+ #protocol error
205
+ raise 'Protocol Error, closing connection.'
206
+ return close
207
+ end
208
+ end
209
+ end
210
+ reply.string.clear
211
+
212
+ request[:ws_client_params] = options
213
+
214
+ Iodine::Http::Websockets.new( ( ssl || socket), self.new(request), request )
215
+
216
+ rescue => e
217
+ (ssl || socket).tap {|io| next if io.nil?; io.close unless io.closed?}
218
+ raise e
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+
@@ -0,0 +1,40 @@
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
+
12
+ module Iodine
13
+ class Http < Iodine::Protocol
14
+ class WebsocketEchoDemo
15
+ def initialize request, response
16
+ @request = request
17
+ @response = response
18
+ on_open
19
+ end
20
+ def on_open
21
+ end
22
+ def on_message data
23
+ @response << "You >> #{data}"
24
+ end
25
+ def on_close
26
+ end
27
+
28
+ # This method allows the class itself to act as the Websocket handler, usable with:
29
+ # Iodine::Http.on_websocket Iodine::Http::WebsocketEchoDemo
30
+ def self.call request, response
31
+ return false if request[:path] =~ /refuse/i
32
+ self.new request, response
33
+ end
34
+
35
+ protected
36
+ end
37
+ end
38
+ end
39
+
40
+
@@ -0,0 +1,319 @@
1
+ module Iodine
2
+ class Http < Iodine::Protocol
3
+ class Websockets < ::Iodine::Protocol
4
+ def initialize io, handler, request, ws_extentions = nil
5
+ @handler = handler
6
+ @ws_extentions = ws_extentions
7
+ request[:io] = self
8
+ super(io)
9
+ end
10
+ def on_open
11
+ set_timeout 45
12
+ @parser = {body: '', stage: 0, step: 0, mask_key: [], len_bytes: []}
13
+ set_timeout = self.class.default_timeout
14
+ @handler.on_open self if @handler.respond_to? :on_open
15
+ end
16
+ def on_message data
17
+ extract_message StringIO.new(data)
18
+ end
19
+ def on_broadcast data
20
+ @handler.on_broadcast(data) if @handler.respond_to? :on_broadcast
21
+ end
22
+ def on_close
23
+ @handler.on_close if @handler.respond_to? :on_close
24
+ if @ws_extentions
25
+ @ws_extentions.each { |ex| ex.close }
26
+ @ws_extentions.clear
27
+ end
28
+ end
29
+
30
+ def send_response response, finish = false
31
+ body = response.extract_body
32
+ send_data body
33
+ end
34
+ alias :stream_response :send_response
35
+
36
+ # sends the data as one (or more) Websocket frames
37
+ def send_data data, op_code = nil, fin = true, ext = 0
38
+ return false if !data || data.empty?
39
+ return false if @io.closed?
40
+ data = data.dup # needed?
41
+ unless op_code # apply extenetions to the message as a whole
42
+ op_code = (data.encoding == ::Encoding::UTF_8 ? 1 : 2)
43
+ @ws_extentions.each { |ex| ext |= ex.edit_message data } if @ws_extentions
44
+ end
45
+ byte_size = data.bytesize
46
+ if byte_size > (FRAME_SIZE_LIMIT+2)
47
+ sections = byte_size/FRAME_SIZE_LIMIT + (byte_size%FRAME_SIZE_LIMIT ? 1 : 0)
48
+ send_data( data.slice!( 0...FRAME_SIZE_LIMIT ), op_code, data.empty?, ext) && (ext = op_code = 0) until data.empty?
49
+ return true # avoid sending an empty frame.
50
+ end
51
+ @ws_extentions.each { |ex| ext |= ex.edit_frame data } if @ws_extentions
52
+ header = ( (fin ? 0b10000000 : 0) | (op_code & 0b00001111) | ext).chr.force_encoding(::Encoding::ASCII_8BIT)
53
+
54
+ if byte_size < 125
55
+ header << byte_size.chr
56
+ elsif byte_size.bit_length <= 16
57
+ header << 126.chr
58
+ header << [byte_size].pack('S>'.freeze)
59
+ else
60
+ header << 127.chr
61
+ header << [byte_size].pack('Q>'.freeze)
62
+ end
63
+ write header
64
+ write(data) && true
65
+ end
66
+ alias :<< :send_data
67
+
68
+ # Sends a ping.
69
+ def ping
70
+ write PING_FRAME
71
+ end
72
+ # Sends an empty pong.
73
+ def pong
74
+ write PONG_FRAME
75
+ end
76
+
77
+ # Broadcasts data to ALL the websocket connections EXCEPT the once specified (if specified).
78
+ #
79
+ # Data broadcasted will be recived by the websocket handler it's #on_broadcast(ws) method (if exists).
80
+ #
81
+ # Accepts:
82
+ #
83
+ # data:: One object of data. Usually a Hash, Array, String or a JSON formatted object.
84
+ # ignore_io (optional):: The IO to be ignored by the broadcast. Usually the broadcaster's IO.
85
+ #
86
+ def self.broadcast data, ignore_io = nil
87
+ if ignore_io
88
+ ig_id = ignore_io.object_id
89
+ each {|io| Iodine.run io, data, &broadcast_proc unless io.object_id == ig_id}
90
+ else
91
+ each {|io| Iodine.run io, data, &broadcast_proc }
92
+ end
93
+ true
94
+ end
95
+
96
+ # Broadcasts the data to all the listening websockets, except self. See {::Iodine::Http::Websockets.broadcast}
97
+ def broadcast data
98
+ self.class.broadcast data, self
99
+ end
100
+
101
+ # Unicast data to a specific websocket connection (ONLY the connection specified).
102
+ #
103
+ # Data broadcasted will be recived by the websocket handler it's #on_broadcast(ws) method (if exists).
104
+ # Accepts:
105
+ # uuid:: the UUID of the websocket connection recipient.
106
+ # data:: the data to be sent.
107
+ #
108
+ # @return [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).
109
+ def self.unicast id, data
110
+ return false unless id && data
111
+ each {|io| next unless io.id == id; Iodine.run io, data, &broadcast_proc; return true}
112
+ false
113
+ end
114
+ # @return [true, false] Unicasts the data to the requested connection. returns `true` if the requested connection id was found on this server. See {::Iodine::Http::Websockets.unicast}
115
+ def unicast id, data
116
+ self.class.unicast id, data
117
+ end
118
+
119
+ def self.handshake request, response, handler
120
+ # review handshake (version, extentions)
121
+ # should consider adopting the websocket gem for handshake and framing:
122
+ # https://github.com/imanel/websocket-ruby
123
+ # http://www.rubydoc.info/github/imanel/websocket-ruby
124
+ return refuse response unless handler || handler == true
125
+ io = request[:io]
126
+ response.keep_alive = true
127
+ response.status = 101
128
+ response['upgrade'.freeze] = 'websocket'.freeze
129
+ response['content-length'.freeze] = '0'.freeze
130
+ response['connection'.freeze] = 'Upgrade'.freeze
131
+ response['sec-websocket-version'.freeze] = '13'.freeze
132
+ # Note that the client is only offering to use any advertised extensions
133
+ # and MUST NOT use them unless the server indicates that it wishes to use the extension.
134
+ ws_extentions = []
135
+ ext = []
136
+ 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] ] }
137
+ ext.compact!
138
+ if ext.any?
139
+ response['sec-websocket-extensions'.freeze] = ext.join(', ')
140
+ else
141
+ ws_extentions = nil
142
+ end
143
+ response['Sec-WebSocket-Accept'.freeze] = Digest::SHA1.base64digest(request['sec-websocket-key'.freeze] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'.freeze)
144
+ response.session
145
+ # Iodine.logger << "#{@request[:client_ip]} [#{Time.now.utc}] - #{@connection.object_id} Upgraded HTTP to WebSockets.\n"
146
+ # request.io.handler.send_response response
147
+ response.finish
148
+ self.new(io.io, handler, request, ws_extentions)
149
+ return true
150
+ end
151
+
152
+ # Gets the new connection timeout in seconds. Whenever this timeout is reached, a ping will be sent. Defaults to 40 (seconds).
153
+ def self.default_timeout
154
+ @default_timeout
155
+ end
156
+ # Sets the new connection timeout in seconds. Whenever this timeout is reached, a ping will be sent. Defaults to 40 (seconds).
157
+ def self.default_timeout= val
158
+ @default_timeout = val
159
+ end
160
+ # Sets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
161
+ #
162
+ # Although memory will be allocated for the latest TCP/IP frame,
163
+ # this allows the websocket to disconnect if the incoming expected message size exceeds the allowed maximum size.
164
+ #
165
+ # 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.
166
+ def self.message_size_limit=val
167
+ @message_size_limit = val
168
+ end
169
+ # Gets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
170
+ def self.message_size_limit
171
+ @message_size_limit ||= 0
172
+ end
173
+
174
+ protected
175
+ FRAME_SIZE_LIMIT = 17_895_697
176
+ SUPPORTED_EXTENTIONS = {}
177
+ CLOSE_FRAME = "\x88\x00".freeze
178
+ PONG_FRAME = "\x8A\x00".freeze
179
+ PING_FRAME = "\x89\x00".freeze
180
+ @default_timeout = 40
181
+
182
+ def self.broadcast_proc
183
+ @broadcast_proc ||= Proc.new {|io, data| io.on_broadcast data }
184
+ end
185
+
186
+ def self.refuse response
187
+ response.status = 400
188
+ response['sec-websocket-extensions'.freeze] = SUPPORTED_EXTENTIONS.keys.join(', ')
189
+ response['sec-websocket-version'.freeze] = '13'.freeze
190
+ false
191
+ end
192
+
193
+ # parse the message and send it to the handler
194
+ #
195
+ # test: frame = ["819249fcd3810b93b2fb69afb6e62c8af3e83adc94ee2ddd"].pack("H*").bytes; parser[:stage] = 0; parser = {}
196
+ # accepts:
197
+ # data:: an IO object (usually a StringIO object)
198
+ def extract_message data
199
+ parser = @parser
200
+ until data.eof?
201
+ if parser[:stage] == 0
202
+ tmp = data.getbyte
203
+ return unless tmp
204
+ parser[:fin] = tmp[7] == 1
205
+ parser[:rsv1] = tmp[6] == 1
206
+ parser[:rsv2] = tmp[5] == 1
207
+ parser[:rsv3] = tmp[4] == 1
208
+ parser[:op_code] = tmp & 0b00001111
209
+ parser[:p_op_code] ||= tmp & 0b00001111
210
+ parser[:stage] += 1
211
+ end
212
+ if parser[:stage] == 1
213
+ tmp = data.getbyte
214
+ return unless tmp
215
+ parser[:mask] = tmp[7]
216
+ parser[:mask_key].clear
217
+ parser[:len] = tmp & 0b01111111
218
+ parser[:len_bytes].clear
219
+ parser[:stage] += 1
220
+ end
221
+ if parser[:stage] == 2
222
+ tmp = 0
223
+ tmp = 2 if parser[:len] == 126
224
+ tmp = 8 if parser[:len] == 127
225
+ while parser[:len_bytes].length < tmp
226
+ parser[:len_bytes] << data.getbyte
227
+ return parser[:len_bytes].pop unless parser[:len_bytes].last
228
+ end
229
+ parser[:len] = merge_bytes( parser[:len_bytes] ) if tmp > 0
230
+ parser[:step] = 0
231
+ parser[:stage] += 1
232
+ return false unless review_message_size
233
+ end
234
+ if parser[:stage] == 3 && parser[:mask] == 1
235
+ until parser[:mask_key].length == 4
236
+ parser[:mask_key] << data.getbyte
237
+ return parser[:mask_key].pop unless parser[:mask_key].last
238
+ end
239
+ parser[:stage] += 1
240
+ elsif parser[:stage] == 3 && parser[:mask] != 1
241
+ parser[:stage] += 1
242
+ end
243
+ if parser[:stage] == 4
244
+ if parser[:body].bytesize < parser[:len]
245
+ tmp = data.read(parser[:len] - parser[:body].bytesize)
246
+ return unless tmp
247
+ parser[:body] << tmp
248
+ end
249
+ if parser[:body].bytesize >= parser[:len]
250
+ parser[:body].bytesize.times {|i| parser[:body][i] = (parser[:body][i].ord ^ parser[:mask_key][i % 4]).chr} if parser[:mask] == 1
251
+ parser[:stage] = 99
252
+ end
253
+ end
254
+ complete_frame if parser[:stage] == 99
255
+ end
256
+ end
257
+
258
+ # takes and Array of bytes and combines them to an int(16 Bit), 32Bit or 64Bit number
259
+ def merge_bytes bytes
260
+ return 0 unless bytes.any?
261
+ return bytes.pop if bytes.length == 1
262
+ bytes.pop ^ (merge_bytes(bytes) << 8)
263
+ end
264
+
265
+ # handles the completed frame and sends a message to the handler once all the data has arrived.
266
+ def complete_frame
267
+ parser = @parser
268
+ @ws_extentions.each {|ex| ex.parse_frame(parser) } if @ws_extentions
269
+
270
+ case parser[:op_code]
271
+ when 9 # ping
272
+ # handle parser[:op_code] == 9 (ping)
273
+ ::Iodine.run { send_data parser[:body], 10 }
274
+ parser[:p_op_code] = nil if parser[:p_op_code] == 9
275
+ when 10 #pong
276
+ # handle parser[:op_code] == 10 (pong)
277
+ parser[:p_op_code] = nil if parser[:p_op_code] == 10
278
+ when 8 # close
279
+ # handle parser[:op_code] == 8 (close)
280
+ write( CLOSE_FRAME )
281
+ close
282
+ parser[:p_op_code] = nil if parser[:p_op_code] == 8
283
+ else
284
+ parser[:message] ? ((parser[:message] << parser[:body]) && parser[:body].clear) : ((parser[:message] = parser[:body]) && parser[:body] = '')
285
+ # handle parser[:op_code] == 0 / fin == false (continue a frame that hasn't ended yet)
286
+ if parser[:fin]
287
+ @ws_extentions.each {|ex| ex.parse_message(parser) } if @ws_extentions
288
+ Iodine::Http::Request.make_utf8! parser[:message] if parser[:p_op_code] == 1
289
+ @handler.on_message parser[:message]
290
+ parser[:message] = nil
291
+ parser[:p_op_code] = nil
292
+ end
293
+ end
294
+ parser[:stage] = 0
295
+ parser[:body].clear
296
+ parser[:step] = 0
297
+ parser[:mask_key].clear
298
+ parser[:p_op_code] = nil
299
+ end
300
+ #reviews the message size and closes the connection if expected message size is over the allowed limit.
301
+ def review_message_size
302
+ if ( self.class.message_size_limit.to_i > 0 ) && ( ( @parser[:len] + (@parser[:message] ? @parser[:message].bytesize : 0) ) > self.class.message_size_limit.to_i )
303
+ close
304
+ @parser.delete :message
305
+ @parser[:step] = 0
306
+ @parser[:body].clear
307
+ @parser[:mask_key].clear
308
+ Iodine.warn "Websocket message above limit's set - closing connection."
309
+ return false
310
+ end
311
+ true
312
+ end
313
+
314
+
315
+ end
316
+ end
317
+ end
318
+
319
+