class Jekyll::Commands::Serve

Constants

COMMAND_OPTIONS
DIRECTORY_INDEX
LIVERELOAD_DIR
LIVERELOAD_PORT

Attributes

mutex[R]
run_cond[R]
running[R]
running?[R]

Public Class Methods

init_with_program(prog) click to toggle source
# File lib/jekyll/commands/serve.rb, line 61
def init_with_program(prog)
  prog.command(:serve) do |cmd|
    cmd.description "Serve your site locally"
    cmd.syntax "serve [options]"
    cmd.alias :server
    cmd.alias :s

    add_build_options(cmd)
    COMMAND_OPTIONS.each do |key, val|
      cmd.option key, *val
    end

    cmd.action do |_, opts|
      opts["livereload_port"] ||= LIVERELOAD_PORT
      opts["serving"] = true
      opts["watch"]   = true unless opts.key?("watch")

      # Set the reactor to nil so any old reactor will be GCed.
      # We can't unregister a hook so while running tests we don't want to
      # inadvertently keep using a reactor created by a previous test.
      @reload_reactor = nil

      config = configuration_from_options(opts)
      config["url"] = default_url(config) if Jekyll.env == "development"

      process_with_graceful_fail(cmd, config, Build, Serve)
    end
  end
end
process(opts) click to toggle source
# File lib/jekyll/commands/serve.rb, line 93
def process(opts)
  opts = configuration_from_options(opts)
  destination = opts["destination"]
  if opts["livereload"]
    validate_options(opts)
    register_reload_hooks(opts)
  end
  setup(destination)

  start_up_webrick(opts, destination)
end
shutdown() click to toggle source
# File lib/jekyll/commands/serve.rb, line 105
def shutdown
  @server.shutdown if running?
end

Private Class Methods

boot_or_detach(server, opts) click to toggle source

Keep in our area with a thread or detach the server as requested by the user. This method determines what we do based on what you ask us to do.

# File lib/jekyll/commands/serve.rb, line 274
def boot_or_detach(server, opts)
  if opts["detach"]
    pid = Process.fork do
      server.start
    end

    Process.detach(pid)
    Jekyll.logger.info "Server detached with pid '#{pid}'.",
                       "Run `pkill -f jekyll' or `kill -9 #{pid}' to stop the server."
  else
    t = Thread.new { server.start }
    trap("INT") { server.shutdown }
    t.join
  end
end
create_error_page() click to toggle source
# File lib/jekyll/commands/serve.rb, line 185
def create_error_page
  @header["Content-Type"] = "text/html; charset=UTF-8"
  @body = IO.read(File.join(@config[:DocumentRoot], "404.html"))
end
default_url(opts) click to toggle source
# File lib/jekyll/commands/serve.rb, line 253
def default_url(opts)
  config = configuration_from_options(opts)
  format_url(
    config["ssl_cert"] && config["ssl_key"],
    config["host"] == "127.0.0.1" ? "localhost" : config["host"],
    config["port"]
  )
end
enable_logging(opts) click to toggle source

Make the stack verbose if the user requests it.

# File lib/jekyll/commands/serve.rb, line 291
def enable_logging(opts)
  opts[:AccessLog] = []
  level = WEBrick::Log.const_get(opts[:JekyllOptions]["verbose"] ? :DEBUG : :WARN)
  opts[:Logger] = WEBrick::Log.new($stdout, level)
end
enable_ssl(opts) click to toggle source

Add SSL to the stack if the user triggers –enable-ssl and they provide both types of certificates commonly needed. Raise if they forget to add one of the certificates.

# File lib/jekyll/commands/serve.rb, line 300
def enable_ssl(opts)
  cert, key, src =
    opts[:JekyllOptions].values_at("ssl_cert", "ssl_key", "source")

  return if cert.nil? && key.nil?
  raise "Missing --ssl_cert or --ssl_key. Both are required." unless cert && key

  require "openssl"
  require "webrick/https"

  opts[:SSLCertificate] = OpenSSL::X509::Certificate.new(read_file(src, cert))
  begin
    opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(read_file(src, key))
  rescue StandardError
    if defined?(OpenSSL::PKey::EC)
      opts[:SSLPrivateKey] = OpenSSL::PKey::EC.new(read_file(src, key))
    else
      raise
    end
  end
  opts[:SSLEnable] = true
