tamashii-manager 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ require "tamashii/manager/errors/authorization_error"
2
+ require "tamashii/manager/authorizator/token"
3
+ require "tamashii/common"
4
+
5
+ module Tamashii
6
+ module Manager
7
+ class Authorization < Tamashii::Handler
8
+ def resolve(data = nil)
9
+ type, client_id = case @type
10
+ when Tamashii::Type::AUTH_TOKEN
11
+ Authorizator::Token.new.verify!(data)
12
+ else
13
+ raise AuthorizationError.new("Invalid authorization type.")
14
+ end
15
+ @env[:client].accept(type, client_id)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ require "tamashii/manager/errors/authorization_error"
2
+ require "tamashii/manager/config"
3
+
4
+ module Tamashii
5
+ module Manager
6
+ module Authorizator
7
+ class Token
8
+ attr_reader :client_id
9
+
10
+ def initialize
11
+ @client_id = nil
12
+ @authorized = false
13
+ @type = Type::CLIENT[:agent]
14
+ end
15
+
16
+ def verify!(data)
17
+ @type, @client_id, token = data.split(",")
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)
21
+ [@type.to_i, @client_id]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,84 @@
1
+ require 'tamashii/manager/channel_pool'
2
+
3
+ module Tamashii
4
+ module Manager
5
+ class Channel < Set
6
+ SERVER_ID = 0
7
+
8
+ class << self
9
+ def pool
10
+ @pool ||= ChannelPool.new
11
+ end
12
+
13
+ def get(id)
14
+ pool[id]
15
+ end
16
+
17
+ def servers
18
+ @servers ||= Channel.new(SERVER_ID)
19
+ end
20
+
21
+ def select_channel(client)
22
+ case client.type
23
+ when :checkin
24
+ servers
25
+ else
26
+ return pool.get_idle || pool.create! if pool[client.tag].nil?
27
+ pool[client.tag]
28
+ end
29
+ end
30
+
31
+ def subscribe(client)
32
+ channel = select_channel(client)
33
+ channel.add(client)
34
+ client.tag = channel.id
35
+
36
+ pool.ready(channel)
37
+
38
+ Manager.logger.info("Client #{client.id} subscribe to Channel ##{channel.id}")
39
+
40
+ channel
41
+ end
42
+
43
+ def unsubscribe(client)
44
+ channel = select_channel(client)
45
+ channel.delete(client)
46
+
47
+ Manager.logger.info("Client #{client.id} unsubscribe to Channel ##{channel.id}")
48
+
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
53
+ end
54
+ end
55
+
56
+ attr_reader :id
57
+
58
+ def initialize(id, *args)
59
+ super(*args)
60
+ @id = id
61
+ end
62
+
63
+ def send_to(channel_id, packet)
64
+ return unless channel = Channel.pool[channel_id]
65
+ channel.broadcast(packet)
66
+ end
67
+
68
+ def broadcast(packet, exclude_server = false)
69
+ Manager.logger.info("Broadcast \"#{packet}\" to Channel ##{@id}")
70
+ each do |client|
71
+ client.send(packet)
72
+ end
73
+ Channel.servers.broadcast(packet) unless id == SERVER_ID || exclude_server
74
+ end
75
+
76
+ def broadcast_all(packet)
77
+ Channel.pool.each do |id, channel|
78
+ channel.broadcast(packet, true) unless channel.nil?
79
+ end
80
+ Channel.servers.broadcast(packet) unless id == SERVER_ID
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,43 @@
1
+ require 'tamashii/manager/channel'
2
+
3
+ module Tamashii
4
+ module Manager
5
+ class ChannelPool < Hash
6
+ def initialize(size = 10)
7
+ @idle = []
8
+ @ptr = 1
9
+
10
+ size.times { create! }
11
+ end
12
+
13
+ def create!
14
+ @idle << Channel.new(@ptr)
15
+ @ptr += 1
16
+ end
17
+
18
+ def idle(channel_id = nil)
19
+ return @idle if channel_id.nil?
20
+ return unless self[channel_id]&.empty?
21
+ @idle << self[channel_id]
22
+ self[channel_id] = nil
23
+ end
24
+
25
+ def ready(channel)
26
+ return if channel.empty?
27
+ self[channel.id] = channel
28
+ if @idle.include?(channel)
29
+ @idle.delete(channel)
30
+ end
31
+ channel
32
+ end
33
+
34
+ def available?
35
+ !@idle.empty?
36
+ end
37
+
38
+ def get_idle
39
+ @idle.first
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,140 @@
1
+ require "websocket/driver"
2
+ require "tamashii/manager/stream"
3
+ require "tamashii/manager/channel"
4
+ require "tamashii/manager/authorization"
5
+ require "tamashii/common"
6
+
7
+ module Tamashii
8
+ module Manager
9
+ class Client
10
+
11
+ attr_reader :env, :url
12
+ attr_reader :channel
13
+
14
+ attr_reader :last_beat_timestamp
15
+ attr_reader :last_response_time
16
+
17
+ attr_accessor :tag
18
+
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
+ def id
53
+ return "<Unauthorized : #{@env['REMOTE_ADDR']}>" if @id.nil?
54
+ @id
55
+ end
56
+
57
+ def type
58
+ Type::CLIENT.key(@type)
59
+ end
60
+
61
+ def write(buffer)
62
+ @io.write(buffer)
63
+ end
64
+
65
+ def send(packet)
66
+ packet = packet.dump if packet.is_a?(Tamashii::Packet)
67
+ @driver.binary(packet)
68
+ end
69
+
70
+ def parse(buffer)
71
+ @driver.parse(buffer)
72
+ end
73
+
74
+ def authorized?
75
+ !@id.nil?
76
+ end
77
+
78
+ def accept(type, id)
79
+ @id = id
80
+ @type = type
81
+ @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
96
+ end
97
+
98
+ def beat
99
+ beat_time = Time.now
100
+ @driver.ping("heart-beat-at-#{beat_time}") do
101
+ heartbeat_callback(beat_time)
102
+ end
103
+ end
104
+
105
+ def heartbeat_callback(beat_time)
106
+ @last_beat_timestamp = Time.now
107
+ @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"
109
+ end
110
+
111
+ private
112
+ def on_open
113
+ Manager.logger.info("Client #{id} is ready")
114
+ end
115
+
116
+ def receive(data)
117
+ Manager.logger.debug("Receive Data: #{data}")
118
+ return unless data.is_a?(Array)
119
+ 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
124
+ 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)}
128
+ end
129
+
130
+ def on_close(e)
131
+ Manager.logger.info("Client #{id} closed connection")
132
+ @stream.close
133
+ end
134
+
135
+ def emit_error(message)
136
+ Manager.logger.error("Client #{id} has error => #{message}")
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,23 @@
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
@@ -0,0 +1,24 @@
1
+ require 'tamashii/common'
2
+
3
+ module Tamashii
4
+ module Manager
5
+ class Config < Tamashii::Config
6
+ AUTH_TYPES = [:none, :token]
7
+
8
+ register :auth_type, :none
9
+ register :log_file, STDOUT
10
+ register :heartbeat_interval, 3
11
+
12
+ def auth_type(type = nil)
13
+ return self[:auth_type] if type.nil?
14
+ return unless AUTH_TYPES.include?(type)
15
+ self[:auth_type] = type
16
+ end
17
+
18
+ def log_level(level = nil)
19
+ return Manager.logger.level if level.nil?
20
+ Manager.logger.level = level
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
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
@@ -0,0 +1,6 @@
1
+ module Tamashii
2
+ module Manager
3
+ class AuthorizationError < RuntimeError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ require 'tamashii/common'
2
+
3
+ module Tamashii
4
+ module Manager
5
+ module Handler
6
+ class Broadcaster < Tamashii::Handler
7
+ def resolve(data = nil)
8
+ client = @env[:client]
9
+ if client.authorized?
10
+ client.channel.broadcast(Packet.new(@type, client.tag , data).dump)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,81 @@
1
+ require "json"
2
+ require "securerandom"
3
+ require "websocket/driver"
4
+
5
+ require "tamashii/manager/client"
6
+ require "tamashii/manager/stream"
7
+ require "tamashii/manager/stream_event_loop"
8
+
9
+ module Tamashii
10
+ module Manager
11
+ class Server
12
+ class << self
13
+ attr_reader :instance
14
+
15
+ LOCK = Mutex.new
16
+
17
+ def compile
18
+ @instance ||= new
19
+ end
20
+
21
+ def call(env)
22
+ LOCK.synchronize { compile } unless instance
23
+ call!(env)
24
+ end
25
+
26
+ def call!(env)
27
+ instance.call(env)
28
+ end
29
+ end
30
+
31
+ def initialize
32
+ @event_loop = StreamEventLoop.new
33
+ setup_heartbeat_timer
34
+
35
+ Manager.logger.info("Server is created, read for accept connection")
36
+ end
37
+
38
+ def setup_heartbeat_timer
39
+ @heartbeat_timer = @event_loop.timer(Config.heartbeat_interval) do
40
+ @event_loop.post { Connection.instance.map(&:beat) }
41
+ end
42
+ end
43
+
44
+ def call(env)
45
+ if WebSocket::Driver.websocket?(env)
46
+ Client.new(env, @event_loop)
47
+ # A dummy rack response
48
+ body = {
49
+ message: "WS connected",
50
+ version: Tamashii::Manager::VERSION
51
+ }.to_json
52
+
53
+ [
54
+ 200,
55
+ {
56
+ "Content-Type" => "application/json",
57
+ "Content-Length" => body.bytesize
58
+ },
59
+ [body]
60
+ ]
61
+ else
62
+
63
+ # TODO: Handle HTTP API
64
+ body = {
65
+ message: "Invalid protocol or api request",
66
+ version: Tamashii::Manager::VERSION
67
+ }.to_json
68
+
69
+ [
70
+ 404,
71
+ {
72
+ "Content-Type" => "application/json",
73
+ "Content-Length" => body.bytesize
74
+ },
75
+ [body]
76
+ ]
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end