class OopRailsServer::RailsServer
Constants
- SERVER_VERIFY_TIMEOUT
- START_SERVER_TIMEOUT
Attributes
actual_rails_version[R]
actual_ruby_engine[R]
actual_ruby_version[R]
gemfile_modifier[R]
name[R]
port[R]
rails_env[R]
rails_root[R]
rails_version[R]
runtime_base_directory[R]
server_pid[R]
template_paths[R]
Public Class Methods
new(options)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 12 def initialize(options) options.assert_valid_keys( :name, :template_paths, :runtime_base_directory, :rails_version, :rails_env, :gemfile_modifier, :log, :verbose ) @name = options[:name] || raise(ArgumentError, "You must specify a name for the Rails server") @template_paths = options[:template_paths] || raise(ArgumentError, "You must specify one or more template paths") @template_paths = Array(@template_paths).map { |t| File.expand_path(t) } @template_paths = base_template_directories + @template_paths @runtime_base_directory = options[:runtime_base_directory] || raise(ArgumentError, "You must specify a runtime_base_directory") @runtime_base_directory = File.expand_path(@runtime_base_directory) @rails_version = options[:rails_version] || :default @rails_env = (options[:rails_env] || 'production').to_s @log = options[:log] || $stderr @verbose = options.fetch(:verbose, true) @gemfile_modifier = options[:gemfile_modifier] || (Proc.new { |gemfile| }) @rails_root = File.expand_path(File.join(@runtime_base_directory, rails_version.to_s, name.to_s)) @port = 20_000 + rand(10_000) @server_pid = nil end
Public Instance Methods
get(path_or_uri, options = { })
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 77 def get(path_or_uri, options = { }) out = get_response(path_or_uri, options) out.body.strip if out end
get_response(path_or_uri, options = { })
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 133 def get_response(path_or_uri, options = { }) send_http_request(path_or_uri, options.merge(:http_method => :get)) end
localhost_name()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 96 def localhost_name "127.0.0.1" end
post(path_or_uri, options = { })
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 73 def post(path_or_uri, options = { }) send_http_request(path_or_uri, options.merge(:http_method => :post)) end
run_command_in_rails_root!(command)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 137 def run_command_in_rails_root!(command) Bundler.with_clean_env do with_rails_env do in_rails_root do safe_system("bundle exec #{command}") end end end end
send_http_request(path_or_uri, options = { })
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 100 def send_http_request(path_or_uri, options = { }) options.assert_valid_keys(:ignore_status_code, :nil_on_not_found, :query, :no_layout, :accept_header, :http_method, :post_variables) uri = uri_for(path_or_uri, options[:query]) data = nil accept_header = options.fetch(:accept_header, 'text/html') http_method = options[:http_method] || :get Net::HTTP.start(uri.host, uri.port) do |http| klass = Net::HTTP.const_get(http_method.to_s.strip.camelize) request = klass.new(uri.to_s) if options[:post_variables] request.set_form_data(options[:post_variables]) end request['Accept'] = accept_header if accept_header data = http.request(request) end if (data.code.to_s != '200') if options[:ignore_status_code] # ok, nothing elsif options[:nil_on_not_found] data = nil else raise "'#{uri}' returned #{data.code.inspect}, not 200; body was: #{data.body.inspect}" end end data end
setup!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 46 def setup! @set_up ||= begin Bundler.with_clean_env do with_rails_env do setup_directories! in_rails_root_parent do splat_bootstrap_gemfile! rails_new! update_gemfile! end in_rails_root do run_bundle_install!(:primary) splat_template_files! end end end true end end
start!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 42 def start! do_start! unless server_pid end
stop!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 69 def stop! stop_server! if server_pid end
uri_for(path_or_uri, query_values = nil)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 82 def uri_for(path_or_uri, query_values = nil) query_values ||= { } if path_or_uri.kind_of?(::URI) path_or_uri else uri_string = "http://#{localhost_name}:#{@port}/#{path_or_uri}" if query_values.length > 0 uri_string += ("?" + query_values.map { |k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join("&")) end URI.parse(uri_string) end end
Private Instance Methods
attempt_bundle_install_cmd!(name, use_local)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 471 def attempt_bundle_install_cmd!(name, use_local) cmd = "bundle install" cmd << " --local" if use_local description = "running 'bundle install' for #{name.inspect}" description << " (with remote fetching allowed)" if (! use_local) attempts = 0 while true begin safe_system(cmd, description) break rescue CommandFailedError => cfe # Sigh. Travis CI sometimes fails this with the following exception: # # Gem::RemoteFetcher::FetchError: Errno::ETIMEDOUT: Connection timed out - connect(2) # # So, we catch the command failure, look to see if this is the problem, and, if so, retry raise if (! is_travis_remote_fetcher_error?(cfe)) || attempts >= 5 attempts += 1 end end end
backcompat_bootstrap_gems!(gemfile)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 619 def backcompat_bootstrap_gems!(gemfile) backcompat_i18n!(gemfile) backcompat_rake!(gemfile) backcompat_rack_cache!(gemfile) backcompat_mime_types!(gemfile) end
backcompat_execjs!(gemfile)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 606 def backcompat_execjs!(gemfile) if is_ruby_18 # Apparently execjs released a version 2.2.0 that will happily install on Ruby 1.8.7, but which contains some # new-style hash syntax. As a result, we pin the version backwards in this one specific case. gemfile.add_version_constraints!('execjs', '~> 2.0.0') end end
backcompat_i18n!(gemfile)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 570 def backcompat_i18n!(gemfile) if is_rails_30 # Since Rails 3.0.20 was released, a new version of the I18n gem, 0.5.2, was released that moves a constant # into a different namespace. (See https://github.com/mislav/will_paginate/issues/347 for more details.) # So, if we're running Rails 3.0.x, we lock the 'i18n' gem to an earlier version. gemfile.add_version_constraints!('i18n', '= 0.5.0') elsif is_ruby_18 # Since Rails 3.x was released, a new version of the I18n gem, 0.7.0, was released that is incompatible # with Ruby 1.8.7. So, if we're running with Ruby 1.8.7, we lock the 'i18n' gem to an earlier version. gemfile.add_version_constraints!('i18n', '< 0.7.0') end end
backcompat_mime_types!(gemfile)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 599 def backcompat_mime_types!(gemfile) if is_ruby_1 # mime-types 3.x depends on mime-types-data, which is not compatible with ruby < 2.x gemfile.add_version_constraints!('mime-types', '< 3.0.0') end end
backcompat_rack_cache!(gemfile)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 590 def backcompat_rack_cache!(gemfile) if is_ruby_1 && (is_rails_31 || is_rails_32) # Since Rails 3.1.12 was released, a new version of the rack-cache gem, 1.3.0, was released that requires # Ruby 2.0 or above. So, if we're running Rails 3.1.x or 3.2.x on Ruby 1.x, we lock the 'rack-cache' gem # to an earlier version. gemfile.add_version_constraints!('rack-cache', '< 1.3.0') end end
backcompat_rake!(gemfile)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 583 def backcompat_rake!(gemfile) if is_ruby_18 # Rake 11 is incompatible with Ruby 1.8 gemfile.add_version_constraints!('rake', '< 11.0.0') end end
backcompat_uglifier!(gemfile)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 614 def backcompat_uglifier!(gemfile) # Uglifier 3 is incompatible with Ruby 1.8 gemfile.add_version_constraints!('uglifier', '< 3.0.0') end
base_template_directories()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 150 def base_template_directories [ File.expand_path(File.join(File.dirname(__FILE__), '../../templates/oop_rails_server_base')) ] end
do_bundle_install!(name, allow_remote)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 504 def do_bundle_install!(name, allow_remote) begin attempt_bundle_install_cmd!(name, true) rescue CommandFailedError => cfe if is_remote_flag_required_error?(cfe) && allow_remote attempt_bundle_install_cmd!(name, false) else raise end end end
do_start!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 156 def do_start! setup! Bundler.with_clean_env do with_rails_env do in_rails_root do start_server! verify_server_and_shut_down_if_fails! end end end end
in_rails_root(&block)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 209 def in_rails_root(&block) Dir.chdir(rails_root, &block) end
in_rails_root_parent(&block)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 213 def in_rails_root_parent(&block) Dir.chdir(File.dirname(rails_root), &block) end
is_alive?(pid)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 416 def is_alive?(pid) cmd = "ps -o pid #{pid}" results = `#{cmd}` results.split(/[\r\n]+/).each do |line| line = line.strip.downcase next if line == 'pid' if line =~ /^\d+$/i return true if Integer(line) == pid else raise "Unexpected output from '#{cmd}': #{results}" end end false end
is_rails_30()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 558 def is_rails_30 rails_version && rails_version =~ /^3\.0\./ end
is_rails_31()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 562 def is_rails_31 rails_version && rails_version =~ /^3\.1\./ end
is_rails_32()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 566 def is_rails_32 rails_version && rails_version =~ /^3\.2\./ end
is_remote_flag_required_error?(command_failed_error)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 499 def is_remote_flag_required_error?(command_failed_error) command_failed_error.output =~ /could\s+not\s+find.*in\s+the\s+gems\s+available\s+on\s+this\s+machine/mi || command_failed_error.output =~ /could\s+not\s+find.*in\s+any\s+of\s+the.*\s+sources/mi end
is_ruby_1()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 554 def is_ruby_1 !! (RUBY_VERSION =~ /^1\./) end
is_ruby_18()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 550 def is_ruby_18 !! (RUBY_VERSION =~ /^1\.8\./) end
is_travis_remote_fetcher_error?(command_failed_error)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 495 def is_travis_remote_fetcher_error?(command_failed_error) command_failed_error.output =~ /Gem::RemoteFetcher::FetchError.*connect/i end
rails_new!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 229 def rails_new! # This is a little trick to specify the exact version of Rails you want to create it with... # http://stackoverflow.com/questions/379141/specifying-rails-version-to-use-when-creating-a-new-application rails_version_spec = rails_version == :default ? "" : "_#{rails_version}_" cmd = "bundle exec rails #{rails_version_spec} new #{File.basename(rails_root)} -d sqlite3 -f -B" safe_system(cmd, "creating a new Rails installation for '#{name}'") end
raise_startup_failed_error!(elapsed_time, exception)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 179 def raise_startup_failed_error!(elapsed_time, exception) last_lines = server_logfile = nil if File.exist?(server_output_file) && File.readable?(server_output_file) server_logfile = server_output_file File::Tail::Logfile.open(server_output_file, :break_if_eof => true) do |f| f.extend(File::Tail) last_lines ||= [ ] begin f.tail(100) { |l| last_lines << l } rescue File::Tail::BreakException # ok end end end raise FailedStartupError.new(elapsed_time, exception, server_logfile, last_lines) end
run_bundle_install!(name)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 516 def run_bundle_install!(name) @bundle_installs_run ||= { } do_bundle_install!(name, ! @bundle_installs_run[name]) @bundle_installs_run[name] ||= true end
safe_system(cmd, notice = nil, options = { })
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 533 def safe_system(cmd, notice = nil, options = { }) say("#{notice}...", false) if notice total_cmd = if options[:background] "#{cmd} 2>&1 &" else "#{cmd} 2>&1" end output = `#{total_cmd}` raise CommandFailedError.new(Dir.pwd, total_cmd, $?, output) unless $?.success? say "OK" if notice output end
say(s, newline = true)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 522 def say(s, newline = true) if @verbose if newline @log.puts s else @log << s end @log.flush end end
server_output_file()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 290 def server_output_file @server_output_file ||= File.join(rails_root, 'log', 'rails-server.out') end
set_env(new_env)
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 262 def set_env(new_env) new_env.each do |k,v| if v ENV[k] = v else ENV.delete(k) end end end
setup_directories!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 197 def setup_directories! return if @directories_setup template_paths.each do |template_path| raise Errno::ENOENT, "You must specify template paths that exist; this doesn't: '#{template_path}'" unless File.directory?(template_path) end FileUtils.rm_rf(rails_root) if File.exist?(rails_root) FileUtils.mkdir_p(rails_root) @directories_setup = true end
splat_bootstrap_gemfile!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 217 def splat_bootstrap_gemfile! rails_version_specs = if rails_version == :default then [ ] else [ "= #{rails_version}" ] end gemfile = ::OopRailsServer::Gemfile.new("Gemfile") gemfile.add_version_constraints!("rails", *rails_version_specs) backcompat_bootstrap_gems!(gemfile) gemfile.write! run_bundle_install!(:bootstrap) end
splat_template_files!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 272 def splat_template_files! @template_paths.each do |template_path| Find.find(template_path) do |file| next unless File.file?(file) if file[0..(template_path.length)] == "#{template_path}/" subpath = file[(template_path.length + 1)..-1] else raise "#{file} isn't under #{template_path}?!?" end dest_file = File.join(rails_root, subpath) FileUtils.mkdir_p(File.dirname(dest_file)) FileUtils.cp(file, dest_file) end end end
start_server!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 296 def start_server! output = server_output_file cmd = "bundle exec rails server -p #{port} > '#{output}' 2>&1" safe_system(cmd, "starting 'rails server' on port #{port}", :background => true) server_pid_file = File.join(rails_root, 'tmp', 'pids', 'server.pid') start_time = Time.now while Time.now < start_time + START_SERVER_TIMEOUT if File.exist?(server_pid_file) server_pid = File.read(server_pid_file).strip if server_pid =~ /^(\d{1,10})$/i @server_pid = Integer(server_pid) break end end sleep 0.1 end unless server_pid raise "Unable to start the Rails server even after #{Time.now - start_time} seconds; there seems to be no file at '#{server_pid_file}', or no PID in that file if it does exist. Help!" end end
stop_server!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 432 def stop_server! # We do this because under 1.8.7 SIGTERM doesn't seem to work, and it's actually fine to slaughter this # process mercilessly -- we don't need anything it has at this point, anyway. Process.kill("KILL", server_pid) start_time = Time.now while true if is_alive?(server_pid) raise "Unable to kill server at PID #{server_pid}!" if (Time.now - start_time) > 20 say "Waiting for server at PID #{server_pid} to die." sleep 0.1 else break end end say "Successfully terminated Rails server at PID #{server_pid}." @server_pid = nil end
update_gemfile!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 237 def update_gemfile! gemfile = ::OopRailsServer::Gemfile.new(File.join(rails_root, 'Gemfile')) backcompat_bootstrap_gems!(gemfile) backcompat_execjs!(gemfile) backcompat_uglifier!(gemfile) gemfile_modifier.call(gemfile) gemfile.write! end
verify_server!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 372 def verify_server! server_verify_url = "http://#{localhost_name}:#{port}/working/rails_is_working" uri = URI.parse(server_verify_url) data = nil start_time = Time.now last_exception = nil while Time.now < (start_time + SERVER_VERIFY_TIMEOUT) sleep 0.1 begin data = Net::HTTP.get_response(uri) last_exception = nil rescue Errno::ECONNREFUSED, EOFError => e last_exception = e end break if data && data.code && data.code.to_s == '200' end unless data && data.code && data.code.to_s == '200' raise_startup_failed_error!(Time.now - start_time, last_exception || "'#{server_verify_url}' returned #{data.code.inspect}, not 200") end result = data.body.strip unless result =~ /^Rails\s+version\s*:\s*(\d+\.\d+\.\d+(\.\d+)?)\s*\n+\s*Ruby\s+version\s*:\s*(\d+\..*?)\s*\n+\s*Ruby\s+engine:\s*(.*?)\s*\n?$/mi raise_startup_failed_error!(Time.now - start_time, "'#{server_verify_url}' returned: #{result.inspect}") end actual_version = $1 ruby_version = $3 ruby_engine = $4 if rails_version != :default && (actual_version != rails_version) raise "We seem to have spawned the wrong version of Rails; wanted: #{rails_version.inspect} but got: #{actual_version.inspect}" end @actual_rails_version = actual_version @actual_ruby_version = ruby_version @actual_ruby_engine = ruby_engine say "Successfully spawned a server running Rails #{actual_version} (Ruby #{ruby_version}, engine #{ruby_engine.inspect}) on port #{port}." end
verify_server_and_shut_down_if_fails!()
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 321 def verify_server_and_shut_down_if_fails! begin verify_server! rescue Exception => e say "Verification of Rails server failed:\n #{e.message} (#{e.class.name})\n #{e.backtrace.join("\n ")}" begin stop_server! rescue Exception => e say "WARNING: Verification of server failed, so we tried to stop it, but we couldn't do that. Proceeding, but you may have a Rails server left around anyway. The exception from trying to stop the server was:\n #{e.message} (#{e.class.name})\n #{e.backtrace.join("\n ")}" end raise end end
with_env(new_env) { || ... }
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 250 def with_env(new_env) old_env = { } new_env.keys.each { |k| old_env[k] = ENV[k] } begin set_env(new_env) yield ensure set_env(old_env) end end
with_rails_env() { || ... }
click to toggle source
# File lib/oop_rails_server/rails_server.rb, line 169 def with_rails_env old_rails_env = ENV['RAILS_ENV'] begin ENV['RAILS_ENV'] = rails_env yield ensure ENV['RAILS_ENV'] = old_rails_env end end