tamashii-manager 0.1.4
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -0
- data/Guardfile +70 -0
- data/LICENSE.md +201 -0
- data/README.md +38 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/tamashii-manager +46 -0
- data/lib/tamashii/manager.rb +23 -0
- data/lib/tamashii/manager/authorization.rb +19 -0
- data/lib/tamashii/manager/authorizator/token.rb +26 -0
- data/lib/tamashii/manager/channel.rb +84 -0
- data/lib/tamashii/manager/channel_pool.rb +43 -0
- data/lib/tamashii/manager/client.rb +140 -0
- data/lib/tamashii/manager/clients.rb +23 -0
- data/lib/tamashii/manager/config.rb +24 -0
- data/lib/tamashii/manager/connection.rb +23 -0
- data/lib/tamashii/manager/errors/authorization_error.rb +6 -0
- data/lib/tamashii/manager/handler/broadcaster.rb +16 -0
- data/lib/tamashii/manager/server.rb +81 -0
- data/lib/tamashii/manager/stream.rb +41 -0
- data/lib/tamashii/manager/stream_event_loop.rb +106 -0
- data/lib/tamashii/manager/version.rb +5 -0
- data/tamashii-manager.gemspec +46 -0
- metadata +257 -0
| @@ -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,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
         |