end
file_handler_opts() click to toggle source

Recreate NondisclosureName under utf-8 circumstance

# File lib/jekyll/commands/serve.rb, line 227
def file_handler_opts
  WEBrick::Config::FileHandler.merge(
    :FancyIndexing     => true,
    :NondisclosureName => [
      ".ht*", "~*",
    ]
  )
end
format_url(ssl_enabled, address, port, baseurl = nil) click to toggle source
# File lib/jekyll/commands/serve.rb, line 245
def format_url(ssl_enabled, address, port, baseurl = nil)
  format("%<prefix>s://%<address>s:%<port>i%<baseurl>s",
         :prefix  => ssl_enabled ? "https" : "http",
         :address => address,
         :port    => port,
         :baseurl => baseurl ? "#{baseurl}/" : "")
end
launch_browser(server, opts) click to toggle source
# File lib/jekyll/commands/serve.rb, line 262
def launch_browser(server, opts)
  address = server_address(server, opts)
  return system "start", address if Utils::Platforms.windows?
  return system "xdg-open", address if Utils::Platforms.linux?
  return system "open", address if Utils::Platforms.osx?

  Jekyll.logger.error "Refusing to launch browser. Platform launcher unknown."
end
mime_types() click to toggle source
# File lib/jekyll/commands/serve.rb, line 352
def mime_types
  file = File.expand_path("../mime.types", __dir__)
  WEBrick::HTTPUtils.load_mime_types(file)
end
mime_types_charset() click to toggle source
# File lib/jekyll/commands/serve.rb, line 357
def mime_types_charset
  SafeYAML.load_file(File.expand_path("serve/mime_types_charset.json", __dir__))
end
read_file(source_dir, file_path) click to toggle source
# File lib/jekyll/commands/serve.rb, line 361
def read_file(source_dir, file_path)
  File.read(Jekyll.sanitized_path(source_dir, file_path))
end
register_reload_hooks(opts) click to toggle source

rubocop:disable Metrics/AbcSize

# File lib/jekyll/commands/serve.rb, line 142
def register_reload_hooks(opts)
  require_relative "serve/live_reload_reactor"
  @reload_reactor = LiveReloadReactor.new

  Jekyll::Hooks.register(:site, :post_render) do |site|
    @changed_pages = []
    site.each_site_file do |item|
      @changed_pages << item if site.regenerator.regenerate?(item)
    end
  end

  # A note on ignoring files: LiveReload errs on the side of reloading when it
  # comes to the message it gets.  If, for example, a page is ignored but a CSS
  # file linked in the page isn't, the page will still be reloaded if the CSS
  # file is contained in the message sent to LiveReload.  Additionally, the
  # path matching is very loose so that a message to reload "/" will always
  # lead the page to reload since every page starts with "/".
  Jekyll::Hooks.register(:site, :post_write) do
    if @changed_pages && @reload_reactor && @reload_reactor.running?
      ignore, @changed_pages = @changed_pages.partition do |p|
        Array(opts["livereload_ignore"]).any? do |filter|
          File.fnmatch(filter, Jekyll.sanitized_path(p.relative_path))
        end
      end
      Jekyll.logger.debug "LiveReload:", "Ignoring #{ignore.map(&:relative_path)}"
      @reload_reactor.reload(@changed_pages)
    end
    @changed_pages = nil
  end
end
server_address(server, options = {}) click to toggle source
# File lib/jekyll/commands/serve.rb, line 236
def server_address(server, options = {})
  format_url(
    server.config[:SSLEnable],
    server.config[:BindAddress],
    server.config[:Port],
    options["baseurl"]
  )
end
setup(destination) click to toggle source

