Skip to content

Introduce explicit rails server handler option #32058

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 4, 2018

Conversation

gsamokovarov
Copy link
Contributor

@gsamokovarov gsamokovarov commented Feb 19, 2018

I mistype rails server production instead of rails server -e production expecting to lunch a server in the production environment
all the time. However, the signature of rails server --help is:

Usage:
  rails server [puma, thin etc] [options]

This means that the production argument is being interpreted as a Rack
server handler like Puma, Thin or Unicorn.

Should we argue for the rails server production? I'm not sure of the
reasons, but the rails console production behavior was deprecated in:
#29358, so parity with the existing
rails console production usage may not hold anymore.

In any case, this PR introduces an explicit option for the Rack servers
configuration. I call them --handlers (or -x for short, as -h is
already as the shortcut for --help) to avoid the rails server --server tantrum.

The new interface of rails server is:

Usage:
  rails server [handler] [options]

Options:
  -p, [--port=port]                        # Runs Rails on the specified port - defaults to 3000.
  -b, [--binding=IP]                       # Binds Rails to the specified IP - defaults to 'localhost' in development and '0.0.0.0' in other environments'.
  -c, [--config=file]                      # Uses a custom rackup configuration.
                                           # Default: config.ru
  -d, [--daemon], [--no-daemon]            # Runs server as a Daemon.
  -e, [--environment=name]                 # Specifies the environment to run this server under (development/test/production).
  -x, [--handler=name]                     # Specifies the Rack server to run the application (thin/puma/webrick).
  -P, [--pid=PID]                          # Specifies the PID file.
                                           # Default: tmp/pids/server.pid
  -C, [--dev-caching], [--no-dev-caching]  # Specifies whether to perform caching in development.
      [--early-hints], [--no-early-hints]  # Enables HTTP/2 early hints.

As a bonus, if you mistype the handler, you'll get an auto-correction
message:

$ rails s tin
Could not find handler 'tin'. Maybe you meant 'thin' or 'cgi'?
Run `rails server --help` for more options.

@rails-bot
Copy link

r? @schneems

(@rails-bot has picked a reviewer for you, use r? to override)

@gsamokovarov
Copy link
Contributor Author

The previous error was:

$ rails server production
Exiting
Traceback (most recent call last):
	36: from bin/rails:3:in `<main>'
	35: from bin/rails:3:in `load'
	34: from /Users/genadi/Development/bithost/dashboard.bithost.io/bin/spring:15:in `<top (required)>'
	33: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:70:in `require'
	32: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:70:in `require'
	31: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/spring-2.0.2/lib/spring/binstub.rb:31:in `<top (required)>'
	30: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/spring-2.0.2/lib/spring/binstub.rb:31:in `load'
	29: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/spring-2.0.2/bin/spring:49:in `<top (required)>'
	28: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/spring-2.0.2/lib/spring/client.rb:30:in `run'
	27: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/spring-2.0.2/lib/spring/client/command.rb:7:in `call'
	26: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/spring-2.0.2/lib/spring/client/rails.rb:28:in `call'
	25: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/spring-2.0.2/lib/spring/client/rails.rb:28:in `load'
	24: from /Users/genadi/Development/bithost/dashboard.bithost.io/bin/rails:9:in `<top (required)>'
	23: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0.rc1/lib/active_support/dependencies.rb:283:in `require'
	22: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0.rc1/lib/active_support/dependencies.rb:249:in `load_dependency'
	21: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0.rc1/lib/active_support/dependencies.rb:283:in `block in require'
	20: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/bootsnap-1.1.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:17:in `require'
	19: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/bootsnap-1.1.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:17:in `require'
	18: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-5.2.0.rc1/lib/rails/commands.rb:18:in `<main>'
	17: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-5.2.0.rc1/lib/rails/command.rb:46:in `invoke'
	16: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-5.2.0.rc1/lib/rails/command/base.rb:65:in `perform'
	15: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-0.20.0/lib/thor.rb:387:in `dispatch'
	14: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-0.20.0/lib/thor/invocation.rb:126:in `invoke_command'
	13: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-0.20.0/lib/thor/command.rb:27:in `run'
	12: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-5.2.0.rc1/lib/rails/commands/server/server_command.rb:142:in `perform'
	11: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-5.2.0.rc1/lib/rails/commands/server/server_command.rb:142:in `tap'
	10: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-5.2.0.rc1/lib/rails/commands/server/server_command.rb:147:in `block in perform'
	 9: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-5.2.0.rc1/lib/rails/commands/server/server_command.rb:47:in `start'
	 8: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-5.2.0.rc1/lib/rails/commands/server/server_command.rb:76:in `print_boot_information'
	 7: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-5.2.0.rc1/lib/rails/commands/server/server_command.rb:105:in `use_puma?'
	 6: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/server.rb:301:in `server'
	 5: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/handler.rb:16:in `get'
	 4: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/handler.rb:74:in `try_require'
	 3: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0.rc1/lib/active_support/dependencies.rb:283:in `require'
	 2: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0.rc1/lib/active_support/dependencies.rb:249:in `load_dependency'
	 1: from /Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0.rc1/lib/active_support/dependencies.rb:283:in `block in require'
