mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-02 03:33:34 +08:00
This commit adds a default protection to all Discourse app which will turn away all anonymous requests that has queued for more than the duration configured by the `reject_anonymous_min_queue_seconds` global setting, 1 second by default. A queue time of 1 second is a sign that the app is very overloaded so we are opting to turn away anonymous requests when that happens. While there may be better solutions like introducing an adaptive concurrency limit somewhere in the stack, we like this solution for its simplicity. We may consider the more complex solutions in the future but the time is not now. Other significant changes in this commit: 1. Moved setting of the `REQUEST_QUEUE_SECONDS` key in `env` to `Middleware::ProcessingRequest`. 2. Introduction of the `reject_anonymous_min_queue_seconds` global Setting.
126 lines
4.4 KiB
Ruby
126 lines
4.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# we want MessageBus to be close to the front
|
|
# this is important cause the vast majority of web requests go to it
|
|
# this allows us to avoid full middleware crawls each time
|
|
#
|
|
# We aren't manipulating the middleware stack directly because of
|
|
# https://github.com/rails/rails/pull/27936
|
|
|
|
Rails.configuration.middleware.unshift(MessageBus::Rack::Middleware)
|
|
|
|
# no reason to track this in development, that is 300+ redis calls saved per
|
|
# page view (we serve all assets out of thin in development)
|
|
if !Rails.env.development? || ENV["TRACK_REQUESTS"]
|
|
require "middleware/request_tracker"
|
|
Rails.configuration.middleware.unshift(Middleware::RequestTracker)
|
|
Rails.configuration.middleware.move_before(Middleware::RequestTracker, ActionDispatch::RemoteIp)
|
|
|
|
MethodProfiler.ensure_discourse_instrumentation! if GlobalSetting.enable_performance_http_headers
|
|
end
|
|
|
|
require "middleware/overload_protections"
|
|
Rails.configuration.middleware.unshift(Middleware::OverloadProtections)
|
|
|
|
require "middleware/processing_request"
|
|
Rails.configuration.middleware.unshift(Middleware::ProcessingRequest)
|
|
|
|
if Rails.env.test?
|
|
# In test mode we can't insert/remove middlewares
|
|
# Therefore we insert a small helper which effectively switches the multisite
|
|
# middleware on/off based on the Rails.configuration.multisite value
|
|
class TestMultisiteMiddleware < RailsMultisite::Middleware
|
|
def call(env)
|
|
return @app.call(env) if !Rails.configuration.multisite
|
|
super(env)
|
|
end
|
|
end
|
|
|
|
Rails.configuration.middleware.unshift TestMultisiteMiddleware,
|
|
RailsMultisite::DiscoursePatches.config
|
|
|
|
class BlockRequestsMiddleware
|
|
RSPEC_CURRENT_EXAMPLE_COOKIE_STRING = "rspec_current_example_location"
|
|
|
|
cattr_accessor :current_example_location
|
|
|
|
@@block_requests = false
|
|
|
|
def self.block_requests!
|
|
@@block_requests = true
|
|
end
|
|
|
|
def self.allow_requests!
|
|
@@block_requests = false
|
|
end
|
|
|
|
def initialize(app)
|
|
@app = app
|
|
end
|
|
|
|
def call(env)
|
|
request = Rack::Request.new(env)
|
|
|
|
if (
|
|
@@block_requests ||
|
|
(
|
|
request.xhr? && self.class.current_example_location.present? &&
|
|
self.class.current_example_location !=
|
|
request.cookies[RSPEC_CURRENT_EXAMPLE_COOKIE_STRING]
|
|
)
|
|
)
|
|
return [
|
|
503,
|
|
{ "Content-Type" => "text/plain" },
|
|
[
|
|
"Blocked by BlockRequestsMiddleware for requests initiated by #{request.cookies[RSPEC_CURRENT_EXAMPLE_COOKIE_STRING]} when running #{self.class.current_example_location}",
|
|
]
|
|
]
|
|
end
|
|
|
|
status, headers, body = @app.call(env)
|
|
if headers["Content-Type"]&.match?(/html/) && BlockRequestsMiddleware.current_example_location
|
|
headers["Set-Cookie"] = [
|
|
headers["Set-Cookie"],
|
|
"#{RSPEC_CURRENT_EXAMPLE_COOKIE_STRING}=#{BlockRequestsMiddleware.current_example_location}; path=/;",
|
|
].compact.join("\n")
|
|
end
|
|
[status, headers, body]
|
|
end
|
|
end
|
|
|
|
Rails.configuration.middleware.unshift BlockRequestsMiddleware
|
|
elsif Rails.configuration.multisite
|
|
assets_hostnames = GlobalSetting.cdn_hostnames
|
|
|
|
if assets_hostnames.empty?
|
|
assets_hostnames = Discourse::Application.config.database_configuration[Rails.env]["host_names"]
|
|
end
|
|
|
|
RailsMultisite::ConnectionManagement.asset_hostnames = assets_hostnames
|
|
|
|
# Multisite needs to be first, because the request tracker and message bus rely on it
|
|
Rails.configuration.middleware.unshift RailsMultisite::Middleware,
|
|
RailsMultisite::DiscoursePatches.config
|
|
Rails.configuration.middleware.delete ActionDispatch::Executor
|
|
|
|
if defined?(RailsFailover::ActiveRecord) && Rails.configuration.active_record_rails_failover
|
|
Rails.configuration.middleware.insert_after(
|
|
RailsMultisite::Middleware,
|
|
RailsFailover::ActiveRecord::Middleware,
|
|
)
|
|
end
|
|
|
|
if Rails.env.development?
|
|
# Automatically allow development multisite hosts
|
|
RailsMultisite::ConnectionManagement.instance.db_spec_cache.each do |db, specification|
|
|
next if db == "default"
|
|
Rails.configuration.hosts.concat(specification.spec.configuration_hash[:host_names])
|
|
end
|
|
end
|
|
elsif defined?(RailsFailover::ActiveRecord) && Rails.configuration.active_record_rails_failover
|
|
Rails.configuration.middleware.insert_before(
|
|
MessageBus::Rack::Middleware,
|
|
RailsFailover::ActiveRecord::Middleware,
|
|
)
|
|
end
|