Do a base pre-setup of WEBRick so that everything is in place when we get ready to party, checking for an setting up an error page and making sure our destination exists.

rubocop:disable Security/IoMethods

# File lib/jekyll/commands/serve.rb, line 179
def setup(destination)
  require_relative "serve/servlet"

  FileUtils.mkdir_p(destination)
  if File.exist?(File.join(destination, "404.html"))
    WEBrick::HTTPResponse.class_eval do
      def create_error_page
        @header["Content-Type"] = "text/html; charset=UTF-8"
        @body = IO.read(File.join(@config[:DocumentRoot], "404.html"))
      end
    end
  end
end
start_callback(detached) click to toggle source
# File lib/jekyll/commands/serve.rb, line 323
def start_callback(detached)
  unless detached
    proc do
      mutex.synchronize do
        # Block until EventMachine reactor starts
        @reload_reactor&.started_event&.wait
        @running = true
        Jekyll.logger.info("Server running...", "press ctrl-c to stop.")
        @run_cond.broadcast
      end
    end
  end
end
start_up_webrick(opts, destination) click to toggle source
# File lib/jekyll/commands/serve.rb, line 215
def start_up_webrick(opts, destination)
  @reload_reactor.start(opts) if opts["livereload"]

  @server = WEBrick::HTTPServer.new(webrick_opts(opts)).tap { |o| o.unmount("") }
  @server.mount(opts["baseurl"].to_s, Servlet, destination, file_handler_opts)

  Jekyll.logger.info "Server address:", server_address(@server, opts)
  launch_browser @server, opts if opts["open_url"]
  boot_or_detach @server, opts
end
stop_callback(detached) click to toggle source
# File lib/jekyll/commands/serve.rb, line 337
def stop_callback(detached)
  unless detached
    proc do
      mutex.synchronize do
        unless @reload_reactor.nil?
          @reload_reactor.stop
          @reload_reactor.stopped_event.wait
        end
        @running = false
        @run_cond.broadcast
      end
    end
  end
end
validate_options(opts) click to toggle source

Perform logical validation of CLI options

# File lib/jekyll/commands/serve.rb, line 113
def validate_options(opts)
  if opts["livereload"]
    if opts["detach"]
      Jekyll.logger.warn "Warning:", "--detach and --livereload are mutually exclusive. " \
                                     "Choosing --livereload"
      opts["detach"] = false
    end
    if opts["ssl_cert"] || opts["ssl_key"]
      # This is not technically true.  LiveReload works fine over SSL, but
      # EventMachine's SSL support in Windows requires building the gem's
      # native extensions against OpenSSL and that proved to be a process
      # so tedious that expecting users to do it is a non-starter.
      Jekyll.logger.abort_with "Error:", "LiveReload does not support SSL"
    end
    unless opts["watch"]
      # Using livereload logically implies you want to watch the files
      opts["watch"] = true
    end
  elsif %w(livereload_min_delay
           livereload_max_delay
           livereload_ignore
           livereload_port).any? { |o| opts[o] }
    Jekyll.logger.abort_with "--livereload-min-delay, --livereload-max-delay, " \
                             "--livereload-ignore, and --livereload-port require " \
                             "the --livereload option."
  end
end
webrick_opts(opts) click to toggle source

rubocop:enable Security/IoMethods

# File lib/jekyll/commands/serve.rb, line 194
def webrick_opts(opts)
  opts = {
    :JekyllOptions      => opts,
    :DoNotReverseLookup => true,
    :MimeTypes          => mime_types,
    :MimeTypesCharset   => mime_types_charset,
    :DocumentRoot       => opts["destination"],
    :StartCallback      => start_callback(opts["detach"]),
    :StopCallback       => stop_callback(opts["detach"]),
    :BindAddress        => opts["host"],
    :Port               => opts["port"],
    :DirectoryIndex     => DIRECTORY_INDEX,
  }

  opts[:DirectoryIndex] = [] if opts[:JekyllOptions]["show_dir_listing"]

  enable_ssl(opts)
  enable_logging(opts)
  opts
end