/Users/genadi/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/bootsnap-1.1.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:19:in `require': cannot load such file -- rack/handler/production (LoadError)

Copy link
Contributor

@kaspth kaspth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 to adding the argument validation. But I don't understand why it follows that we should turn the server positional arg into an option?


msg = "Could not find handler '#{handler}'. ".dup
msg << "Maybe you meant #{suggestions.first} or #{suggestions.second}?\n"
msg << "Run `rails server --help` for more options."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're on Ruby 2.3 now, so lets use the squiggly heredoc <<~

HANDLERS = %w[cgi fastcgi webrick lsws scgi thin puma unicorn]

def self.message(handler)
suggestions = HANDLERS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this might be clearer as:

suggestions = HANDLERS.sort_by { |h| levenshstein_distance(handler, h) }.map(&:inspect)

Don't think the first(2) makes sense since we use first and second below — there's only 8 things in the array, we aren't saving anything with it anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only subtle difference with .map(&:inspect) is that the message contains double quotes instead of single ones.


# Hard-coding a bunch of handlers here as we don't have a public way of
# querying them from the Rack::Handler registry.
HANDLERS = %w[cgi fastcgi webrick lsws scgi thin puma unicorn]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might prefer %w( … )

@@ -122,6 +148,8 @@ class ServerCommand < Base # :nodoc:
desc: "Runs server as a Daemon."
class_option :environment, aliases: "-e", type: :string,
desc: "Specifies the environment to run this server under (development/test/production).", banner: :name
class_option :handler, aliases: "-x", type: :string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know Rack uses handler, but it reads a tad too generic here for me — especially if the shorthand is -x. How about -r for runner? E.g. the thing to run the rails server.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--using? Runner seems particularly problematic given rails runner.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matthewd yeah, definitely thought that -r is on sketchy ground because of that clash. I like --using. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--using sounds good to me too.


def test_handler_mistype_in_positional_argument
run_command(["tink"])
assert_match(/Could not find handler 'tink'. Maybe you meant 'thin' or 'cgi'/, output)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think attr_reader :output and @output makes sense. We can just do:

assert_match(/Could not find handler 'tink'. Maybe you meant 'thin' or 'cgi'/, run_command(["tink"]))

Then it'll match our test/application/test_runner_test.rb tests too.


def test_handler_mistype
run_command(["-x", "tin"])
assert_match(/Could not find handler 'tin'. Maybe you meant 'thin' or 'cgi'/, output)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How deterministic is it that cgi will be the other option here? I don't think we'd want intermittent test failures here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handlers list is hard-coded, as we don't have a clean way to query the handlers from rack/lib/rack/handler.rb. If the order of the HANDLERS doesn't change often, the test should be stable.

@matthewd
Copy link
Member

matthewd commented Feb 24, 2018

@kaspth we removed the positional parameter for console in #29358; I don't see why we'd want to retain it for server but not console.

(Specifically, given that we can only load a handler that's available in the Gemfile, and that we automatically load the first handler that's available in the Gemfile, it seems pretty rare to me for people to have cause to name a handler at all.)

.map! { |s| "'#{s}'" }

msg = "Could not find handler '#{handler}'. ".dup
msg << "Maybe you meant #{suggestions.first} or #{suggestions.second}?\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICS this could end up suggesting exactly what they typed, which seems not-good. If handler is in HANDLERS, maybe we should instead suggest that they're missing a Gemfile entry.

@kaspth
Copy link
Contributor

kaspth commented Feb 24, 2018

@matthewd sure, but that was for the environment, no? I thought bin/rails dbconsole replicant -e production was still possible?

Anyway, you're right that passing it manually is probably a minority case, so lets go with the deprecation — and --using 👍

@gsamokovarov
Copy link
Contributor Author

I have changed the option to --using and deprecated the passing of the Rack server as a regular argument.

@kaspth
Copy link
Contributor

kaspth commented Feb 25, 2018

This is probably for another time, but… I just realized that now we're on 2.3+ we could leverage the DidYouMean gem's spellchecker: https://github.com/yuki24/did_you_mean/blob/4d4484940cdab27566538e6453ff428d39fbad7a/lib/did_you_mean/spell_checker.rb#L8

unless HANDLERS.include?(options[:using])
  DidYouMean::SpellChecker.new(HANDLERS).correct(options[:using])
end
irb(main):002:0> c = DidYouMean::SpellChecker.new(dictionary: [ 'cgi', 'puma' ])
=> #<DidYouMean::SpellChecker:0x00007fa055866ee0 @dictionary=["cgi", "puma"]>
irb(main):003:0> c.correct('cgy')
=> ["cgi"]
irb(main):004:0> c.correct('pumma')
=> ["puma"]

We could also remove Rails' Levenshtein distance method from other places.

Copy link
Contributor

@kaspth kaspth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting real close!

Could not find server "#{handler}". Maybe you meant #{suggestions.first} or #{suggestions.second}?
Run `rails server --help` for more options.
MSG
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing argv validation seems like it should a concern of the command and not the server itself. Both include Rails::Command::Behavior and rails server --help seem to allude as much.

Could we move it to the command if we do this?

capturing_missing_handler do
  server.start
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried that as well – it's still messy because levenshtein_distance is a private class method:

        def suggest_rack_server(handler)
          if handler.in?(HANDLERS)
            <<~MSG
              Could not load server "#{handler}". Maybe you need the add it to the Gemfile?

                gem "#{handler}"

              Run `rails server --help` for more options.
            MSG
          else
            suggestions = HANDLERS.sort_by { |h| self.class.send(:levenshtein_distance, handler, h) }.map(&:inspect)

            <<~MSG
              Could not find server "#{handler}". Maybe you meant #{suggestions.first} or #{suggestions.second}?
              Run `rails server --help` for more options.
            MSG
          end
        end

# The '-h' option calls exit before @options is set.
# If we call 'options' with it unset, we get double help banners.
puts "Exiting" unless @options && options[:daemonize]
unless missing_handler
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing the stopgap to inlining the HandlerSuggestion module in the command is this unless. But I don't fully understand why we need it? Isn't it fine for the server to say Exiting in the off case that a handler is missing?

Copy link
Contributor Author

@gsamokovarov gsamokovarov Feb 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the stopgap is the levenstein_distance method being private and defined in the Rails::Command::Behavior module which carries command specific helpers. Decided to at least isolate it. I'd love to revisit the levenshtein_distance usage or swap it for the algo in DidYouMean, but I'd like to do it in a follow-up change, as it will have to touch the generator commands as well.

The guard here is for my personal aesthetic reasons. 😅 This is the message with the trailing Exiting:

Could not load server "unicorn". Maybe you need the add it to the Gemfile?

  gem "unicorn"

Run `rails server --help` for more options.
Exiting

The Exiting line seems redundant to me, especially with "Run rails server --help for more options." as a final suggestion. Maybe the user mistyped an option and triggered this?

Can an option be to complicate the existing guard a bit and move the missing_handler check there?

    # ...
    ensure
      # The '-h' option calls exit before @options is set.
      # If we call 'options' with it unset, we get double help banners.
      puts "Exiting" unless @options && options[:daemonize] || missing_handler
    end

@@ -132,7 +170,17 @@ class ServerCommand < Base # :nodoc:
def initialize(args = [], local_options = {}, config = {})
@original_options = local_options
super
@server = self.args.shift
@server = if using
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'd probably extract the message into a method like below, but it's probably fine like this :)

@server = deprecate_positional_using(using) || options[:using]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I think I can extract it in a separate method.

@@ -132,7 +170,17 @@ class ServerCommand < Base # :nodoc:
def initialize(args = [], local_options = {}, config = {})
@original_options = local_options
super
@server = self.args.shift
@server = if using
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use <<~ here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still needs the squish; deprecation messages should not be hard-wrapped on output.

print_boot_information
trap(:INT) { exit }
create_tmp_directories
setup_dev_caching
log_to_stdout if options[:log_stdout]

super
rescue LoadError
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make sure to only rescue a LoadError for "rack/handler/#{options[:using]}"; if something else is wrong, the original exception will do a better job of describing the issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rack::Handler.get can raise either LoadError or NameError, if the Puma constant does not exist after the rack/handler/puma require, for example. I think I can override the Rack::Server#server method to isolate the error catch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can not deal with this by explicitly calling Rack::Handler.get before starting server?
I am also concerned that other errors by LoadError will be wrapped by this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can, I'll do the changes accordingly.

@gsamokovarov
Copy link
Contributor Author

@kaspth, I think I'll be able to get the printing out of the Rails::Server class and put it into the command. I had a couple of naive attempts but part of the printing needs to know about whether Rails::Server#server is puma and this crosses some boundaries, but I'm sure we can get it.

As for moving the handler suggestion logic entirely into the Rails::Command::ServerCommand it may not be as pretty because of the current Rails::Command::Behavior concern ahem, behavior 😅, that introduces the levenstein_distance method as private class method, which is pretty specific to the first place it was used.


Now:

$ bin/rails dbconsole -u thin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bin/rails server ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! 😅

Run `rails server --help` for more options.
MSG
else
suggestions = HANDLERS.sort_by { |h| levenshtein_distance(handler, h) }.map(&:inspect)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious. Is the user using the server option so much that mistyped check is needed?
I understand that mistakenly specify server as env. But will not it solve the necessity of option to specify server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@y-yagi doubt it's used often. This tries to give you more context on what actually happens when you pass a positional argument. I'm setting the suggestions here by suggestion (rim-shot). 😅 Anyway, I think it's quite easy to setup the suggestions and we can follow the example in more command options that accept a fixed set of values.

@gsamokovarov
Copy link
Contributor Author

Friends, I have moved the printing out of Rails::Server. Want to take another look? There are still some funky bits, but I think we're getting there.

Dir.chdir(Rails.application.root)

server = Rails::Server.new(server_options)
handler = begin
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can wrap this in a capturing method.

return
end

begin
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. The purpose of the Exiting message is to show up, for example, when you CTRL-C/terminate a running server.


def print_boot_information(handler)
use_puma = handler.to_s == "Rack::Handler::Puma"
url = "on #{options[:SSLEnable] ? 'https' : 'http'}://#{host}:#{port}" unless use_puma
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url can be nil here, if we're not on Puma.

Copy link
Contributor

@kaspth kaspth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like where you took this in this round, Genadi! Hadn't thought of extracting all the printing to the command.

Also don't be afraid to say if you've had enough review rounds and would rather focus on wrapping it up. Then we'll find a way to cut it.

I've been using this case to ease back into the command infrastructure and what exactly the responsibility of a command should be. So if it feels like it's playing around a little bit — that's because it is! 😄

def handler
server
rescue LoadError, NameError
raise MissingError
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

def serveable? # :nodoc:
  server
  true
rescue LoadError, NameError
  false
end

Then in the command:

unless server.serveable?
  say HandlerSuggestion.message(using) # We ought to use `say` now that we're in the command.
  return
end

Hell, we could even get server.start to yield to a finalizer block when it exits. Then it'd be:

server.start do |exited_successfully|
  if exited_successfully
    say "Exiting" unless options[:daemon]
  else
    say HandlerSuggestion.message(using)
  end
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh. The last suggestion won't work because of print_boot_information. Maybe there's still something useful we can dig out of the suggestion though? 😊

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kasper, I have tried something similar in the last round. I wasn't able to extract all the params out of print_boot_information, because using can be nil, or empty if we coerce it, but that still won't give us a server name to print.

Run `rails server --help` for more options.
MSG
else
suggestions = HANDLERS.sort_by { |h| levenshtein_distance(handler, h) }.map(&:inspect)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this being private is what stops us from removing this HandlerSuggestion then lets make it public. It's marked as :doc: anyway, so it's already public API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also a class method, so I'd have to call it through self.class. We go through lots of hoops to use it now, so I'll extract it in a Rails::Command::Spellchecker module as we already blew up the PR a bit. 😅

url = "on #{ssl ? 'https' : 'http'}://#{host}:#{port}" unless use_puma

puts <<~MSG
=> Booting #{ActiveSupport::Inflector.demodulize(handler)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this just use using?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=> Booting Puma, where using is usually puma. But yeah, we can save this argument pass.


def print_boot_information(handler, ssl)
use_puma = handler.to_s == "Rack::Handler::Puma"
url = "on #{ssl ? 'https' : 'http'}://#{host}:#{port}" unless use_puma
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's let the server build this URL in a nodoc'ed served_url, so we can do server.served_url below.

Then we can keep use_puma?

@@ -132,27 +155,34 @@ class ServerCommand < Base # :nodoc:
def initialize(args = [], local_options = {}, config = {})
@original_options = local_options
super
@server = self.args.shift
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better and clearer to do this than the @_using construct:

super

deprecate_positional_using if @using
@using ||= options[:using]

@log_stdout = 

@gsamokovarov
Copy link
Contributor Author

Don't worry Kasper I'm having fun as well. 😀

@@ -9,7 +9,7 @@
require "rails/dev_caching"

module Rails
class Server < ::Rack::Server
class Server < ::Rack::Server #:nodoc:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could technically be a backward incompatible change, but:

  1. I don't think we should list Rails::Server in the documentation at all.
  2. This change aims Rails 6, so it should be okay.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We have listed it in the past, so it's technically public API.
  2. Just because it's a major doesn't mean we should just make undue backwards incompatible changes willy-nilly.

I am learning towards the Rails::Server published as public API being a mistake and not being intended. I don't think any of the other Railties command helpers are public.

What does a GitHub search for Rails::Server say? It might corroborate the above 😊

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we find there's no real usage of Rails::Server on GitHub, I'm 👍 on # :nodoc: (but do add the space so it matches the command nodoc 🤓)

Copy link
Contributor

@kaspth kaspth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really like how this turned out 👍

Feels right that the command handles the error printing and the server is more focused on its own boot.

Some minor nits left, then I'll merge.

server.start

if server.serveable?
print_boot_information(server.server, server.served_url)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put a newline after this.

def rack_server_suggestion(server)
if server.in?(RACK_SERVERS)
<<~MSG
Could not load server "#{server}". Maybe you need the add it to the Gemfile?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to

<<~MSG
Could not find server "#{server}". Maybe you meant #{suggestions.first} or #{suggestions.second}?
Run `rails server --help` for more options.
MSG
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For another time: I could totally see us enriching Thor's argument/option with a :suggestions to get messages like this for free. E.g.:

class_option :using, aliases: "-u", type: :string, suggestions: RACK_SERVERS,
  desc: "Specifies the Rack server used to run the application (thin/puma/webrick).", banner: :name

trap(:INT) { exit }
create_tmp_directories
setup_dev_caching
log_to_stdout if options[:log_stdout]

super
ensure
# The '-h' option calls exit before @options is set.
# If we call 'options' with it unset, we get double help banners.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍, dig that we got rid of these comments.

@@ -9,7 +9,7 @@
require "rails/dev_caching"

module Rails
class Server < ::Rack::Server
class Server < ::Rack::Server #:nodoc:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we find there's no real usage of Rails::Server on GitHub, I'm 👍 on # :nodoc: (but do add the space so it matches the command nodoc 🤓)

@kaspth
Copy link
Contributor

kaspth commented Mar 3, 2018

Oh, needs a commit squash and rebase too! Normally we'd want 1 commit, but I'll grant you a commit for the spellchecker change first, then one for the server changes after that if you want. 1 commit is fine too 👍

@gsamokovarov gsamokovarov force-pushed the rails-server-x-option branch 2 times, most recently from 245c603 to dd8b60e Compare March 4, 2018 15:06
I mistype `rails server production` instead of `rails server -e
production` expecting to lunch a server in the production environment
all the time. However, the signature of `rails server --help` is:

```
Usage:
  rails server [puma, thin etc] [options]
