def split(string, limit = nil) string.split(/\s+/, limit) end def report_error(client, whole_command, error) $stderr.puts "=== ERROR WHILE PROCESSING THE COMMAND \"#{whole_command}\" ===\n"\ "#{error.class.name}: #{error.message}\n#{error.backtrace.join("\n")}" client.puts "sorry" end def dev_log(*a) $stdout.puts(*a) if Rails.env.development? end def log(*a) $stdout.puts(*a) unless Rails.env.test? end # ==== Send Format: ======= # One line strings: "#{command} #{arg1} #{arg2} #{etc}" # The last argument, depending on the command, may contain spaces (but usually does not need to) # ========================= # === Receive Format: ===== # Usually one string, like "yes", or "no". # Returns "denied" if an unauthorized command was attempted. # Returns "invalid" if an invalid command was attempted. # Returns "sorry" if an error was raised while processing the command. # Can be a json argument, often following "yes ". # ========================= def address_of(socket) _family, port, name, host = socket.addr if host == name "#{host}:#{port}" else "#{name}(#{host}):#{port}" end end def start_server!(*args) log "Connecting...!" if args.size > 1 port = args.first else port = ENV['port'] || ENV['PORT'] || 2900 end server = TCPServer.new port log "Ready! Using \"#{ActiveRecord::Base.connection.current_database}\" database" client_count = 0 commands = args.last loop do Thread.start(server.accept) do |client| begin client.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) rescue Exception => e puts "Couldn't set TCP_NODELAY" end client_count += 1 dev_log "New client! ##{client_count} #{address_of client}" if Rails.env.development? response_logger = Module.new do def puts(s) $stdout.puts "==> #{s}" super end end client.singleton_class.send :include, response_logger end while line_in = client.gets.chomp log "Processing \"#{line_in}\"" if Rails.env.test? command, rest_of_command = split(line_in, 2) before = Time.now begin command = commands[command.downcase.to_sym] if command.nil? log "SOMEONE attempted invalid command: \"#{line_in}\"" else dev_log "<== #{line_in}" ActiveRecord::Base.connection_pool.with_connection do command.call(client, rest_of_command) client.flush end end rescue StandardError => e report_error(client, line_in, e) rescue Exception => e report_error(client, line_in, e) break end after = Time.now ms = (after - before) * 1000 dev_log %((#{'%.2f' % ms}ms) from #{address_of(client)}) dev_log "" end client_count -= 1 client.close rescue nil dev_log "Client disconnected. #{address_of client}" end end end