diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 566c79be634..8a64fb843a4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -107,7 +107,15 @@ class ApplicationController < ActionController::Base end def render_rate_limit_error(e) - render_json_error e.description, type: :rate_limit, status: 429, extras: { wait_seconds: e&.available_in } + retry_time_in_seconds = e&.available_in + + render_json_error( + e.description, + type: :rate_limit, + status: 429, + extras: { wait_seconds: retry_time_in_seconds }, + headers: { 'Retry-After': retry_time_in_seconds }, + ) end # If they hit the rate limiter @@ -523,12 +531,15 @@ class ApplicationController < ActionController::Base # Render action for a JSON error. # - # obj - a translated string, an ActiveRecord model, or an array of translated strings + # obj - a translated string, an ActiveRecord model, or an array of translated strings # opts: - # type - a machine-readable description of the error - # status - HTTP status code to return + # type - a machine-readable description of the error + # status - HTTP status code to return + # headers - extra headers for the response def render_json_error(obj, opts = {}) opts = { status: opts } if opts.is_a?(Integer) + opts.fetch(:headers, {}).each { |name, value| headers[name.to_s] = value } + render json: MultiJson.dump(create_errors_json(obj, opts)), status: opts[:status] || 422 end diff --git a/config/initializers/004-message_bus.rb b/config/initializers/004-message_bus.rb index d9adc7a8c13..58ba09b6b88 100644 --- a/config/initializers/004-message_bus.rb +++ b/config/initializers/004-message_bus.rb @@ -70,7 +70,7 @@ MessageBus.on_middleware_error do |env, e| if Discourse::InvalidAccess === e [403, {}, ["Invalid Access"]] elsif RateLimiter::LimitExceeded === e - [429, {}, [e.description]] + [429, { 'Retry-After' => e.available_in }, [e.description]] end end diff --git a/spec/integration/rate_limiting_spec.rb b/spec/integration/rate_limiting_spec.rb index 93ad7bc53a9..25a389cf9b0 100644 --- a/spec/integration/rate_limiting_spec.rb +++ b/spec/integration/rate_limiting_spec.rb @@ -25,7 +25,7 @@ describe 'rate limiter integration' do } end - it 'can cleanly limit requests' do + it 'can cleanly limit requests and sets a Retry-After header' do freeze_time #request.set_header("action_dispatch.show_exceptions", true) @@ -50,6 +50,7 @@ describe 'rate limiter integration' do data = JSON.parse(response.body) + expect(response.headers['Retry-After']).to eq(60) expect(data["extras"]["wait_seconds"]).to eq(60) end end