iodine 0.1.21 → 0.2.0
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/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- data/lib/iodine/timers.rb +0 -95
@@ -1,101 +0,0 @@
|
|
1
|
-
|
2
|
-
module Iodine
|
3
|
-
module Http
|
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 shoulw be called while still communicating over Http,
|
15
|
-
# as part of the "upgrade" process. The new object should be returned by the {Iodine::Http#on_websocket} handler.
|
16
|
-
#
|
17
|
-
# i.e.:
|
18
|
-
#
|
19
|
-
# Iodine::Http.on_websocket do |request, response|
|
20
|
-
# next if request.path =~ /refuse/
|
21
|
-
# Iodine::Http::WebsocketHandler.new request, response
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# see also the {WebsocketHandler.call} method for an example.
|
26
|
-
def initialize request, response
|
27
|
-
@request = request
|
28
|
-
@response = response
|
29
|
-
end
|
30
|
-
# initialize the protocol data once the connection had opened.
|
31
|
-
def on_open
|
32
|
-
end
|
33
|
-
# Accept data using this callback - this is a required callback.
|
34
|
-
def on_message data
|
35
|
-
end
|
36
|
-
# Accept unicasts or broadcasts using this callback.
|
37
|
-
def on_broadcast data
|
38
|
-
end
|
39
|
-
# cleanup, if needed, using this callback.
|
40
|
-
def on_close
|
41
|
-
end
|
42
|
-
# called whenever a ping is sent (no data transfer caused timeout).
|
43
|
-
def on_ping
|
44
|
-
end
|
45
|
-
# extra cleanup, if needed, when server is shutting down while the websocket is connected.
|
46
|
-
#
|
47
|
-
# You can use on_close unless some "going away" cleanup is required.
|
48
|
-
def on_shutdown
|
49
|
-
end
|
50
|
-
|
51
|
-
# This method allows the class itself to act as the global Websocket handler, accepting websocket connections. Example use:
|
52
|
-
#
|
53
|
-
# # Iodine::Http::WebsocketHandler's default implementation does nothing.
|
54
|
-
# Iodine::Http.on_websocket Iodine::Http::WebsocketHandler
|
55
|
-
def self.call request, response
|
56
|
-
self.new request, response
|
57
|
-
end
|
58
|
-
|
59
|
-
protected
|
60
|
-
|
61
|
-
### some helper methods
|
62
|
-
|
63
|
-
# Write data to the client, using Websockets encoded frames.
|
64
|
-
def write data
|
65
|
-
# We can use Websocket#send_data or it's alias Websocket#<< for example:
|
66
|
-
#
|
67
|
-
# # @request[:io] contains the Websockets Protocol instance
|
68
|
-
# @request[:io] << data
|
69
|
-
#
|
70
|
-
# do NOT use Websocket#write (which writes the data directly, bypassing the protocol).
|
71
|
-
#
|
72
|
-
# We can also leverage the fact that the Http response can be used to send Websocket data.
|
73
|
-
#
|
74
|
-
# @response << data
|
75
|
-
(@___ws_io ||= @request[:io]) << data
|
76
|
-
end
|
77
|
-
|
78
|
-
# Send messages directly to a specific Websocket.
|
79
|
-
#
|
80
|
-
# This implementation is limited to a single process on a single server.
|
81
|
-
# Consider using Redis for a scalable implementation.
|
82
|
-
def unicast id, data
|
83
|
-
::Iodine::Http::Websockets.unicast id, data
|
84
|
-
end
|
85
|
-
# Broadcast to all Websockets, except self.
|
86
|
-
#
|
87
|
-
# This implementation is limited to a single process on a single server.
|
88
|
-
# Consider using Redis for a scalable implementation.
|
89
|
-
def broadcast data
|
90
|
-
::Iodine::Http::Websockets.broadcast data, @request[:io]
|
91
|
-
end
|
92
|
-
# Closes the connection
|
93
|
-
def close
|
94
|
-
# @request[:io] contains the Websockets Protocol instance
|
95
|
-
@request[:io].go_away
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
|
@@ -1,336 +0,0 @@
|
|
1
|
-
module Iodine
|
2
|
-
module Http
|
3
|
-
class Websockets < ::Iodine::Protocol
|
4
|
-
# continue to initialize the websocket protocol.
|
5
|
-
def on_open
|
6
|
-
@handler = @options[:handler]
|
7
|
-
@ws_extentions = @options[:ext]
|
8
|
-
@options[:request][:io] = self
|
9
|
-
@parser = {body: String.new, stage: 0, step: 0, mask_key: [], len_bytes: []}
|
10
|
-
set_timeout self.class.default_timeout
|
11
|
-
@handler.on_open if @handler.respond_to? :on_open
|
12
|
-
end
|
13
|
-
# parse and handle messages.
|
14
|
-
def on_message data
|
15
|
-
extract_message StringIO.new(data)
|
16
|
-
end
|
17
|
-
# handle broadcasts.
|
18
|
-
def on_broadcast data
|
19
|
-
@locker.synchronize { @handler.on_broadcast(data) } if @handler.respond_to? :on_broadcast
|
20
|
-
end
|
21
|
-
# cleanup after closing.
|
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
|
-
# a politer disconnection.
|
31
|
-
def go_away
|
32
|
-
write CLOSE_FRAME
|
33
|
-
close
|
34
|
-
end
|
35
|
-
|
36
|
-
# a politer disconnection during shutdown.
|
37
|
-
def on_shutdown
|
38
|
-
@handler.on_shutdown if @handler.respond_to?(:on_shutdown)
|
39
|
-
go_away
|
40
|
-
end
|
41
|
-
|
42
|
-
# allow Http responses to be used for sending Websocket data.
|
43
|
-
def send_response response, finish = false
|
44
|
-
body = response.extract_body
|
45
|
-
send_data body.read
|
46
|
-
end
|
47
|
-
alias :stream_response :send_response
|
48
|
-
|
49
|
-
# Sends the data as one (or more) Websocket frames.
|
50
|
-
#
|
51
|
-
# Use THIS method to send data using the Websocket protocol.
|
52
|
-
# Using {Iodine::Protocol#write} will bypass the Websocket data framing and send the raw data, breaking the connection.
|
53
|
-
def send_data data, op_code = nil, fin = true, ext = 0
|
54
|
-
return false if !data || data.empty?
|
55
|
-
return false if @io.closed?
|
56
|
-
data = data.dup # needed?
|
57
|
-
unless op_code # apply extenetions to the message as a whole
|
58
|
-
op_code = (data.encoding == ::Encoding::UTF_8 ? 1 : 2)
|
59
|
-
@ws_extentions.each { |ex| ext |= ex.edit_message data } if @ws_extentions
|
60
|
-
end
|
61
|
-
byte_size = data.bytesize
|
62
|
-
if byte_size > (FRAME_SIZE_LIMIT+2)
|
63
|
-
# sections = byte_size/FRAME_SIZE_LIMIT + (byte_size%FRAME_SIZE_LIMIT ? 1 : 0)
|
64
|
-
send_data( data.slice!( 0...FRAME_SIZE_LIMIT ), op_code, data.empty?, ext) && (ext = op_code = 0) until data.empty?
|
65
|
-
return true # avoid sending an empty frame.
|
66
|
-
end
|
67
|
-
@ws_extentions.each { |ex| ext |= ex.edit_frame data } if @ws_extentions
|
68
|
-
header = ( (fin ? 0b10000000 : 0) | (op_code & 0b00001111) | ext).chr.force_encoding(::Encoding::ASCII_8BIT)
|
69
|
-
|
70
|
-
if byte_size < 125
|
71
|
-
header << byte_size.chr
|
72
|
-
elsif byte_size.bit_length <= 16
|
73
|
-
header << 126.chr
|
74
|
-
header << [byte_size].pack('S>'.freeze)
|
75
|
-
else
|
76
|
-
header << 127.chr
|
77
|
-
header << [byte_size].pack('Q>'.freeze)
|
78
|
-
end
|
79
|
-
write header
|
80
|
-
write(data) && true
|
81
|
-
end
|
82
|
-
alias :<< :send_data
|
83
|
-
|
84
|
-
# Sends a ping and calles the :on_ping callback (if exists).
|
85
|
-
def ping
|
86
|
-
write(PING_FRAME) && ( (@handler.respond_to?(:on_ping) && @handler.on_ping) || true)
|
87
|
-
end
|
88
|
-
# Sends an empty pong.
|
89
|
-
def pong
|
90
|
-
write PONG_FRAME
|
91
|
-
end
|
92
|
-
|
93
|
-
# Broadcasts data to ALL the websocket connections EXCEPT the once specified (if specified).
|
94
|
-
#
|
95
|
-
# Data broadcasted will be recived by the websocket handler it's #on_broadcast(ws) method (if exists).
|
96
|
-
#
|
97
|
-
# Accepts:
|
98
|
-
#
|
99
|
-
# data:: One object of data. Usually a Hash, Array, String or a JSON formatted object.
|
100
|
-
# ignore_io (optional):: The IO to be ignored by the broadcast. Usually the broadcaster's IO.
|
101
|
-
#
|
102
|
-
def self.broadcast data, ignore_io = nil
|
103
|
-
if ignore_io
|
104
|
-
ig_id = ignore_io.object_id
|
105
|
-
each {|io| Iodine.run io, data, &broadcast_proc unless io.object_id == ig_id}
|
106
|
-
else
|
107
|
-
each {|io| Iodine.run io, data, &broadcast_proc }
|
108
|
-
end
|
109
|
-
true
|
110
|
-
end
|
111
|
-
|
112
|
-
# Broadcasts the data to all the listening websockets, except self. See {::Iodine::Http::Websockets.broadcast}
|
113
|
-
def broadcast data
|
114
|
-
self.class.broadcast data, self
|
115
|
-
end
|
116
|
-
|
117
|
-
# Unicast data to a specific websocket connection (ONLY the connection specified).
|
118
|
-
#
|
119
|
-
# Data broadcasted will be recived by the websocket handler it's #on_broadcast(ws) method (if exists).
|
120
|
-
# Accepts:
|
121
|
-
# uuid:: the UUID of the websocket connection recipient.
|
122
|
-
# data:: the data to be sent.
|
123
|
-
#
|
124
|
-
# @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).
|
125
|
-
def self.unicast id, data
|
126
|
-
return false unless id && data
|
127
|
-
each {|io| next unless io.id == id; Iodine.run io, data, &broadcast_proc; return true}
|
128
|
-
false
|
129
|
-
end
|
130
|
-
# @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}
|
131
|
-
def unicast id, data
|
132
|
-
self.class.unicast id, data
|
133
|
-
end
|
134
|
-
|
135
|
-
def self.handshake request, response, handler
|
136
|
-
# review handshake (version, extentions)
|
137
|
-
# should consider adopting the websocket gem for handshake and framing:
|
138
|
-
# https://github.com/imanel/websocket-ruby
|
139
|
-
# http://www.rubydoc.info/github/imanel/websocket-ruby
|
140
|
-
return refuse response unless handler || handler == true
|
141
|
-
io = request[:io]
|
142
|
-
response.keep_alive = true
|
143
|
-
response.status = 101
|
144
|
-
response['upgrade'.freeze] = 'websocket'.freeze
|
145
|
-
response['content-length'.freeze] = '0'.freeze
|
146
|
-
response['connection'.freeze] = 'Upgrade'.freeze
|
147
|
-
response['sec-websocket-version'.freeze] = '13'.freeze
|
148
|
-
# Note that the client is only offering to use any advertised extensions
|
149
|
-
# and MUST NOT use them unless the server indicates that it wishes to use the extension.
|
150
|
-
ws_extentions = []
|
151
|
-
ext = []
|
152
|
-
request['sec-websocket-extensions'.freeze].to_s.split(/[\s]*[,][\s]*/.freeze).each {|ex| ex = ex.split(/[\s]*;[\s]*/.freeze); ( ( tmp = SUPPORTED_EXTENTIONS[ ex[0] ].call(ex[1..-1]) ) && (ws_extentions << tmp) && (ext << tmp.name) ) if SUPPORTED_EXTENTIONS[ ex[0] ] }
|
153
|
-
ext.compact!
|
154
|
-
if ext.any?
|
155
|
-
response['sec-websocket-extensions'.freeze] = ext.join(', '.freeze)
|
156
|
-
else
|
157
|
-
ws_extentions = nil
|
158
|
-
end
|
159
|
-
response['sec-websocket-accept'.freeze] = Digest::SHA1.base64digest(request['sec-websocket-key'.freeze] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'.freeze)
|
160
|
-
response.session
|
161
|
-
# Iodine.log "#{@request[:client_ip]} [#{Time.now.utc}] - #{@connection.object_id} Upgraded HTTP to WebSockets.\n"
|
162
|
-
response.finish
|
163
|
-
self.new(io.io, handler: handler, request: request, ext: ws_extentions)
|
164
|
-
return true
|
165
|
-
end
|
166
|
-
|
167
|
-
# Gets the new connection timeout in seconds. Whenever this timeout is reached, a ping will be sent. Defaults to 40 (seconds).
|
168
|
-
def self.default_timeout
|
169
|
-
@default_timeout
|
170
|
-
end
|
171
|
-
# Sets the new connection timeout in seconds. Whenever this timeout is reached, a ping will be sent. Defaults to 40 (seconds).
|
172
|
-
def self.default_timeout= val
|
173
|
-
@default_timeout = val
|
174
|
-
end
|
175
|
-
# Sets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
|
176
|
-
#
|
177
|
-
# Although memory will be allocated for the latest TCP/IP frame,
|
178
|
-
# this allows the websocket to disconnect if the incoming expected message size exceeds the allowed maximum size.
|
179
|
-
#
|
180
|
-
# If the message size limit is exceeded, the disconnection will be immidiate as an attack will be assumed. The protocol's normal disconnect sequesnce will be discarded.
|
181
|
-
def self.message_size_limit=val
|
182
|
-
@message_size_limit = val
|
183
|
-
end
|
184
|
-
# Gets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
|
185
|
-
def self.message_size_limit
|
186
|
-
@message_size_limit ||= 0
|
187
|
-
end
|
188
|
-
|
189
|
-
protected
|
190
|
-
FRAME_SIZE_LIMIT = 17_895_697
|
191
|
-
SUPPORTED_EXTENTIONS = {}
|
192
|
-
CLOSE_FRAME = "\x88\x00".freeze
|
193
|
-
PONG_FRAME = "\x8A\x00".freeze
|
194
|
-
PING_FRAME = "\x89\x00".freeze
|
195
|
-
@default_timeout = 40
|
196
|
-
|
197
|
-
def self.broadcast_proc
|
198
|
-
@broadcast_proc ||= Proc.new {|io, data| io.on_broadcast data }
|
199
|
-
end
|
200
|
-
|
201
|
-
def self.refuse response
|
202
|
-
response.status = 400
|
203
|
-
response['sec-websocket-extensions'.freeze] = SUPPORTED_EXTENTIONS.keys.join(', '.freeze)
|
204
|
-
response['sec-websocket-version'.freeze] = '13'.freeze
|
205
|
-
false
|
206
|
-
end
|
207
|
-
|
208
|
-
# parse the message and send it to the handler
|
209
|
-
#
|
210
|
-
# test: frame = ["819249fcd3810b93b2fb69afb6e62c8af3e83adc94ee2ddd"].pack("H*").bytes; parser[:stage] = 0; parser = {}
|
211
|
-
# accepts:
|
212
|
-
# data:: an IO object (usually a StringIO object)
|
213
|
-
def extract_message data
|
214
|
-
parser = @parser
|
215
|
-
until data.eof?
|
216
|
-
if parser[:stage] == 0
|
217
|
-
tmp = data.getbyte
|
218
|
-
return unless tmp
|
219
|
-
parser[:fin] = tmp[7] == 1
|
220
|
-
parser[:rsv1] = tmp[6] == 1
|
221
|
-
parser[:rsv2] = tmp[5] == 1
|
222
|
-
parser[:rsv3] = tmp[4] == 1
|
223
|
-
parser[:op_code] = tmp & 0b00001111
|
224
|
-
parser[:p_op_code] ||= tmp & 0b00001111
|
225
|
-
parser[:stage] += 1
|
226
|
-
end
|
227
|
-
if parser[:stage] == 1
|
228
|
-
tmp = data.getbyte
|
229
|
-
return unless tmp
|
230
|
-
parser[:mask] = tmp[7]
|
231
|
-
parser[:mask_key].clear
|
232
|
-
parser[:len] = tmp & 0b01111111
|
233
|
-
parser[:len_bytes].clear
|
234
|
-
parser[:stage] += 1
|
235
|
-
end
|
236
|
-
if parser[:stage] == 2
|
237
|
-
tmp = 0
|
238
|
-
tmp = 2 if parser[:len] == 126
|
239
|
-
tmp = 8 if parser[:len] == 127
|
240
|
-
while parser[:len_bytes].length < tmp
|
241
|
-
parser[:len_bytes] << data.getbyte
|
242
|
-
return parser[:len_bytes].pop unless parser[:len_bytes].last
|
243
|
-
end
|
244
|
-
parser[:len] = merge_bytes( parser[:len_bytes] ) if tmp > 0
|
245
|
-
parser[:step] = 0
|
246
|
-
parser[:stage] += 1
|
247
|
-
return false unless review_message_size
|
248
|
-
end
|
249
|
-
if parser[:stage] == 3 && parser[:mask] == 1
|
250
|
-
until parser[:mask_key].length == 4
|
251
|
-
parser[:mask_key] << data.getbyte
|
252
|
-
return parser[:mask_key].pop unless parser[:mask_key].last
|
253
|
-
end
|
254
|
-
parser[:stage] += 1
|
255
|
-
elsif parser[:stage] == 3 && parser[:mask] != 1
|
256
|
-
parser[:stage] += 1
|
257
|
-
end
|
258
|
-
if parser[:stage] == 4
|
259
|
-
if parser[:body].bytesize < parser[:len]
|
260
|
-
tmp = data.read(parser[:len] - parser[:body].bytesize)
|
261
|
-
return unless tmp
|
262
|
-
parser[:body] << tmp
|
263
|
-
end
|
264
|
-
if parser[:body].bytesize >= parser[:len]
|
265
|
-
tmp = -1
|
266
|
-
parser[:body] = parser[:body].bytes.map! {|b| (b ^ parser[:mask_key][tmp = (tmp + 1)%4]) } .pack('C*'.freeze) if parser[:mask] == 1
|
267
|
-
# parser[:body].bytesize.times {|i| parser[:body][i] = (parser[:body][i].ord ^ parser[:mask_key][i % 4]).chr} if parser[:mask] == 1
|
268
|
-
parser[:stage] = 99
|
269
|
-
end
|
270
|
-
end
|
271
|
-
complete_frame if parser[:stage] == 99
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
# takes and Array of bytes and combines them to an int(16 Bit), 32Bit or 64Bit number
|
276
|
-
def merge_bytes bytes
|
277
|
-
return 0 unless bytes.any?
|
278
|
-
return bytes.pop if bytes.length == 1
|
279
|
-
bytes.pop ^ (merge_bytes(bytes) << 8)
|
280
|
-
end
|
281
|
-
|
282
|
-
# handles the completed frame and sends a message to the handler once all the data has arrived.
|
283
|
-
def complete_frame
|
284
|
-
parser = @parser
|
285
|
-
@ws_extentions.each {|ex| ex.parse_frame(parser) } if @ws_extentions
|
286
|
-
|
287
|
-
case parser[:op_code]
|
288
|
-
when 9 # ping
|
289
|
-
# handle parser[:op_code] == 9 (ping)
|
290
|
-
::Iodine.run { send_data parser[:body], 10 }
|
291
|
-
parser[:p_op_code] = nil if parser[:p_op_code] == 9
|
292
|
-
when 10 #pong
|
293
|
-
# handle parser[:op_code] == 10 (pong)
|
294
|
-
parser[:p_op_code] = nil if parser[:p_op_code] == 10
|
295
|
-
when 8 # close
|
296
|
-
# handle parser[:op_code] == 8 (close)
|
297
|
-
write( CLOSE_FRAME )
|
298
|
-
close
|
299
|
-
parser[:p_op_code] = nil if parser[:p_op_code] == 8
|
300
|
-
else
|
301
|
-
parser[:message] ? ((parser[:message] << parser[:body]) && parser[:body].clear) : ((parser[:message] = parser[:body]) && parser[:body] = String.new)
|
302
|
-
# handle parser[:op_code] == 0 / fin == false (continue a frame that hasn't ended yet)
|
303
|
-
if parser[:fin]
|
304
|
-
@ws_extentions.each {|ex| ex.parse_message(parser) } if @ws_extentions
|
305
|
-
Iodine::Http::Request.make_utf8! parser[:message] if parser[:p_op_code] == 1
|
306
|
-
@handler.on_message parser[:message]
|
307
|
-
parser[:message] = nil
|
308
|
-
parser[:p_op_code] = nil
|
309
|
-
end
|
310
|
-
end
|
311
|
-
parser[:stage] = 0
|
312
|
-
parser[:body].clear
|
313
|
-
parser[:step] = 0
|
314
|
-
parser[:mask_key].clear
|
315
|
-
parser[:p_op_code] = nil
|
316
|
-
end
|
317
|
-
#reviews the message size and closes the connection if expected message size is over the allowed limit.
|
318
|
-
def review_message_size
|
319
|
-
if ( self.class.message_size_limit.to_i > 0 ) && ( ( @parser[:len] + (@parser[:message] ? @parser[:message].bytesize : 0) ) > self.class.message_size_limit.to_i )
|
320
|
-
close
|
321
|
-
@parser.delete :message
|
322
|
-
@parser[:step] = 0
|
323
|
-
@parser[:body].clear
|
324
|
-
@parser[:mask_key].clear
|
325
|
-
Iodine.warn "Websocket message above limit's set - closing connection."
|
326
|
-
return false
|
327
|
-
end
|
328
|
-
true
|
329
|
-
end
|
330
|
-
|
331
|
-
|
332
|
-
end
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
|
data/lib/iodine/io.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
module Iodine
|
2
|
-
|
3
|
-
public
|
4
|
-
|
5
|
-
# @return [Time] Gets the last time at which the IO Reactor was last active (last "tick").
|
6
|
-
def time
|
7
|
-
@time
|
8
|
-
end
|
9
|
-
# Replaces (or creates) an IO's protocol object.
|
10
|
-
#
|
11
|
-
# Accepts 2 arguments, in the following order:
|
12
|
-
#
|
13
|
-
# io:: the raw IO object.
|
14
|
-
# protocol:: a protocol instance - should be an instance of a class inheriting from {Iodine::Protocol}. type will NOT be checked - but Iodine could break if there is a type mismatch.
|
15
|
-
# @return [Protocol]
|
16
|
-
def switch_protocol *args
|
17
|
-
@io_in << args
|
18
|
-
args[1]
|
19
|
-
end
|
20
|
-
|
21
|
-
# @return [Array] Returns an Array with all the currently active connection's Protocol instances.
|
22
|
-
def to_a
|
23
|
-
@ios.values
|
24
|
-
end
|
25
|
-
|
26
|
-
|
27
|
-
protected
|
28
|
-
|
29
|
-
# internal helper methods and classes.
|
30
|
-
module Base
|
31
|
-
# the server listener Protocol.
|
32
|
-
class Listener < ::Iodine::Protocol
|
33
|
-
def on_open
|
34
|
-
@protocol = Iodine.protocol
|
35
|
-
@ssl = Iodine.ssl
|
36
|
-
@accept_proc = @protocol.method(:accept)
|
37
|
-
end
|
38
|
-
def call
|
39
|
-
begin
|
40
|
-
n_io = nil
|
41
|
-
loop do
|
42
|
-
n_io = @io.accept_nonblock
|
43
|
-
# @protocol.accept(n_io, @ssl)
|
44
|
-
Iodine.run n_io, @ssl, &(@accept_proc)
|
45
|
-
end
|
46
|
-
rescue Errno::EWOULDBLOCK => e
|
47
|
-
|
48
|
-
rescue => e
|
49
|
-
n_io.close if n_io && !n_io.closed?
|
50
|
-
@stop = true
|
51
|
-
raise e
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|