rakie 0.0.2 → 0.0.8

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.
@@ -0,0 +1,11 @@
1
+ module Rakie
2
+ VERSION = [0, 0, 8]
3
+
4
+ def self.version_s
5
+ VERSION.join('.')
6
+ end
7
+
8
+ def self.full_version_s
9
+ "#{NAME} v#{self.version_s}"
10
+ end
11
+ end
@@ -0,0 +1,138 @@
1
+ module Rakie
2
+ class Websocket
3
+ attr_accessor :delegate, :client_side
4
+ attr_reader :channel
5
+
6
+ # @param [Rakie::TCPChannel] channel
7
+ def initialize(delegate=nil, channel=nil)
8
+ @delegate = delegate
9
+
10
+ if channel == nil
11
+ channel = TCPChannel.new('127.0.0.1', 10086, self)
12
+ end
13
+
14
+ @channel = channel
15
+
16
+ # @type [WebsocketMessage]
17
+ @recv_message = WebsocketMessage.new
18
+
19
+ # @type [Array<WebsocketMessage>]
20
+ @send_messages = []
21
+ @client_side = true
22
+ end
23
+
24
+ def on_recv(channel, data)
25
+ Log.debug("Rakie::Websocket recv: #{data}")
26
+
27
+ # @type [WebsocketMessage] request
28
+ message = @recv_message
29
+
30
+ if message.parse_status == ParseStatus::COMPLETE
31
+ message = WebsocketMessage.new
32
+ @recv_message = message
33
+ end
34
+
35
+ len = message.parse(data)
36
+
37
+ Log.debug("Rakie::Websocket receive message: #{message.to_s} parse with #{len}")
38
+
39
+ if message.parse_status == ParseStatus::COMPLETE
40
+ response = WebsocketMessage.new
41
+
42
+ if message.op_code == WebsocketMessage::OP_PING
43
+ response.fin = true
44
+ response.op_code = WebsocketMessage::OP_PONG
45
+ response.payload = "pong"
46
+
47
+ elsif message.op_code == WebsocketMessage::OP_PONG
48
+ response.fin = true
49
+ response.op_code = WebsocketMessage::OP_PING
50
+ response.payload = "ping"
51
+
52
+ elsif @delegate
53
+ @delegate.on_message(self, message.payload)
54
+ return len
55
+
56
+ else
57
+ response.fin = true
58
+ response.op_code = WebsocketMessage::OP_TEXT
59
+ response.payload = "Rakie!"
60
+ end
61
+
62
+ response_data = response.to_s
63
+
64
+ Log.debug("Rakie::Websocket response: #{response_data}")
65
+
66
+ channel.write(response_data) # Response data
67
+
68
+ elsif message.parse_status == ParseStatus::ERROR
69
+ channel.close
70
+
71
+ Log.debug("Rakie::Websocket: Illegal message")
72
+ return 0
73
+ end
74
+
75
+ return len
76
+ end
77
+
78
+ def on_send(channel)
79
+ # @type [WebsocketMessage]
80
+ last_message = @send_messages.shift
81
+
82
+ if last_message
83
+ if last_message.op_code == WebsocketMessage::OP_CLOSE
84
+ Log.debug("Rakie::Websocket: send finish and close channel")
85
+ channel.close
86
+ end
87
+ end
88
+ end
89
+
90
+ def on_close(channel)
91
+ if @delegate
92
+ @delegate.on_disconnect(self)
93
+ end
94
+ end
95
+
96
+ def send(message, is_binary=false)
97
+ ws_message = WebsocketMessage.new
98
+ ws_message.fin = true
99
+ ws_message.op_code = WebsocketMessage::OP_TEXT
100
+ ws_message.payload = message
101
+
102
+ if is_binary
103
+ ws_message.op_code = WebsocketMessage::OP_BIN
104
+ end
105
+
106
+ if @client_side
107
+ ws_message.mask = true
108
+ ws_message.refresh_masking
109
+ end
110
+
111
+ send_message = ws_message.to_s
112
+ @send_messages << ws_message
113
+
114
+ Log.debug("Rakie::Websocket send: #{send_message}")
115
+
116
+ @channel.write(send_message) # Response data
117
+ end
118
+
119
+ def close
120
+ ws_message = WebsocketMessage.new
121
+ ws_message.fin = true
122
+ ws_message.op_code = WebsocketMessage::OP_CLOSE
123
+
124
+ if @client_side
125
+ ws_message.mask = true
126
+ ws_message.refresh_masking
127
+ end
128
+
129
+ ws_message.payload = "close"
130
+
131
+ send_message = ws_message.to_s
132
+
133
+ Log.debug("Rakie::Websocket send close: #{send_message}")
134
+
135
+ @channel.write(send_message) # Response data
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,286 @@
1
+ module Rakie
2
+ class WebsocketBasicMessage
3
+ include Proto
4
+
5
+ FLAG_FIN = 1 << 7
6
+
7
+ OP_CONTINUE = 0x0
8
+ OP_TEXT = 0x1
9
+ OP_BIN = 0x2
10
+ OP_CLOSE = 0x8
11
+ OP_PING = 0x9
12
+ OP_PONG = 0xA
13
+
14
+ FLAG_MASK = 1 << 7
15
+
16
+ PARSE_OPERATION = PARSE_BEGIN
17
+ PARSE_LEN = 1
18
+ PARSE_EXT_LEN = 2
19
+ PARSE_MASKING = 3
20
+ PARSE_PAYLOAD = 4
21
+
22
+ attr_accessor :op_code, :mask, :length, :payload
23
+ attr_writer :fin
24
+
25
+ def initialize
26
+ @fin = false
27
+ @op_code = 0x0
28
+ @mask = false
29
+ @masking = []
30
+ @length = 0
31
+ @long_ext = false
32
+ @payload = ''
33
+ end
34
+
35
+ def fin?
36
+ @fin
37
+ end
38
+
39
+ # @param [String] source
40
+ def deserialize(source)
41
+ current_state = parse_state
42
+
43
+ case current_state
44
+ when PARSE_OPERATION
45
+ return parse_source_operation(source)
46
+
47
+ when PARSE_LEN
48
+ return parse_source_len(source)
49
+
50
+ when PARSE_EXT_LEN
51
+ return parse_source_ext_len(source)
52
+
53
+ when PARSE_MASKING
54
+ return parse_source_masking(source)
55
+
56
+ when PARSE_PAYLOAD
57
+ return parse_source_payload(source)
58
+ end
59
+ end
60
+
61
+ def serialize
62
+ data = ''
63
+
64
+ data += pack_source_operation
65
+ data += pack_source_len
66
+
67
+ if @mask
68
+ data += pack_source_masking
69
+ data += pack_source_masked_payload
70
+
71
+ return data
72
+ end
73
+
74
+ data += @payload
75
+
76
+ return data
77
+ end
78
+ end
79
+
80
+ class WebsocketMessage < WebsocketBasicMessage
81
+ # @param [String] source
82
+ def parse_source_operation(source)
83
+ offset = parse_offset
84
+
85
+ if source.length >= 1 + offset
86
+ byte = source[offset]
87
+ data = byte.unpack('C')[0]
88
+
89
+ if data & FLAG_FIN > 0
90
+ @fin = true
91
+ end
92
+
93
+ i = 3
94
+ code = 0x0
95
+
96
+ while i >= 0
97
+ code |= data & (1 << i)
98
+ i -= 1
99
+ end
100
+
101
+ @op_code = code
102
+
103
+ self.parse_state = PARSE_LEN
104
+ self.parse_offset = offset + 1
105
+
106
+ Log.debug("Rakie::WebsocketMessage parse source operation ok")
107
+ return ParseStatus::CONTINUE
108
+ end
109
+
110
+ return ParseStatus::PENDING
111
+ end
112
+
113
+ # @param [String] source
114
+ def parse_source_len(source)
115
+ offset = parse_offset
116
+
117
+ if source.length >= 1 + offset
118
+ byte = source[offset]
119
+ data = byte.unpack('C')[0]
120
+
121
+ if data & FLAG_MASK > 0
122
+ @mask = true
123
+ end
124
+
125
+ len = data & ~FLAG_MASK
126
+
127
+ Log.debug("Rakie::WebsocketMessage len: #{len}")
128
+
129
+ if len <= 125
130
+ @length = len
131
+
132
+ if @mask
133
+ self.parse_state = PARSE_MASKING
134
+ self.parse_offset = offset + 1
135
+
136
+ Log.debug("Rakie::WebsocketMessage parse source len ok")
137
+ return ParseStatus::CONTINUE
138
+ end
139
+
140
+ self.parse_state = PARSE_PAYLOAD
141
+ self.parse_offset = offset + 1
142
+
143
+ Log.debug("Rakie::WebsocketMessage parse source len ok")
144
+ return ParseStatus::CONTINUE
145
+ end
146
+
147
+ if len == 127
148
+ @long_ext = true
149
+ end
150
+
151
+ self.parse_state = PARSE_EXT_LEN
152
+ self.parse_offset = offset + 1
153
+
154
+ Log.debug("Rakie::WebsocketMessage parse source len ok")
155
+ return ParseStatus::CONTINUE
156
+ end
157
+
158
+ return ParseStatus::PENDING
159
+ end
160
+
161
+ # @param [String] source
162
+ def parse_source_ext_len(source)
163
+ offset = parse_offset
164
+ ext_len_size = 2
165
+ byte_format = 'S'
166
+
167
+ if @long_ext
168
+ ext_len_size = 8
169
+ byte_format = 'Q'
170
+ end
171
+
172
+ if source.length >= ext_len_size + offset
173
+ bytes = source[offset .. (offset + ext_len_size - 1)]
174
+ @length = bytes.unpack(byte_format + '>')[0]
175
+
176
+ if @mask
177
+ self.parse_state = PARSE_MASKING
178
+ self.parse_offset = offset + ext_len_size
179
+
180
+ Log.debug("Rakie::WebsocketMessage parse source ext len ok")
181
+ return ParseStatus::CONTINUE
182
+ end
183
+
184
+ self.parse_state = PARSE_PAYLOAD
185
+ self.parse_offset = offset + ext_len_size
186
+
187
+ Log.debug("Rakie::WebsocketMessage parse source ext len ok")
188
+ return ParseStatus::CONTINUE
189
+ end
190
+
191
+ return ParseStatus::PENDING
192
+ end
193
+
194
+ # @param [String] source
195
+ def parse_source_masking(source)
196
+ offset = parse_offset
197
+
198
+ if source.length >= 4 + offset
199
+ bytes = source[offset .. (offset + 4 - 1)]
200
+ @masking = bytes.unpack('C*')
201
+
202
+ self.parse_state = PARSE_PAYLOAD
203
+ self.parse_offset = offset + 4
204
+
205
+ Log.debug("Rakie::WebsocketMessage parse source masking ok")
206
+ return ParseStatus::CONTINUE
207
+ end
208
+
209
+ return ParseStatus::PENDING
210
+ end
211
+
212
+ # @param [String] source
213
+ def parse_source_payload(source)
214
+ offset = parse_offset
215
+
216
+ if source.length >= @length + offset
217
+ bytes = source[offset .. (offset + @length - 1)]
218
+
219
+ if @mask
220
+ bytes_list = bytes.unpack('C*')
221
+ masking = @masking
222
+ bytes_list_unmasked = bytes_list.map.with_index { |b, i| b ^ masking[i % 4] }
223
+
224
+ @payload = bytes_list_unmasked.pack('C*')
225
+
226
+ self.parse_state = PARSE_OPERATION
227
+ self.parse_offset = offset + @length
228
+
229
+ Log.debug("Rakie::WebsocketMessage parse source masked payload ok")
230
+ return ParseStatus::COMPLETE
231
+ end
232
+
233
+ @payload = bytes
234
+
235
+ self.parse_state = PARSE_OPERATION
236
+ self.parse_offset = offset + @length
237
+
238
+ Log.debug("Rakie::WebsocketMessage parse source payload ok")
239
+ return ParseStatus::COMPLETE
240
+ end
241
+
242
+ return ParseStatus::PENDING
243
+ end
244
+
245
+ def pack_source_operation
246
+ fin_bit = @fin ? 1 : 0
247
+ return [(fin_bit << 7) + @op_code].pack('C')
248
+ end
249
+
250
+ def pack_source_len
251
+ mask_bit = @mask ? 1 : 0
252
+
253
+ if @payload.length < 126
254
+ return [(mask_bit << 7) + @payload.length].pack('C')
255
+
256
+ elsif @payload.length < 65536
257
+ return [(mask_bit << 7) + 126, @payload.length].pack('CS>')
258
+ end
259
+
260
+ return [(mask_bit << 7) + 127, @payload.length].pack('CQ>')
261
+ end
262
+
263
+ def pack_source_masking
264
+ return @masking.pack('C*')
265
+ end
266
+
267
+ def pack_source_masked_payload
268
+ masking = @masking
269
+
270
+ if masking.empty?
271
+ return ''
272
+ end
273
+
274
+ bytes_list = @payload.unpack('C*')
275
+ bytes_list_masked = bytes_list.map.with_index { |b, i| b ^ masking[i % 4] }
276
+
277
+ return bytes_list_masked.pack('C*')
278
+ end
279
+
280
+ def refresh_masking
281
+ masking = []
282
+ 4.times { masking << rand(1 .. 255) }
283
+ @masking = masking
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,89 @@
1
+ module Rakie
2
+ class WebsocketServer < Websocket
3
+ # @param [Rakie::HttpServer] http_server
4
+ def initialize(host: '127.0.0.1', port: 10086, delegate: nil, http_server: nil)
5
+ @delegate = delegate
6
+
7
+ if http_server == nil
8
+ http_server = HttpServer.new(host: host, port: port)
9
+ end
10
+
11
+ @host = http_server.host
12
+ @port = http_server.port
13
+
14
+ http_server.opt[:websocket_delegate] = self
15
+ @channel = http_server.channel
16
+
17
+ # @type [Array<WebsocketClient>]
18
+ @clients = {}
19
+ end
20
+
21
+ def on_accept(channel)
22
+ ws_client = Websocket.new(@delegate, channel)
23
+ ws_client.client_side = false
24
+
25
+ channel.delegate = self
26
+
27
+ if @delegate
28
+ @delegate.on_connect(ws_client)
29
+ end
30
+
31
+ @clients[channel] = ws_client
32
+ Log.debug("Rakie::WebsocketServer accept client: #{ws_client}")
33
+ end
34
+
35
+ # @param [HttpRequest] request
36
+ # @param [HttpResponse] response
37
+ # @return bool
38
+ def upgrade(request, response)
39
+ if websocket_key = request.headers["sec-websocket-key"]
40
+ digest_key = Digest::SHA1.base64digest(websocket_key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
41
+
42
+ response.head.status = 101
43
+ response.head.message = 'Switching Protocols'
44
+ response.headers["connection"] = "upgrade"
45
+ response.headers["upgrade"] = "websocket"
46
+ response.headers["sec-websocket-accept"] = digest_key
47
+ end
48
+ end
49
+
50
+ def on_recv(channel, data)
51
+ client = @clients[channel]
52
+
53
+ if client
54
+ return client.on_recv(channel, data)
55
+ end
56
+
57
+ return data.length
58
+ end
59
+
60
+ def on_send(channel)
61
+ client = @clients[channel]
62
+
63
+ if client
64
+ client.on_send(channel)
65
+ end
66
+ end
67
+
68
+ def on_close(channel)
69
+ client = @clients[channel]
70
+
71
+ if client
72
+ client.on_close(channel)
73
+ end
74
+
75
+ @clients.delete(channel)
76
+ end
77
+
78
+ def send(message, is_binary=false); end
79
+
80
+ def close; end
81
+
82
+ def clients
83
+ @clients.values
84
+ end
85
+ end
86
+ end
87
+
88
+ require "digest"
89
+ require "base64"