require 'socket' require 'logger' require 'securerandom' require 'active_model' require 'rubypitaya/core/path' require 'rubypitaya/core/setup' require 'rubypitaya/core/config' require 'rubypitaya/core/session' require 'rubypitaya/core/postman' require 'rubypitaya/core/parameters' require 'rubypitaya/core/http_routes' require 'rubypitaya/core/route_error' require 'rubypitaya/core/routes_base' require 'rubypitaya/core/status_codes' require 'rubypitaya/core/etcd_connector' require 'rubypitaya/core/handler_router' require 'rubypitaya/core/nats_connector' require 'rubypitaya/core/service_holder' require 'rubypitaya/core/instance_holder' require 'rubypitaya/core/database_connector' require 'rubypitaya/core/initializer_content' require 'rubypitaya/core/initializer_broadcast' require 'rubypitaya/core/application_files_importer' module RubyPitaya class Main def initialize @setup = Setup.new @environment_name = @setup.fetch('rubypitaya.server.environment', 'development') @is_cheats_enabled = @setup.fetch('rubypitaya.server.cheats', false) @is_development_environment = @environment_name == 'development' @setup.auto_reload if @is_development_environment @log = Logger.new('/proc/self/fd/1') @log.level = Logger::INFO @log.formatter = proc do |severity, datetime, progname, msg| "#{msg}\n" end @application_files_importer = ApplicationFilesImporter.new @application_files_importer.import(@is_cheats_enabled) @application_files_importer.auto_reload if @is_development_environment @server_name = @setup['rubypitaya.server.name'] @service_uuid = SecureRandom.uuid @desktop_name = Socket.gethostname @etcd_prefix = @setup['rubypitaya.etcd.prefix'] @etcd_address = @setup['rubypitaya.etcd.url'] @etcd_lease_seconds = @setup['rubypitaya.etcd.leaseSeconds'] @allow_reconnect = false @etcd_connector = EtcdConnector.new(@service_uuid, @desktop_name, @server_name, @etcd_prefix, @etcd_address, @allow_reconnect, @etcd_lease_seconds, @log) @etcd_connector.connect @nats_address = @setup['rubypitaya.nats.url'] @nats_connector = NatsConnector.new(@nats_address, @service_uuid, @server_name) @database_connector = DatabaseConnector.new(@setup, @log) @database_connector.connect @session = Session.new @postman = Postman.new(@nats_connector) @config = Config.new @config.auto_reload if @is_development_environment @services = ServiceHolder.new @handler_router = HandlerRouter.new(@is_cheats_enabled) @initializer_content = InitializerContent.new(@log, @services, @setup, @config, @handler_router.handlers) @initializer_broadcast = InitializerBroadcast.new @initializer_broadcast.run(@initializer_content) connect_services run_http run_server end private def connect_services @services.all_services.map(&:connect) end def disconnect_services @services.all_services.map(&:disconnect) end def run_http HttpRoutes.set :log, @log HttpRoutes.set :services, @services HttpRoutes.set :setup, @setup HttpRoutes.set :config, @config HttpRoutes.set :views, [Path::HTTP_VIEWS_FOLDER_PATH] + Path::Plugins::HTTP_VIEWS_FOLDER_PATHS HttpRoutes.auto_reload if @is_development_environment Thread.new do HttpRoutes.bind = '0.0.0.0' HttpRoutes.port = '4567' HttpRoutes.run! end end def run_server @is_shutting_down = false catch :sig_shutdown do Signal.trap("SIGINT") { throw :sig_shutdown } Signal.trap("SIGQUIT") { throw :sig_shutdown } Signal.trap("SIGTERM") { throw :sig_shutdown } @log.info "Server started!" run_nats_connection end Signal.trap("SIGINT", nil) Signal.trap("SIGQUIT", nil) Signal.trap("SIGTERM", nil) shutdown_server end def shutdown_server return if @is_shutting_down @is_shutting_down = true @log.info "Server shutting down..." disconnect_services @etcd_connector.disconnect @nats_connector.disconnect @database_connector.disconnect exit(0) end def run_nats_connection loop do @nats_connector.connect do |request| start_time_seconds = Time.now.to_f message_route = request[:msg][:route] message_data = request[:msg][:data] message_json = JSON.parse(message_data) if !message_data.nil? && !message_data.empty? params = Parameters.new(message_json || {}) session_id = request[:session][:id] session_uid = request[:session].fetch(:uid, '') session_data = request[:session][:data] session_data = JSON.parse(session_data, symbolize_names: true) frontend_id = request[:frontendID] metadata = request[:metadata] metadata = JSON.parse(metadata, symbolize_names: true) @session.update(session_id, session_uid, session_data, metadata, frontend_id) handler_name, action_name = message_route.scan(/\A\w+\.(\w+)\.(\w+)\z/)[0] @log.info "request -> route: #{message_route}" @log.info " -> data: #{message_data}" @config.clear_cache response = {} begin response = @handler_router.call(handler_name, action_name, @session, @postman, @services, @setup, @config, @log, params) rescue RouteError => error @log.error "ROUTE ERROR: #{error.code} | #{error.class} | #{error.message} \n #{error.backtrace.join("\n")}" response = { code: error.code, message: error.message } rescue Exception => error @log.error "INTERNAL ERROR: #{error.class} | #{error.message} \n #{error.backtrace.join("\n")}" response = { code: StatusCodes::CODE_INTERNAL_ERROR, message: StatusCodes::MESSAGE_INTERNAL_ERROR, } end response_json = JSON.generate(response) delta_time_seconds = ((Time.now.to_f - start_time_seconds) * 1000).round(2) @log.info "response [#{delta_time_seconds}ms] -> #{response_json}" response_json end rescue Exception => error @log.error "INTERNAL ERROR: #{error.class} | #{error.message}} \n #{error.backtrace.join("\n")}" end end end end