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.
- checksums.yaml +4 -4
- data/lib/rakie.rb +15 -1
- data/lib/rakie/channel.rb +57 -12
- data/lib/rakie/event.rb +70 -35
- data/lib/rakie/http.rb +5 -0
- data/lib/rakie/http_proto.rb +197 -0
- data/lib/rakie/http_server.rb +134 -0
- data/lib/rakie/log.rb +96 -0
- data/lib/rakie/proto.rb +63 -0
- data/lib/rakie/simple_server.rb +7 -11
- data/lib/rakie/tcp_channel.rb +19 -3
- data/lib/rakie/tcp_server_channel.rb +32 -16
- data/lib/rakie/version.rb +11 -0
- data/lib/rakie/websocket.rb +138 -0
- data/lib/rakie/websocket_proto.rb +286 -0
- data/lib/rakie/websocket_server.rb +89 -0
- data/test/test_rakie.rb +8 -3
- metadata +13 -4
@@ -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"
|