rakie 0.0.2 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|