```

This means that the `production` argument is being interpreted as a Rack
server handler like Puma, Thin or Unicorn.

Should we argue for the `rails server production`? I'm not sure of the
reasons, but the `rails console production` behavior was deprecated in:
rails#29358, so parity with the existing
`rails console production` usage may not hold anymore.

In any case, this PR introduces an explicit option for the Rack servers
configuration. The option is called `--using` (or `-u` for short) to
avoid the `rails server --server` tantrum.

The new interface of `rails server` is:

```
Usage:
  rails server [using] [options]

Options:
  -p, [--port=port]                        # Runs Rails on the specified port - defaults to 3000.
  -b, [--binding=IP]                       # Binds Rails to the specified IP - defaults to 'localhost' in development and '0.0.0.0' in other environments'.
  -c, [--config=file]                      # Uses a custom rackup configuration.
                                           # Default: config.ru
  -d, [--daemon], [--no-daemon]            # Runs server as a Daemon.
  -e, [--environment=name]                 # Specifies the environment to run this server under (development/test/production).
  -u, [--using=name]                       # Specifies the Rack server used to run the application (thin/puma/webrick).
  -P, [--pid=PID]                          # Specifies the PID file.
                                           # Default: tmp/pids/server.pid
  -C, [--dev-caching], [--no-dev-caching]  # Specifies whether to perform caching in development.
      [--early-hints], [--no-early-hints]  # Enables HTTP/2 early hints.
