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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3615a69b16faac2730ca38fa2f42161db5e89d50
4
- data.tar.gz: 31482ad7e7c1c76fe0dee4651f0b7f21cf53e347
3
+ metadata.gz: b73434b81719979d45631fb55ee06dbc9c681dfb
4
+ data.tar.gz: 1271d4bbe0123c2bc284ba716958df9cf3e75f23
5
5
  SHA512:
6
- metadata.gz: f89bf3e52e8a1121518e385e07b96287d472159e1321ed9ba46dceb484eec2bf5cebd568244f5cf923e7e78307326ca3c9840a6725a352c04c7d9e7d6d1afceb
7
- data.tar.gz: c982c585c7088e7e795e28a221c00262d88ad3f88a8d061457002af66a47630b1ad830f37211dc0d345f7e9c4db655c56bb4f81bf1fbf0f1d8c2979faa04a753
6
+ metadata.gz: da6357c531d820f6f1147c7a8fcf972edaaaedba892db2a75b6553fb96eb09654e063666e4cc8ecaf6334ec5f7a27079ad29273643967bebd28a5c7d1ebe4f9c
7
+ data.tar.gz: ef266d3eaad776acbb73c66a6f1d3a5c99cc7bace32ec7d2e9b53541a7c13d1af17a26381e63752dd4ded688df3f542e32699c167878e779d57d6ce01a52d37b
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.1
data/Gemfile CHANGED
@@ -2,4 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in codeme-manager.gemspec
4
4
  gemspec
5
-
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: "0.0.0.0",
9
+ Host: '0.0.0.0',
12
10
  AccessLog: []
13
11
  }
14
12
 
15
13
  OptionParser.new do |opts|
16
- opts.on("-v", "--version", "Display Tamashii::Manager version") {
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("-h", "--help") {
19
+ opts.on('-h', '--help') do
22
20
  puts opts
23
21
  exit
24
- }
25
-
26
- opts.separator ""
27
- opts.on("-s", "--server SERVER", "Run Tamashii::Manager server") { |name| handlers.unshift(name.to_s) }
28
- opts.on("-o", "--host HOST", "The listen on HOST (default: 0.0.0.0)") { |host| options[:Host] = host.to_s; puts host }
29
- opts.on("-p", "--port PORT", "The listen on PORT (default: 3000)") { |port| options[:Port] = port.to_i }
30
- opts.on("-C", "--config FILE", "The external configuration file") do |config|
31
- if File.exists? config
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
- raise LoadError.new("Token authorization require to set token") if config.token.nil?
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::Server, options
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
@@ -1,23 +1,41 @@
1
- require "tamashii/manager/server"
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
- Tamashii::Resolver.default_handler Tamashii::Manager::Handler::Broadcaster
10
- Tamashii::Resolver.handle Tamashii::Type::AUTH_TOKEN, Tamashii::Manager::Authorization
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.class_eval(&block) if block_given?
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 ||= Tamashii::Logger.new(Config.log_file)
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
- require "tamashii/manager/errors/authorization_error"
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.new("Invalid authorization type.")
12
+ raise Error::AuthorizationError,
13
+ 'Invalid authorization type.'
14
14
  end
15
15
  @env[:client].accept(type, client_id)
16
16
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tamashii
4
+ module Manager
5
+ # :nodoc:
6
+ module Authorizator
7
+ autoload :Token, 'tamashii/manager/authorizator/token'
8
+ end
9
+ end
10
+ end
@@ -1,9 +1,9 @@
1
- require "tamashii/manager/errors/authorization_error"
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.new("Token mismatch!") unless @authorized = Config.token == token
20
- raise AuthorizationError.new("Device type not available!") unless Type::CLIENT.values.include?(@type.to_i)
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
- require 'tamashii/manager/channel_pool'
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.get_idle || pool.create! if pool[client.tag].nil?
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("Client #{client.id} subscribe to Channel ##{channel.id}")
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("Client #{client.id} unsubscribe to Channel ##{channel.id}")
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
- pool.idle(channel.id)
51
- Manager.logger.debug("Channel Pool add - ##{channel.id}, available channels: #{pool.idle.size}")
52
- end
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
- return unless channel = Channel.pool[channel_id]
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 |id, channel|
78
- channel.broadcast(packet, true) unless channel.nil?
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
- require 'tamashii/manager/channel'
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
- @idle = []
10
+ @idles = []
8
11
  @ptr = 1
9
12
 
10
13
  size.times { create! }
11
14
  end
12
15
 
13
16
  def create!
14
- @idle << Channel.new(@ptr)
17
+ @idles << Channel.new(@ptr)
15
18
  @ptr += 1
16
19
  end
17
20
 
18
21
  def idle(channel_id = nil)
19
- return @idle if channel_id.nil?
22
+ return @idles.first if channel_id.nil?
20
23
  return unless self[channel_id]&.empty?
21
- @idle << self[channel_id]
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 @idle.include?(channel)
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
- !@idle.empty?
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
- require "websocket/driver"
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
- class Client
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
- @driver.binary(packet)
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
- Clients.register(self)
83
- send(Tamashii::Packet.new(Tamashii::Type::AUTH_RESPONSE, @channel.id, true).dump)
84
- end
85
-
86
- def close
87
- @driver.close
88
- end
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
- @driver.ping("heart-beat-at-#{beat_time}") do
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 "[#{@id}] Heart beat #{beat_time} returns at #{@last_beat_timestamp}! Delay: #{(@last_response_time * 1000).round} ms"
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 receive(data)
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 => e
121
- Manager.logger.error("Client #{id} authentication failed => #{e.message}")
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
- Manager.logger.error("Error when receiving data from client #{id}: #{e.message}")
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(e)
79
+ def on_close
131
80
  Manager.logger.info("Client #{id} closed connection")
132
- @stream.close
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
- class Config < Tamashii::Config
6
- AUTH_TYPES = [:none, :token]
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[:auth_type] = type
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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tamashii
4
+ module Manager
5
+ # :nodoc:
6
+ module Error
7
+ autoload :AuthorizationError, 'tamashii/manager/error/authorization_error'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tamashii
4
+ module Manager
5
+ module Error
6
+ class AuthorizationError < RuntimeError
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tamashii
4
+ module Manager
5
+ # :nodoc:
6
+ module Handler
7
+ autoload :Broadcaster, 'tamashii/manager/handler/broadcaster'
8
+ end
9
+ end
10
+ end
@@ -1,14 +1,18 @@
1
- require 'tamashii/common'
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
- client.channel.broadcast(Packet.new(@type, client.tag , data).dump)
11
- end
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
- require "json"
2
- require "securerandom"
3
- require "websocket/driver"
4
- require "monitor"
1
+ # frozen_string_literal: true
5
2
 
6
- require "tamashii/manager/client"
7
- require "tamashii/manager/stream"
8
- require "tamashii/manager/stream_event_loop"
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
- class Server
13
- class << self
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
- @event_loop = StreamEventLoop.new
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 { Connection.instance.map(&:beat) }
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
@@ -1,5 +1,5 @@
1
1
  module Tamashii
2
2
  module Manager
3
- VERSION = "0.1.7"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -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.1.7
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-04-24 00:00:00.000000000 Z
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/clients.rb
240
+ - lib/tamashii/manager/client_manager.rb
225
241
  - lib/tamashii/manager/config.rb
226
- - lib/tamashii/manager/connection.rb
227
- - lib/tamashii/manager/errors/authorization_error.rb
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/stream.rb
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,6 +0,0 @@
1
- module Tamashii
2
- module Manager
3
- class AuthorizationError < RuntimeError
4
- end
5
- end
6
- 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