tamashii-manager 0.1.7 → 0.2.0
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/.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
|