```

As a bonus, if you mistype the server to use, you'll get an
auto-correction message:

```
$ rails s tin
Could not find handler "tin". Maybe you meant "thin" or "cgi"?
Run `rails server --help` for more options.
```
@gsamokovarov gsamokovarov force-pushed the rails-server-x-option branch from dd8b60e to a951729 Compare March 4, 2018 15:53
@gsamokovarov
Copy link
Contributor Author

Kasper, I have searched for Rails::Server usage in GitHub, but the two keywords: rails and server yield a lot of uninteresting results for us. See https://help.github.com/articles/searching-code:

You can't use the following wildcard characters as part of your search query: . , : ; / \ ` ' " = * ! ? # $ & + ^ | ~ < > ( ) { } [ ]. The search will simply ignore these symbols.

This means that we cannot match Rails::Server exactly, because of the : character ignore and we can't be exactly sure whether it's used. I left the # :nodoc: only to the serveable? method.

@kaspth kaspth merged commit f4ddbff into rails:master Mar 4, 2018
@kaspth
Copy link
Contributor

kaspth commented Mar 4, 2018

Fine with me! Really glad how this turned out 👏

@gsamokovarov
Copy link
Contributor Author

Thanks for the review! 🙇‍♂️

@gsamokovarov gsamokovarov deleted the rails-server-x-option branch March 4, 2018 21:33
@kaspth
Copy link
Contributor

kaspth commented Jul 7, 2018

Did some work on the server command a0061d2...1d97b8f and realized I forgot to backport this to 5-2-stable 🤭

y-yagi added a commit that referenced this pull request Jan 18, 2019
…mand"

This reverts commit fa791fb.

Reason: `server` argument was deprecated in Rails 6.0. Ref: #32058.
kaspth pushed a commit that referenced this pull request Mar 11, 2019
- Because just passing the server argument to this command is
  deprecated in #32058
@kunal6673
Copy link

Recover all my page

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants