diff options
Diffstat (limited to 'railties/lib/rails/application')
-rw-r--r-- | railties/lib/rails/application/bootstrap.rb | 87 | ||||
-rw-r--r-- | railties/lib/rails/application/configuration.rb | 250 | ||||
-rw-r--r-- | railties/lib/rails/application/default_middleware_stack.rb | 99 | ||||
-rw-r--r-- | railties/lib/rails/application/finisher.rb | 194 | ||||
-rw-r--r-- | railties/lib/rails/application/routes_reloader.rb | 69 |
5 files changed, 699 insertions, 0 deletions
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb new file mode 100644 index 0000000000..44b49556d7 --- /dev/null +++ b/railties/lib/rails/application/bootstrap.rb @@ -0,0 +1,87 @@ +require "fileutils" +require "active_support/notifications" +require "active_support/dependencies" +require "active_support/descendants_tracker" +require_relative "../secrets" + +module Rails + class Application + module Bootstrap + include Initializable + + initializer :load_environment_hook, group: :all do end + + initializer :load_active_support, group: :all do + require "active_support/all" unless config.active_support.bare + end + + initializer :set_eager_load, group: :all do + if config.eager_load.nil? + warn <<-INFO +config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly: + + * development - set it to false + * test - set it to false (unless you use a tool that preloads your test environment) + * production - set it to true + +INFO + config.eager_load = config.cache_classes + end + end + + # Initialize the logger early in the stack in case we need to log some deprecation. + initializer :initialize_logger, group: :all do + Rails.logger ||= config.logger || begin + path = config.paths["log"].first + unless File.exist? File.dirname path + FileUtils.mkdir_p File.dirname path + end + + f = File.open path, "a" + f.binmode + f.sync = config.autoflush_log # if true make sure every write flushes + + logger = ActiveSupport::Logger.new f + logger.formatter = config.log_formatter + logger = ActiveSupport::TaggedLogging.new(logger) + logger + rescue StandardError + logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDERR)) + logger.level = ActiveSupport::Logger::WARN + logger.warn( + "Rails Error: Unable to access log file. Please ensure that #{path} exists and is writable " \ + "(ie, make it writable for user and group: chmod 0664 #{path}). " \ + "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." + ) + logger + end + + Rails.logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase) + end + + # Initialize cache early in the stack so railties can make use of it. + initializer :initialize_cache, group: :all do + unless Rails.cache + Rails.cache = ActiveSupport::Cache.lookup_store(config.cache_store) + + if Rails.cache.respond_to?(:middleware) + config.middleware.insert_before(::Rack::Runtime, Rails.cache.middleware) + end + end + end + + # Sets the dependency loading mechanism. + initializer :initialize_dependency_mechanism, group: :all do + ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load + end + + initializer :bootstrap_hook, group: :all do |app| + ActiveSupport.run_load_hooks(:before_initialize, app) + end + + initializer :set_secrets_root, group: :all do + Rails::Secrets.root = root + end + end + end +end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb new file mode 100644 index 0000000000..4797223380 --- /dev/null +++ b/railties/lib/rails/application/configuration.rb @@ -0,0 +1,250 @@ +require "active_support/core_ext/kernel/reporting" +require "active_support/file_update_checker" +require_relative "../engine/configuration" +require_relative "../source_annotation_extractor" + +module Rails + class Application + class Configuration < ::Rails::Engine::Configuration + attr_accessor :allow_concurrency, :asset_host, :autoflush_log, + :cache_classes, :cache_store, :consider_all_requests_local, :console, + :eager_load, :exceptions_app, :file_watcher, :filter_parameters, + :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, + :railties_order, :relative_url_root, :secret_key_base, :secret_token, + :ssl_options, :public_file_server, + :session_options, :time_zone, :reload_classes_only_on_change, + :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading, + :read_encrypted_secrets, :log_level + + attr_reader :encoding, :api_only + + def initialize(*) + super + self.encoding = Encoding::UTF_8 + @allow_concurrency = nil + @consider_all_requests_local = false + @filter_parameters = [] + @filter_redirect = [] + @helpers_paths = [] + @public_file_server = ActiveSupport::OrderedOptions.new + @public_file_server.enabled = true + @public_file_server.index_name = "index" + @force_ssl = false + @ssl_options = {} + @session_store = nil + @time_zone = "UTC" + @beginning_of_week = :monday + @log_level = :debug + @generators = app_generators + @cache_store = [ :file_store, "#{root}/tmp/cache/" ] + @railties_order = [:all] + @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] + @reload_classes_only_on_change = true + @file_watcher = ActiveSupport::FileUpdateChecker + @exceptions_app = nil + @autoflush_log = true + @log_formatter = ActiveSupport::Logger::SimpleFormatter.new + @eager_load = nil + @secret_token = nil + @secret_key_base = nil + @api_only = false + @debug_exception_response_format = nil + @x = Custom.new + @enable_dependency_loading = false + @read_encrypted_secrets = false + end + + def load_defaults(target_version) + case target_version.to_s + when "5.0" + if respond_to?(:action_controller) + action_controller.per_form_csrf_tokens = true + action_controller.forgery_protection_origin_check = true + end + + ActiveSupport.to_time_preserves_timezone = true + + if respond_to?(:active_record) + active_record.belongs_to_required_by_default = true + end + + self.ssl_options = { hsts: { subdomains: true } } + + when "5.1" + load_defaults "5.0" + + if respond_to?(:assets) + assets.unknown_asset_fallback = false + end + + if respond_to?(:action_view) + action_view.form_with_generates_remote_forms = true + end + + when "5.2" + load_defaults "5.1" + + if respond_to?(:active_record) + active_record.cache_versioning = true + # Remove the temporary load hook from SQLite3Adapter when this is removed + ActiveSupport.on_load(:active_record_sqlite3adapter) do + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = true + end + end + + if respond_to?(:action_dispatch) + action_dispatch.use_authenticated_cookie_encryption = true + end + + if respond_to?(:active_support) + active_support.use_authenticated_message_encryption = true + end + + if respond_to?(:action_controller) + action_controller.default_protect_from_forgery = true + end + + else + raise "Unknown version #{target_version.to_s.inspect}" + end + end + + def encoding=(value) + @encoding = value + silence_warnings do + Encoding.default_external = value + Encoding.default_internal = value + end + end + + def api_only=(value) + @api_only = value + generators.api_only = value + + @debug_exception_response_format ||= :api + end + + def debug_exception_response_format + @debug_exception_response_format || :default + end + + def debug_exception_response_format=(value) + @debug_exception_response_format = value + end + + def paths + @paths ||= begin + paths = super + paths.add "config/database", with: "config/database.yml" + paths.add "config/secrets", with: "config", glob: "secrets.yml{,.enc}" + paths.add "config/environment", with: "config/environment.rb" + paths.add "lib/templates" + paths.add "log", with: "log/#{Rails.env}.log" + paths.add "public" + paths.add "public/javascripts" + paths.add "public/stylesheets" + paths.add "tmp" + paths + end + end + + # Loads and returns the entire raw configuration of database from + # values stored in `config/database.yml`. + def database_configuration + path = paths["config/database"].existent.first + yaml = Pathname.new(path) if path + + config = if yaml && yaml.exist? + require "yaml" + require "erb" + loaded_yaml = YAML.load(ERB.new(yaml.read).result) || {} + shared = loaded_yaml.delete("shared") + if shared + loaded_yaml.each do |_k, values| + values.reverse_merge!(shared) + end + end + Hash.new(shared).merge(loaded_yaml) + elsif ENV["DATABASE_URL"] + # Value from ENV['DATABASE_URL'] is set to default database connection + # by Active Record. + {} + else + raise "Could not load database configuration. No such file - #{paths["config/database"].instance_variable_get(:@paths)}" + end + + config + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + rescue => e + raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace + end + + def colorize_logging + ActiveSupport::LogSubscriber.colorize_logging + end + + def colorize_logging=(val) + ActiveSupport::LogSubscriber.colorize_logging = val + generators.colorize_logging = val + end + + def session_store(new_session_store = nil, **options) + if new_session_store + if new_session_store == :active_record_store + begin + ActionDispatch::Session::ActiveRecordStore + rescue NameError + raise "`ActiveRecord::SessionStore` is extracted out of Rails into a gem. " \ + "Please add `activerecord-session_store` to your Gemfile to use it." + end + end + + @session_store = new_session_store + @session_options = options || {} + else + case @session_store + when :disabled + nil + when :active_record_store + ActionDispatch::Session::ActiveRecordStore + when Symbol + ActionDispatch::Session.const_get(@session_store.to_s.camelize) + else + @session_store + end + end + end + + def session_store? #:nodoc: + @session_store + end + + def annotations + SourceAnnotationExtractor::Annotation + end + + class Custom #:nodoc: + def initialize + @configurations = Hash.new + end + + def method_missing(method, *args) + if method =~ /=$/ + @configurations[$`.to_sym] = args.first + else + @configurations.fetch(method) { + @configurations[method] = ActiveSupport::OrderedOptions.new + } + end + end + + def respond_to_missing?(symbol, *) + true + end + end + end + end +end diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb new file mode 100644 index 0000000000..63300ffef3 --- /dev/null +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -0,0 +1,99 @@ +module Rails + class Application + class DefaultMiddlewareStack + attr_reader :config, :paths, :app + + def initialize(app, config, paths) + @app = app + @config = config + @paths = paths + end + + def build_stack + ActionDispatch::MiddlewareStack.new do |middleware| + if config.force_ssl + middleware.use ::ActionDispatch::SSL, config.ssl_options + end + + middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header + + if config.public_file_server.enabled + headers = config.public_file_server.headers || {} + + middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers + end + + if rack_cache = load_rack_cache + require "action_dispatch/http/rack_cache" + middleware.use ::Rack::Cache, rack_cache + end + + if config.allow_concurrency == false + # User has explicitly opted out of concurrent request + # handling: presumably their code is not threadsafe + + middleware.use ::Rack::Lock + end + + middleware.use ::ActionDispatch::Executor, app.executor + + middleware.use ::Rack::Runtime + middleware.use ::Rack::MethodOverride unless config.api_only + middleware.use ::ActionDispatch::RequestId + middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies + + middleware.use ::Rails::Rack::Logger, config.log_tags + middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app + middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format + + unless config.cache_classes + middleware.use ::ActionDispatch::Reloader, app.reloader + end + + middleware.use ::ActionDispatch::Callbacks + middleware.use ::ActionDispatch::Cookies unless config.api_only + + if !config.api_only && config.session_store + if config.force_ssl && config.ssl_options.fetch(:secure_cookies, true) && !config.session_options.key?(:secure) + config.session_options[:secure] = true + end + middleware.use config.session_store, config.session_options + middleware.use ::ActionDispatch::Flash + end + + middleware.use ::Rack::Head + middleware.use ::Rack::ConditionalGet + middleware.use ::Rack::ETag, "no-cache" + end + end + + private + + def load_rack_cache + rack_cache = config.action_dispatch.rack_cache + return unless rack_cache + + begin + require "rack/cache" + rescue LoadError => error + error.message << " Be sure to add rack-cache to your Gemfile" + raise + end + + if rack_cache == true + { + metastore: "rails:/", + entitystore: "rails:/", + verbose: false + } + else + rack_cache + end + end + + def show_exceptions_app + config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path) + end + end + end +end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb new file mode 100644 index 0000000000..c027d06663 --- /dev/null +++ b/railties/lib/rails/application/finisher.rb @@ -0,0 +1,194 @@ +module Rails + class Application + module Finisher + include Initializable + + initializer :add_generator_templates do + config.generators.templates.unshift(*paths["lib/templates"].existent) + end + + initializer :ensure_autoload_once_paths_as_subset do + extra = ActiveSupport::Dependencies.autoload_once_paths - + ActiveSupport::Dependencies.autoload_paths + + unless extra.empty? + abort <<-end_error + autoload_once_paths must be a subset of the autoload_paths. + Extra items in autoload_once_paths: #{extra * ','} + end_error + end + end + + initializer :add_builtin_route do |app| + if Rails.env.development? + app.routes.prepend do + get "/rails/info/properties" => "rails/info#properties", internal: true + get "/rails/info/routes" => "rails/info#routes", internal: true + get "/rails/info" => "rails/info#index", internal: true + end + + app.routes.append do + get "/" => "rails/welcome#index", internal: true + end + end + end + + # Setup default session store if not already set in config/application.rb + initializer :setup_default_session_store, before: :build_middleware_stack do |app| + unless app.config.session_store? + app_name = app.class.name ? app.railtie_name.chomp("_application") : "" + app.config.session_store :cookie_store, key: "_#{app_name}_session" + end + end + + initializer :build_middleware_stack do + build_middleware_stack + end + + initializer :define_main_app_helper do |app| + app.routes.define_mounted_helper(:main_app) + end + + initializer :add_to_prepare_blocks do |app| + config.to_prepare_blocks.each do |block| + app.reloader.to_prepare(&block) + end + end + + # This needs to happen before eager load so it happens + # in exactly the same point regardless of config.cache_classes + initializer :run_prepare_callbacks do |app| + app.reloader.prepare! + end + + initializer :eager_load! do + if config.eager_load + ActiveSupport.run_load_hooks(:before_eager_load, self) + config.eager_load_namespaces.each(&:eager_load!) + end + end + + # All initialization is done, including eager loading in production + initializer :finisher_hook do + ActiveSupport.run_load_hooks(:after_initialize, self) + end + + class MutexHook + def initialize(mutex = Mutex.new) + @mutex = mutex + end + + def run + @mutex.lock + end + + def complete(_state) + @mutex.unlock + end + end + + module InterlockHook + def self.run + ActiveSupport::Dependencies.interlock.start_running + end + + def self.complete(_state) + ActiveSupport::Dependencies.interlock.done_running + end + end + + initializer :configure_executor_for_concurrency do |app| + if config.allow_concurrency == false + # User has explicitly opted out of concurrent request + # handling: presumably their code is not threadsafe + + app.executor.register_hook(MutexHook.new, outer: true) + + elsif config.allow_concurrency == :unsafe + # Do nothing, even if we know this is dangerous. This is the + # historical behavior for true. + + else + # Default concurrency setting: enabled, but safe + + unless config.cache_classes && config.eager_load + # Without cache_classes + eager_load, the load interlock + # is required for proper operation + + app.executor.register_hook(InterlockHook, outer: true) + end + end + end + + # Set routes reload after the finisher hook to ensure routes added in + # the hook are taken into account. + initializer :set_routes_reloader_hook do |app| + reloader = routes_reloader + reloader.eager_load = app.config.eager_load + reloader.execute_if_updated + reloaders << reloader + app.reloader.to_run do + # We configure #execute rather than #execute_if_updated because if + # autoloaded constants are cleared we need to reload routes also in + # case any was used there, as in + # + # mount MailPreview => 'mail_view' + # + # This means routes are also reloaded if i18n is updated, which + # might not be necessary, but in order to be more precise we need + # some sort of reloaders dependency support, to be added. + require_unload_lock! + reloader.execute + end + end + + # Set clearing dependencies after the finisher hook to ensure paths + # added in the hook are taken into account. + initializer :set_clear_dependencies_hook, group: :all do |app| + callback = lambda do + ActiveSupport::DescendantsTracker.clear + ActiveSupport::Dependencies.clear + end + + if config.cache_classes + app.reloader.check = lambda { false } + elsif config.reload_classes_only_on_change + app.reloader.check = lambda do + app.reloaders.map(&:updated?).any? + end + else + app.reloader.check = lambda { true } + end + + if config.reload_classes_only_on_change + reloader = config.file_watcher.new(*watchable_args, &callback) + reloaders << reloader + + # Prepend this callback to have autoloaded constants cleared before + # any other possible reloading, in case they need to autoload fresh + # constants. + app.reloader.to_run(prepend: true) do + # In addition to changes detected by the file watcher, if routes + # or i18n have been updated we also need to clear constants, + # that's why we run #execute rather than #execute_if_updated, this + # callback has to clear autoloaded constants after any update. + class_unload! do + reloader.execute + end + end + else + app.reloader.to_complete do + class_unload!(&callback) + end + end + end + + # Disable dependency loading during request cycle + initializer :disable_dependency_loading do + if config.eager_load && config.cache_classes && !config.enable_dependency_loading + ActiveSupport::Dependencies.unhook! + end + end + end + end +end diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb new file mode 100644 index 0000000000..e02ef629f2 --- /dev/null +++ b/railties/lib/rails/application/routes_reloader.rb @@ -0,0 +1,69 @@ +require "active_support/core_ext/module/delegation" + +module Rails + class Application + class RoutesReloader + attr_reader :route_sets, :paths + attr_accessor :eager_load + delegate :updated?, to: :updater + + def initialize + @paths = [] + @route_sets = [] + @eager_load = false + end + + def reload! + clear! + load_paths + finalize! + ensure + revert + end + + def execute + ret = updater.execute + route_sets.each(&:eager_load!) if eager_load + ret + end + + def execute_if_updated + if updated = updater.execute_if_updated + route_sets.each(&:eager_load!) if eager_load + end + updated + end + + private + + def updater + @updater ||= begin + updater = ActiveSupport::FileUpdateChecker.new(paths) { reload! } + updater.execute + updater + end + end + + def clear! + route_sets.each do |routes| + routes.disable_clear_and_finalize = true + routes.clear! + end + end + + def load_paths + paths.each { |path| load(path) } + end + + def finalize! + route_sets.each(&:finalize!) + end + + def revert + route_sets.each do |routes| + routes.disable_clear_and_finalize = false + end + end + end + end +end |