tamashii-manager 0.1.7 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/Gemfile +0 -1
- data/exe/tamashii-manager +19 -16
- data/lib/tamashii/manager.rb +29 -11
- data/lib/tamashii/manager/authorization.rb +4 -4
- data/lib/tamashii/manager/authorizator.rb +10 -0
- data/lib/tamashii/manager/authorizator/token.rb +4 -4
- data/lib/tamashii/manager/channel.rb +24 -11
- data/lib/tamashii/manager/channel_pool.rb +10 -13
- data/lib/tamashii/manager/client.rb +42 -75
- data/lib/tamashii/manager/client_manager.rb +33 -0
- data/lib/tamashii/manager/config.rb +43 -3
- data/lib/tamashii/manager/error.rb +10 -0
- data/lib/tamashii/manager/error/authorization_error.rb +10 -0
- data/lib/tamashii/manager/handler.rb +10 -0
- data/lib/tamashii/manager/handler/broadcaster.rb +8 -4
- data/lib/tamashii/manager/server.rb +10 -67
- data/lib/tamashii/manager/subscription.rb +23 -0
- data/lib/tamashii/manager/version.rb +1 -1
- data/tamashii-manager.gemspec +1 -0
- metadata +23 -7
- data/lib/tamashii/manager/clients.rb +0 -23
- data/lib/tamashii/manager/connection.rb +0 -23
- data/lib/tamashii/manager/errors/authorization_error.rb +0 -6
- data/lib/tamashii/manager/stream.rb +0 -41
- data/lib/tamashii/manager/stream_event_loop.rb +0 -120
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b73434b81719979d45631fb55ee06dbc9c681dfb
|
4
|
+
data.tar.gz: 1271d4bbe0123c2bc284ba716958df9cf3e75f23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da6357c531d820f6f1147c7a8fcf972edaaaedba892db2a75b6553fb96eb09654e063666e4cc8ecaf6334ec5f7a27079ad29273643967bebd28a5c7d1ebe4f9c
|
7
|
+
data.tar.gz: ef266d3eaad776acbb73c66a6f1d3a5c99cc7bace32ec7d2e9b53541a7c13d1af17a26381e63752dd4ded688df3f542e32699c167878e779d57d6ce01a52d37b
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.1
|
data/Gemfile
CHANGED
data/exe/tamashii-manager
CHANGED
@@ -2,36 +2,37 @@
|
|
2
2
|
|
3
3
|
require 'rack'
|
4
4
|
require 'optparse'
|
5
|
-
require 'tamashii/manager/version'
|
6
|
-
require 'tamashii/manager/server'
|
7
5
|
require 'tamashii/manager'
|
8
6
|
|
9
7
|
options = {
|
10
8
|
Port: ENV['PORT'] || 3000,
|
11
|
-
Host:
|
9
|
+
Host: '0.0.0.0',
|
12
10
|
AccessLog: []
|
13
11
|
}
|
14
12
|
|
15
13
|
OptionParser.new do |opts|
|
16
|
-
opts.on(
|
14
|
+
opts.on('-v', '--version', 'Display Tamashii::Manager version') do
|
17
15
|
puts "Tamashii::Manager #{Tamashii::Manager::VERSION}"
|
18
16
|
exit
|
19
|
-
|
17
|
+
end
|
20
18
|
|
21
|
-
opts.on(
|
19
|
+
opts.on('-h', '--help') do
|
22
20
|
puts opts
|
23
21
|
exit
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
opts.
|
28
|
-
opts.on(
|
29
|
-
opts.on(
|
30
|
-
opts.on(
|
31
|
-
|
22
|
+
end
|
23
|
+
|
24
|
+
# rubocop:disable Metrics/LineLength
|
25
|
+
opts.separator ''
|
26
|
+
opts.on('-s', '--server SERVER', 'Run Tamashii::Manager server') { |name| handlers.unshift(name.to_s) }
|
27
|
+
opts.on('-o', '--host HOST', 'The listen on HOST (default: 0.0.0.0)') { |host| options[:Host] = host.to_s }
|
28
|
+
opts.on('-p', '--port PORT', 'The listen on PORT (default: 3000)') { |port| options[:Port] = port.to_i }
|
29
|
+
opts.on('-C', '--config FILE', 'The external configuration file') do |config|
|
30
|
+
if File.exist? config
|
32
31
|
require config
|
32
|
+
options[:Port] = Tamashii::Manager::Config.port
|
33
33
|
end
|
34
34
|
end
|
35
|
+
# rubocop:enable Metrics/LineLength
|
35
36
|
|
36
37
|
opts.parse! ARGV
|
37
38
|
end
|
@@ -40,10 +41,12 @@ begin
|
|
40
41
|
config = Tamashii::Manager::Config
|
41
42
|
case config.auth_type
|
42
43
|
when :token
|
43
|
-
|
44
|
+
# rubocop:disable Metrics/LineLength
|
45
|
+
raise LoadError, 'Token authorization require to set token' if config.token.nil?
|
46
|
+
# rubocop:enable Metrics/LineLength
|
44
47
|
end
|
45
48
|
|
46
|
-
Rack::Handler.default.run Tamashii::Manager
|
49
|
+
Rack::Handler.default.run Tamashii::Manager.server, options
|
47
50
|
rescue LoadError => e
|
48
51
|
# TODO: Improve error message
|
49
52
|
STDERR.puts e
|
data/lib/tamashii/manager.rb
CHANGED
@@ -1,23 +1,41 @@
|
|
1
|
-
|
2
|
-
require "tamashii/manager/version"
|
3
|
-
require "tamashii/manager/config"
|
4
|
-
require "tamashii/manager/authorization"
|
5
|
-
require "tamashii/manager/handler/broadcaster"
|
6
|
-
require "tamashii/manager/clients"
|
7
|
-
require "tamashii/common"
|
1
|
+
# frozen_string_literal: true
|
8
2
|
|
9
|
-
|
10
|
-
|
3
|
+
require 'tamashii/server'
|
4
|
+
require 'tamashii/common'
|
5
|
+
require 'tamashii/manager/version'
|
6
|
+
|
7
|
+
require 'tamashii/manager/subscription'
|
8
|
+
require 'tamashii/manager/config'
|
9
|
+
require 'tamashii/manager/client_manager'
|
10
|
+
require 'tamashii/manager/client'
|
11
|
+
require 'tamashii/manager/channel'
|
12
|
+
require 'tamashii/manager/channel_pool'
|
13
|
+
require 'tamashii/manager/authorization'
|
14
|
+
require 'tamashii/manager/authorizator'
|
15
|
+
require 'tamashii/manager/handler'
|
16
|
+
require 'tamashii/manager/error'
|
17
|
+
require 'tamashii/manager/server'
|
11
18
|
|
12
19
|
module Tamashii
|
20
|
+
# :nodoc:
|
13
21
|
module Manager
|
14
22
|
def self.config(&block)
|
15
|
-
return Config.
|
23
|
+
return instance_exec(Config.instance, &block) if block_given?
|
16
24
|
Config
|
17
25
|
end
|
18
26
|
|
19
27
|
def self.logger
|
20
|
-
@logger ||=
|
28
|
+
@logger ||= ::Logger.new(config.log_file)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.server
|
32
|
+
@server ||= Tamashii::Manager::Server.new
|
21
33
|
end
|
22
34
|
end
|
23
35
|
end
|
36
|
+
|
37
|
+
# TODO: Use block mode to define resolver
|
38
|
+
# rubocop:disable Metrics/LineLength
|
39
|
+
Tamashii::Resolver.default_handler Tamashii::Manager::Handler::Broadcaster
|
40
|
+
Tamashii::Resolver.handle Tamashii::Type::AUTH_TOKEN, Tamashii::Manager::Authorization
|
41
|
+
# rubocop:enable Metrics/LineLength
|
@@ -1,16 +1,16 @@
|
|
1
|
-
|
2
|
-
require "tamashii/manager/authorizator/token"
|
3
|
-
require "tamashii/common"
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
3
|
module Tamashii
|
6
4
|
module Manager
|
5
|
+
# :nodoc:
|
7
6
|
class Authorization < Tamashii::Handler
|
8
7
|
def resolve(data = nil)
|
9
8
|
type, client_id = case @type
|
10
9
|
when Tamashii::Type::AUTH_TOKEN
|
11
10
|
Authorizator::Token.new.verify!(data)
|
12
11
|
else
|
13
|
-
raise AuthorizationError
|
12
|
+
raise Error::AuthorizationError,
|
13
|
+
'Invalid authorization type.'
|
14
14
|
end
|
15
15
|
@env[:client].accept(type, client_id)
|
16
16
|
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
|
2
|
-
require "tamashii/manager/config"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module Tamashii
|
5
4
|
module Manager
|
6
5
|
module Authorizator
|
6
|
+
# :nodoc:
|
7
7
|
class Token
|
8
8
|
attr_reader :client_id
|
9
9
|
|
@@ -16,8 +16,8 @@ module Tamashii
|
|
16
16
|
def verify!(data)
|
17
17
|
@type, @client_id, token = data.split(",")
|
18
18
|
Manager.logger.debug("Client #{@client_id} try to verify token: #{Config.env.production? ? "FILTERED" : token}")
|
19
|
-
raise AuthorizationError
|
20
|
-
raise AuthorizationError
|
19
|
+
raise Error::AuthorizationError, "Token mismatch!" unless @authorized = Config.token == token
|
20
|
+
raise Error::AuthorizationError, "Device type not available!" unless Type::CLIENT.values.include?(@type.to_i)
|
21
21
|
[@type.to_i, @client_id]
|
22
22
|
end
|
23
23
|
end
|
@@ -1,7 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Tamashii
|
4
4
|
module Manager
|
5
|
+
# :nodoc:
|
5
6
|
class Channel < Set
|
6
7
|
SERVER_ID = 0
|
7
8
|
|
@@ -23,7 +24,7 @@ module Tamashii
|
|
23
24
|
when :checkin
|
24
25
|
servers
|
25
26
|
else
|
26
|
-
return pool.
|
27
|
+
return pool.idle || pool.create! if pool[client.tag].nil?
|
27
28
|
pool[client.tag]
|
28
29
|
end
|
29
30
|
end
|
@@ -35,7 +36,9 @@ module Tamashii
|
|
35
36
|
|
36
37
|
pool.ready(channel)
|
37
38
|
|
38
|
-
Manager.logger.info(
|
39
|
+
Manager.logger.info(
|
40
|
+
"Client #{client.id} subscribe to Channel ##{channel.id}"
|
41
|
+
)
|
39
42
|
|
40
43
|
channel
|
41
44
|
end
|
@@ -44,12 +47,18 @@ module Tamashii
|
|
44
47
|
channel = select_channel(client)
|
45
48
|
channel.delete(client)
|
46
49
|
|
47
|
-
Manager.logger.info(
|
50
|
+
Manager.logger.info(
|
51
|
+
"Client #{client.id} unsubscribe to Channel ##{channel.id}"
|
52
|
+
)
|
48
53
|
|
49
|
-
if channel.empty? && channel.id != SERVER_ID
|
50
|
-
|
51
|
-
|
52
|
-
|
54
|
+
idle_channel(channel) if channel.empty? && channel.id != SERVER_ID
|
55
|
+
end
|
56
|
+
|
57
|
+
def idle_channel(channel)
|
58
|
+
pool.idle(channel.id)
|
59
|
+
# rubocop:disable Metrics/LineLength
|
60
|
+
Manager.logger.debug("Channel Pool add - ##{channel.id}, available channels: #{pool.idle.size}")
|
61
|
+
# rubocop:enable Metrics/LineLength
|
53
62
|
end
|
54
63
|
end
|
55
64
|
|
@@ -61,7 +70,8 @@ module Tamashii
|
|
61
70
|
end
|
62
71
|
|
63
72
|
def send_to(channel_id, packet)
|
64
|
-
|
73
|
+
channel = Channel.pool[channel_id]
|
74
|
+
return unless channel.nil?
|
65
75
|
channel.broadcast(packet)
|
66
76
|
end
|
67
77
|
|
@@ -70,12 +80,15 @@ module Tamashii
|
|
70
80
|
each do |client|
|
71
81
|
client.send(packet)
|
72
82
|
end
|
83
|
+
|
84
|
+
# rubocop:disable Metrics/LineLength
|
73
85
|
Channel.servers.broadcast(packet) unless id == SERVER_ID || exclude_server
|
86
|
+
# rubocop:enable Metrics/LineLength
|
74
87
|
end
|
75
88
|
|
76
89
|
def broadcast_all(packet)
|
77
|
-
Channel.pool.each do |
|
78
|
-
channel
|
90
|
+
Channel.pool.each do |_id, channel|
|
91
|
+
channel&.broadcast(packet, true)
|
79
92
|
end
|
80
93
|
Channel.servers.broadcast(packet) unless id == SERVER_ID
|
81
94
|
end
|
@@ -1,42 +1,39 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Tamashii
|
4
4
|
module Manager
|
5
|
+
# :nodoc:
|
5
6
|
class ChannelPool < Hash
|
7
|
+
attr_reader :idles
|
8
|
+
|
6
9
|
def initialize(size = 10)
|
7
|
-
@
|
10
|
+
@idles = []
|
8
11
|
@ptr = 1
|
9
12
|
|
10
13
|
size.times { create! }
|
11
14
|
end
|
12
15
|
|
13
16
|
def create!
|
14
|
-
@
|
17
|
+
@idles << Channel.new(@ptr)
|
15
18
|
@ptr += 1
|
16
19
|
end
|
17
20
|
|
18
21
|
def idle(channel_id = nil)
|
19
|
-
return @
|
22
|
+
return @idles.first if channel_id.nil?
|
20
23
|
return unless self[channel_id]&.empty?
|
21
|
-
@
|
24
|
+
@idles << self[channel_id]
|
22
25
|
self[channel_id] = nil
|
23
26
|
end
|
24
27
|
|
25
28
|
def ready(channel)
|
26
29
|
return if channel.empty?
|
27
30
|
self[channel.id] = channel
|
28
|
-
if @
|
29
|
-
@idle.delete(channel)
|
30
|
-
end
|
31
|
+
@idles.delete(channel) if @idles.include?(channel)
|
31
32
|
channel
|
32
33
|
end
|
33
34
|
|
34
35
|
def available?
|
35
|
-
!@
|
36
|
-
end
|
37
|
-
|
38
|
-
def get_idle
|
39
|
-
@idle.first
|
36
|
+
!@idles.empty?
|
40
37
|
end
|
41
38
|
end
|
42
39
|
end
|
@@ -1,12 +1,10 @@
|
|
1
|
-
|
2
|
-
require "tamashii/manager/stream"
|
3
|
-
require "tamashii/manager/channel"
|
4
|
-
require "tamashii/manager/authorization"
|
5
|
-
require "tamashii/common"
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
3
|
module Tamashii
|
8
4
|
module Manager
|
9
|
-
|
5
|
+
# :nodoc:
|
6
|
+
class Client < Tamashii::Server::Connection::Base
|
7
|
+
include ClientManager
|
10
8
|
|
11
9
|
attr_reader :env, :url
|
12
10
|
attr_reader :channel
|
@@ -16,39 +14,6 @@ module Tamashii
|
|
16
14
|
|
17
15
|
attr_accessor :tag
|
18
16
|
|
19
|
-
def self.accepted_clients
|
20
|
-
Clients.instance
|
21
|
-
end
|
22
|
-
|
23
|
-
def initialize(env, event_loop)
|
24
|
-
@env = env
|
25
|
-
@id = nil
|
26
|
-
@type = Type::CLIENT[:agent]
|
27
|
-
@last_beat_timestamp = Time.at(0)
|
28
|
-
@last_response_time = Float::INFINITY
|
29
|
-
|
30
|
-
secure = Rack::Request.new(env).ssl?
|
31
|
-
scheme = secure ? 'wss:' : 'ws:'
|
32
|
-
@url = "#{scheme}//#{env['HTTP_HOST']}#{env['REQUEST_URI']}"
|
33
|
-
|
34
|
-
Manager.logger.info("Accept connection from #{env['REMOTE_ADDR']}")
|
35
|
-
|
36
|
-
@driver = WebSocket::Driver.rack(self)
|
37
|
-
|
38
|
-
env['rack.hijack'].call
|
39
|
-
@io = env['rack.hijack_io']
|
40
|
-
|
41
|
-
Connection.register(self)
|
42
|
-
@stream = Stream.new(event_loop, @io, self)
|
43
|
-
|
44
|
-
@driver.on(:open) { |e| on_open }
|
45
|
-
@driver.on(:message) { |e| receive(e.data) }
|
46
|
-
@driver.on(:close) { |e| on_close(e) }
|
47
|
-
@driver.on(:error) { |e| emit_error(e.message) }
|
48
|
-
|
49
|
-
@driver.start
|
50
|
-
end
|
51
|
-
|
52
17
|
def id
|
53
18
|
return "<Unauthorized : #{@env['REMOTE_ADDR']}>" if @id.nil?
|
54
19
|
@id
|
@@ -58,17 +23,9 @@ module Tamashii
|
|
58
23
|
Type::CLIENT.key(@type)
|
59
24
|
end
|
60
25
|
|
61
|
-
def write(buffer)
|
62
|
-
@io.write(buffer)
|
63
|
-
end
|
64
|
-
|
65
26
|
def send(packet)
|
66
27
|
packet = packet.dump if packet.is_a?(Tamashii::Packet)
|
67
|
-
@
|
68
|
-
end
|
69
|
-
|
70
|
-
def parse(buffer)
|
71
|
-
@driver.parse(buffer)
|
28
|
+
@socket.transmit(packet)
|
72
29
|
end
|
73
30
|
|
74
31
|
def authorized?
|
@@ -79,25 +36,18 @@ module Tamashii
|
|
79
36
|
@id = id
|
80
37
|
@type = type
|
81
38
|
@channel = Channel.subscribe(self)
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
def shutdown
|
91
|
-
Connection.unregister(self)
|
92
|
-
if authorized?
|
93
|
-
Channel.unsubscribe(self)
|
94
|
-
Clients.unregister(self)
|
95
|
-
end
|
39
|
+
packet = Tamashii::Packet.new(
|
40
|
+
Tamashii::Type::AUTH_RESPONSE,
|
41
|
+
@channel.id,
|
42
|
+
true
|
43
|
+
)
|
44
|
+
Client[id] = self
|
45
|
+
send packet.dump
|
96
46
|
end
|
97
47
|
|
98
48
|
def beat
|
99
49
|
beat_time = Time.now
|
100
|
-
@
|
50
|
+
@socket.ping("heart-beat-at-#{beat_time}") do
|
101
51
|
heartbeat_callback(beat_time)
|
102
52
|
end
|
103
53
|
end
|
@@ -105,36 +55,53 @@ module Tamashii
|
|
105
55
|
def heartbeat_callback(beat_time)
|
106
56
|
@last_beat_timestamp = Time.now
|
107
57
|
@last_response_time = @last_beat_timestamp - beat_time
|
108
|
-
Manager.logger.debug
|
58
|
+
Manager.logger.debug(
|
59
|
+
"[#{id}] Heart beat #{beat_time} " \
|
60
|
+
"returns at #{@last_beat_timestamp}!" \
|
61
|
+
" Delay: #{(@last_response_time * 1000).round} ms"
|
62
|
+
)
|
109
63
|
end
|
110
64
|
|
111
|
-
private
|
112
65
|
def on_open
|
113
66
|
Manager.logger.info("Client #{id} is ready")
|
114
67
|
end
|
115
68
|
|
116
|
-
def
|
69
|
+
def on_message(data)
|
117
70
|
Manager.logger.debug("Receive Data: #{data}")
|
118
71
|
return unless data.is_a?(Array)
|
119
72
|
Tamashii::Resolver.resolve(Tamashii::Packet.load(data), client: self)
|
120
|
-
rescue AuthorizationError =>
|
121
|
-
|
122
|
-
send(Tamashii::Packet.new(Tamashii::Type::AUTH_RESPONSE, 0, false))
|
123
|
-
@driver.close
|
73
|
+
rescue AuthorizationError => reason
|
74
|
+
close_on_authorize_failed(reason)
|
124
75
|
rescue => e
|
125
|
-
|
126
|
-
Manager.logger.debug("Backtrace:")
|
127
|
-
e.backtrace.each {|msg| Manager.logger.debug(msg)}
|
76
|
+
on_message_error(e)
|
128
77
|
end
|
129
78
|
|
130
|
-
def on_close
|
79
|
+
def on_close
|
131
80
|
Manager.logger.info("Client #{id} closed connection")
|
132
|
-
|
81
|
+
Client[id] = nil
|
133
82
|
end
|
134
83
|
|
135
84
|
def emit_error(message)
|
136
85
|
Manager.logger.error("Client #{id} has error => #{message}")
|
137
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def close_on_authorize_failed(reason)
|
91
|
+
Manager.logger.error(
|
92
|
+
"Client #{id} authentication failed => #{reason.message}"
|
93
|
+
)
|
94
|
+
send(Tamashii::Packet.new(Tamashii::Type::AUTH_RESPONSE, 0, false))
|
95
|
+
@socket.close
|
96
|
+
end
|
97
|
+
|
98
|
+
def on_message_error(e)
|
99
|
+
Manager.logger.error(
|
100
|
+
"Error when receiving data from client #{id}: #{e.message}"
|
101
|
+
)
|
102
|
+
Manager.logger.debug('Backtrace:')
|
103
|
+
e.backtrace.each { |msg| Manager.logger.debug(msg) }
|
104
|
+
end
|
138
105
|
end
|
139
106
|
end
|
140
107
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tamashii
|
4
|
+
module Manager
|
5
|
+
# :nodoc:
|
6
|
+
module ClientManager
|
7
|
+
# rubocop:disable Metrics/MethodLength
|
8
|
+
def self.included(other)
|
9
|
+
other.class_eval do
|
10
|
+
class << self
|
11
|
+
def accepted_clients
|
12
|
+
@accepted_clients ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](name)
|
16
|
+
accepted_clients[name.to_s]
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(name, client)
|
20
|
+
return unless client.is_a?(Client)
|
21
|
+
accepted_clients[name.to_s] = client
|
22
|
+
end
|
23
|
+
|
24
|
+
def sent_to(id, packet)
|
25
|
+
Manager.server.pubsub.send_to(id, packet)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
# rubocop:enable Metrics/MethodLength
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,24 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'tamashii/common'
|
4
|
+
require 'tamashii/configurable'
|
2
5
|
|
3
6
|
module Tamashii
|
4
7
|
module Manager
|
5
|
-
|
6
|
-
|
8
|
+
# :nodoc:
|
9
|
+
class Config
|
10
|
+
class << self
|
11
|
+
def instance
|
12
|
+
@instance ||= Config.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to_missing?(name, _all = false)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(name, *args, &block)
|
20
|
+
# rubocop:disable Metrics/LineLength
|
21
|
+
return instance.send(name, *args, &block) if instance.class.exist?(name)
|
22
|
+
# rubocop:enable Metrics/LineLength
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
include Tamashii::Configurable
|
28
|
+
|
29
|
+
AUTH_TYPES = %i[none token].freeze
|
7
30
|
|
8
31
|
register :auth_type, :none
|
32
|
+
register :token, nil
|
9
33
|
register :log_file, STDOUT
|
34
|
+
register :log_level, Logger::INFO
|
35
|
+
register :env, nil
|
10
36
|
register :heartbeat_interval, 3
|
37
|
+
register :port, 3000
|
38
|
+
|
39
|
+
def [](key)
|
40
|
+
config(key)
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(key, value)
|
44
|
+
config(key, value)
|
45
|
+
end
|
11
46
|
|
12
47
|
def auth_type(type = nil)
|
13
48
|
return self[:auth_type] if type.nil?
|
14
49
|
return unless AUTH_TYPES.include?(type)
|
15
|
-
self
|
50
|
+
self.auth_type = type
|
16
51
|
end
|
17
52
|
|
18
53
|
def log_level(level = nil)
|
19
54
|
return Manager.logger.level if level.nil?
|
20
55
|
Manager.logger.level = level
|
21
56
|
end
|
57
|
+
|
58
|
+
def env(env = nil)
|
59
|
+
return Tamashii::Environment.new(self[:env]) if env.nil?
|
60
|
+
self.env = env.to_s
|
61
|
+
end
|
22
62
|
end
|
23
63
|
end
|
24
64
|
end
|
@@ -1,14 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Tamashii
|
4
4
|
module Manager
|
5
5
|
module Handler
|
6
|
+
# :nodoc:
|
6
7
|
class Broadcaster < Tamashii::Handler
|
7
8
|
def resolve(data = nil)
|
8
9
|
client = @env[:client]
|
9
|
-
if client.authorized?
|
10
|
-
|
11
|
-
|
10
|
+
broadcast(client, data) if client.authorized?
|
11
|
+
end
|
12
|
+
|
13
|
+
def broadcast(client, data)
|
14
|
+
packet = Packet.new(@type, client.tag, data)
|
15
|
+
client.channel.broadcast(packet.dump)
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -1,80 +1,23 @@
|
|
1
|
-
|
2
|
-
require "securerandom"
|
3
|
-
require "websocket/driver"
|
4
|
-
require "monitor"
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
Tamashii::Server.config do |config|
|
4
|
+
config.connection_class = Tamashii::Manager::Client
|
5
|
+
config.pubsub_class = Tamashii::Manager::Subscription
|
6
|
+
end
|
9
7
|
|
10
8
|
module Tamashii
|
11
9
|
module Manager
|
12
|
-
|
13
|
-
|
14
|
-
attr_reader :instance
|
15
|
-
|
16
|
-
LOCK = Monitor.new
|
17
|
-
|
18
|
-
def compile
|
19
|
-
@instance ||= new
|
20
|
-
end
|
21
|
-
|
22
|
-
def call(env)
|
23
|
-
LOCK.synchronize { compile } unless instance
|
24
|
-
call!(env)
|
25
|
-
end
|
26
|
-
|
27
|
-
def call!(env)
|
28
|
-
instance.call(env)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
10
|
+
# :nodoc:
|
11
|
+
class Server < Tamashii::Server::Base
|
32
12
|
def initialize
|
33
|
-
|
13
|
+
super
|
34
14
|
setup_heartbeat_timer
|
35
|
-
|
36
|
-
Manager.logger.info("Server is created, read for accept connection")
|
37
15
|
end
|
38
16
|
|
17
|
+
# NOTE: Move into Tamashii::Server maybe better
|
39
18
|
def setup_heartbeat_timer
|
40
19
|
@heartbeat_timer = @event_loop.timer(Config.heartbeat_interval) do
|
41
|
-
@event_loop.post {
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def call(env)
|
46
|
-
if WebSocket::Driver.websocket?(env)
|
47
|
-
Client.new(env, @event_loop)
|
48
|
-
# A dummy rack response
|
49
|
-
body = {
|
50
|
-
message: "WS connected",
|
51
|
-
version: Tamashii::Manager::VERSION
|
52
|
-
}.to_json
|
53
|
-
|
54
|
-
[
|
55
|
-
200,
|
56
|
-
{
|
57
|
-
"Content-Type" => "application/json",
|
58
|
-
"Content-Length" => body.bytesize
|
59
|
-
},
|
60
|
-
[body]
|
61
|
-
]
|
62
|
-
else
|
63
|
-
|
64
|
-
# TODO: Handle HTTP API
|
65
|
-
body = {
|
66
|
-
message: "Invalid protocol or api request",
|
67
|
-
version: Tamashii::Manager::VERSION
|
68
|
-
}.to_json
|
69
|
-
|
70
|
-
[
|
71
|
-
404,
|
72
|
-
{
|
73
|
-
"Content-Type" => "application/json",
|
74
|
-
"Content-Length" => body.bytesize
|
75
|
-
},
|
76
|
-
[body]
|
77
|
-
]
|
20
|
+
@event_loop.post { Client.accepted_clients.values.map(&:beat) }
|
78
21
|
end
|
79
22
|
end
|
80
23
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tamashii
|
4
|
+
module Manager
|
5
|
+
# :nodoc:
|
6
|
+
class Subscription < Tamashii::Server::Subscription::Redis
|
7
|
+
def send_to(id, packet)
|
8
|
+
packet = packet.dump if packet.is_a?(Tamashii::Packet)
|
9
|
+
broadcast([id.bytesize, id.unpack('C*'), packet].flatten)
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def process_message(message)
|
15
|
+
operate = unpack(message)
|
16
|
+
head_size = operate.take(1).first
|
17
|
+
return if head_size.zero?
|
18
|
+
target = operate.take(head_size + 1).drop(1)
|
19
|
+
Client[target.pack('C*')]&.send(operate.drop(head_size + 1))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/tamashii-manager.gemspec
CHANGED
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_runtime_dependency "puma"
|
33
33
|
spec.add_runtime_dependency "concurrent-ruby"
|
34
34
|
spec.add_runtime_dependency "rack"
|
35
|
+
spec.add_runtime_dependency "tamashii"
|
35
36
|
spec.add_runtime_dependency "tamashii-common"
|
36
37
|
spec.add_runtime_dependency "websocket-driver"
|
37
38
|
spec.add_runtime_dependency "nio4r"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tamashii-manager
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 蒼時弦也
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2017-
|
13
|
+
date: 2017-05-26 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: puma
|
@@ -54,6 +54,20 @@ dependencies:
|
|
54
54
|
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: '0'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: tamashii
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
type: :runtime
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
57
71
|
- !ruby/object:Gem::Dependency
|
58
72
|
name: tamashii-common
|
59
73
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +220,7 @@ extra_rdoc_files: []
|
|
206
220
|
files:
|
207
221
|
- ".gitignore"
|
208
222
|
- ".rspec"
|
223
|
+
- ".ruby-version"
|
209
224
|
- ".travis.yml"
|
210
225
|
- Gemfile
|
211
226
|
- Guardfile
|
@@ -217,18 +232,19 @@ files:
|
|
217
232
|
- exe/tamashii-manager
|
218
233
|
- lib/tamashii/manager.rb
|
219
234
|
- lib/tamashii/manager/authorization.rb
|
235
|
+
- lib/tamashii/manager/authorizator.rb
|
220
236
|
- lib/tamashii/manager/authorizator/token.rb
|
221
237
|
- lib/tamashii/manager/channel.rb
|
222
238
|
- lib/tamashii/manager/channel_pool.rb
|
223
239
|
- lib/tamashii/manager/client.rb
|
224
|
-
- lib/tamashii/manager/
|
240
|
+
- lib/tamashii/manager/client_manager.rb
|
225
241
|
- lib/tamashii/manager/config.rb
|
226
|
-
- lib/tamashii/manager/
|
227
|
-
- lib/tamashii/manager/
|
242
|
+
- lib/tamashii/manager/error.rb
|
243
|
+
- lib/tamashii/manager/error/authorization_error.rb
|
244
|
+
- lib/tamashii/manager/handler.rb
|
228
245
|
- lib/tamashii/manager/handler/broadcaster.rb
|
229
246
|
- lib/tamashii/manager/server.rb
|
230
|
-
- lib/tamashii/manager/
|
231
|
-
- lib/tamashii/manager/stream_event_loop.rb
|
247
|
+
- lib/tamashii/manager/subscription.rb
|
232
248
|
- lib/tamashii/manager/version.rb
|
233
249
|
- tags
|
234
250
|
- tamashii-manager.gemspec
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module Tamashii
|
2
|
-
module Manager
|
3
|
-
class Clients < Hash
|
4
|
-
class << self
|
5
|
-
def method_missing(name, *args, &block)
|
6
|
-
self.instance.send(name, *args, &block)
|
7
|
-
end
|
8
|
-
|
9
|
-
def instance
|
10
|
-
@instance ||= new
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def register(client)
|
15
|
-
self[client.id] = client
|
16
|
-
end
|
17
|
-
|
18
|
-
def unregister(client)
|
19
|
-
self.delete(client.id)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module Tamashii
|
2
|
-
module Manager
|
3
|
-
class Connection < Set
|
4
|
-
class << self
|
5
|
-
def instance
|
6
|
-
@instance ||= Connection.new
|
7
|
-
end
|
8
|
-
|
9
|
-
def register(client)
|
10
|
-
instance.add(client)
|
11
|
-
end
|
12
|
-
|
13
|
-
def unregister(client)
|
14
|
-
instance.delete(client)
|
15
|
-
end
|
16
|
-
|
17
|
-
def available?
|
18
|
-
!instance.empty?
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require "nio"
|
2
|
-
|
3
|
-
require "tamashii/manager/connection"
|
4
|
-
|
5
|
-
Thread.abort_on_exception = true
|
6
|
-
|
7
|
-
module Tamashii
|
8
|
-
module Manager
|
9
|
-
class Stream
|
10
|
-
|
11
|
-
attr_reader :event_loop
|
12
|
-
|
13
|
-
def initialize(event_loop, io, client)
|
14
|
-
@client = client
|
15
|
-
@io = io
|
16
|
-
@event_loop = event_loop
|
17
|
-
@event_loop.attach(io, self)
|
18
|
-
end
|
19
|
-
|
20
|
-
def receive(data)
|
21
|
-
@client.parse(data)
|
22
|
-
end
|
23
|
-
|
24
|
-
def shutdown
|
25
|
-
clean_rack_hijack
|
26
|
-
end
|
27
|
-
|
28
|
-
def close
|
29
|
-
shutdown
|
30
|
-
@client.shutdown
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
def clean_rack_hijack
|
35
|
-
return unless @io
|
36
|
-
@event_loop.detach(@io, self)
|
37
|
-
@io = nil
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,120 +0,0 @@
|
|
1
|
-
require 'nio'
|
2
|
-
require 'thread'
|
3
|
-
require 'concurrent'
|
4
|
-
|
5
|
-
Thread.abort_on_exception = true
|
6
|
-
|
7
|
-
module Tamashii
|
8
|
-
module Manager
|
9
|
-
class StreamEventLoop
|
10
|
-
def initialize
|
11
|
-
@nio = @thread = nil
|
12
|
-
@stopping = false
|
13
|
-
@map = {}
|
14
|
-
|
15
|
-
@todo = Queue.new
|
16
|
-
|
17
|
-
@spawn_mutex = Mutex.new
|
18
|
-
end
|
19
|
-
|
20
|
-
def timer(interval, &block)
|
21
|
-
Concurrent::TimerTask.new(execution_interval: interval, &block).tap(&:execute)
|
22
|
-
end
|
23
|
-
|
24
|
-
def post(task = nil, &block)
|
25
|
-
task ||= block
|
26
|
-
Concurrent.global_io_executor << task
|
27
|
-
end
|
28
|
-
|
29
|
-
def attach(io, stream)
|
30
|
-
@todo << lambda do
|
31
|
-
@map[io] = @nio.register(io, :r)
|
32
|
-
@map[io].value = stream
|
33
|
-
end
|
34
|
-
wakeup
|
35
|
-
end
|
36
|
-
|
37
|
-
def detach(io, stream)
|
38
|
-
@todo << lambda do
|
39
|
-
@nio.deregister io
|
40
|
-
@map.delete io
|
41
|
-
io.close
|
42
|
-
end
|
43
|
-
wakeup
|
44
|
-
end
|
45
|
-
|
46
|
-
def stop
|
47
|
-
@stopping = true
|
48
|
-
wakeup if @nio
|
49
|
-
end
|
50
|
-
|
51
|
-
def stopped?
|
52
|
-
@stopping
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
def spawn
|
57
|
-
return if @thread && @thread.status
|
58
|
-
|
59
|
-
@spawn_mutex.synchronize do
|
60
|
-
return if @thread && @thread.status
|
61
|
-
|
62
|
-
@nio ||= NIO::Selector.new
|
63
|
-
|
64
|
-
@thread = Thread.new { run }
|
65
|
-
|
66
|
-
return true
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def wakeup
|
71
|
-
spawn || @nio.wakeup
|
72
|
-
end
|
73
|
-
|
74
|
-
def run
|
75
|
-
loop do
|
76
|
-
if stopped?
|
77
|
-
@nio.close
|
78
|
-
break
|
79
|
-
end
|
80
|
-
|
81
|
-
until @todo.empty?
|
82
|
-
@todo.pop(true).call
|
83
|
-
end
|
84
|
-
|
85
|
-
next unless monitors = @nio.select
|
86
|
-
|
87
|
-
monitors.each do |monitor|
|
88
|
-
io = monitor.io
|
89
|
-
stream = monitor.value
|
90
|
-
|
91
|
-
begin
|
92
|
-
if monitor.writable?
|
93
|
-
if stream.flush_writer_buffer
|
94
|
-
monitor.interests = :r
|
95
|
-
end
|
96
|
-
next unless monitor.readable?
|
97
|
-
end
|
98
|
-
|
99
|
-
incoming = io.read_nonblock(4096, exception: false)
|
100
|
-
case incoming
|
101
|
-
when :wait_readable then next
|
102
|
-
when nil then stream.close
|
103
|
-
else
|
104
|
-
stream.receive incoming
|
105
|
-
end
|
106
|
-
rescue
|
107
|
-
begin
|
108
|
-
stream.close
|
109
|
-
rescue
|
110
|
-
@nio.deregister io
|
111
|
-
@map.delete io
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|