From d0700b6aea00e8368e7b4d5d08741762b16403af Mon Sep 17 00:00:00 2001 From: JarfigNewton Date: Thu, 22 Jul 2021 18:55:13 -0700 Subject: [PATCH 1/2] Updates README --- README.md | 87 ++++++++++--------------------------------------------- 1 file changed, 15 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index a91bfc6..ad57843 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,21 @@ -Rack::Timeout +Rack::Timeout::Select ============= -Abort requests that are taking too long; an exception is raised. +A fork of [Rack::Timeout](https://github.com/heroku/rack-timeout) with extended functionality that allows filtering timeout by Rails request routes. +Default behaviour is still preserved, for documentation on original middleware see the link above. -A timeout of 15s is the default. It's recommended to set the timeout as -low as realistically viable for your application. You can modify this by -setting the `RACK_TIMEOUT_SERVICE_TIMEOUT` environment variable. - -There's a handful of other settings, read on for details. - -Rack::Timeout is not a solution to the problem of long-running requests, -it's a debug and remediation tool. App developers should track -rack-timeout's data and address recurring instances of particular -timeouts, for example by refactoring code so it runs faster or -offsetting lengthy work to happen asynchronously. - -Upgrading ---------- - -For fixing issues when upgrading, please see [UPGRADING](UPGRADING.md). Basic Usage ----------- -The following covers currently supported versions of Rails, Rack, Ruby, -and Bundler. See the Compatibility section at the end for legacy -versions. - -### Rails apps - -```ruby -# Gemfile -gem "rack-timeout" -``` - -This will load rack-timeout and set it up as a Rails middleware using -the default timeout of 15s. The middleware is not inserted for the test -environment. You can modify the timeout by setting a -`RACK_TIMEOUT_SERVICE_TIMEOUT` environment variable. +Pass an an array of paths strings you want to exclude from or only run timeout for. ### Rails apps, manually -You'll need to do this if you removed `Rack::Runtime` from the -middleware stack, or if you want to determine yourself where in the -stack `Rack::Timeout` gets inserted. ```ruby # Gemfile -gem "rack-timeout", require: "rack/timeout/base" +gem "rack-timeout", require:"rack/timeout/base", :git => 'git://github.com/hyfn/rack-timeout.git' ``` ```ruby @@ -55,25 +23,15 @@ gem "rack-timeout", require: "rack/timeout/base" # insert middleware wherever you want in the stack, optionally pass # initialization arguments, or use environment variables -Rails.application.config.middleware.insert_before Rack::Runtime, Rack::Timeout, service_timeout: 15 +Rails.application.config.middleware.insert_before Rack::Runtime, Rack::Timeout::Select, service_timeout: 5, exclude: ["statistics"] ``` -### Sinatra and other Rack apps - -```ruby -# config.ru -require "rack-timeout" - -# Call as early as possible so rack-timeout runs before all other middleware. -# Setting service_timeout or `RACK_TIMEOUT_SERVICE_TIMEOUT` environment -# variable is recommended. If omitted, defaults to 15 seconds. -use Rack::Timeout, service_timeout: 15 -``` +Or include the original `Rack::Timeout` instead, if you want to temporary disable path filtering. Configuring ----------- -Rack::Timeout takes the following settings, shown here with their +Same as the original Rack::Timeout, it takes the following settings, shown here with their default values and associated environment variables. ``` @@ -81,37 +39,22 @@ service_timeout: 15 # RACK_TIMEOUT_SERVICE_TIMEOUT wait_timeout: 30 # RACK_TIMEOUT_WAIT_TIMEOUT wait_overtime: 60 # RACK_TIMEOUT_WAIT_OVERTIME service_past_wait: false # RACK_TIMEOUT_SERVICE_PAST_WAIT -term_on_timeout: false # RACK_TIMEOUT_TERM_ON_TIMEOUT +exclude: [] # RACK_TIMEOUT_EXCLUDE +only: [] # RACK_TIMEOUT_ONLY ``` +Both `exclude` and `only` can be used at the same time, in this case excluded paths will be substracted from the `only` array. + These settings can be overriden during middleware initialization or environment variables `RACK_TIMEOUT_*` mentioned above. Middleware parameters take precedence: ```ruby -use Rack::Timeout, service_timeout: 15, wait_timeout: 30 +use Rack::Timeout::Select, service_timeout: 5, exclude: ["api"] ``` +[Demo application](https://github.com/mkrl/rack-timeout-test) -For more on these settings, please see [doc/settings](doc/settings.md). - -Further Documentation ---------------------- - -Please see the [doc](doc) folder for further documentation on: - -* [Risks and shortcomings of using Rack::Timeout](doc/risks.md) -* [Understanding the request lifecycle](doc/request-lifecycle.md) -* [Exceptions raised by Rack::Timeout](doc/exceptions.md) -* [Rollbar fingerprinting](doc/rollbar.md) -* [Observers](doc/observers.md) -* [Logging](doc/logging.md) - -Compatibility -------------- - -This version of Rack::Timeout is compatible with Ruby 2.1 and up, and, -for Rails apps, Rails 3.x and up. - +Please note that you may have controller actions with names similar to your excludes/targets, use with wise. --- Copyright © 2010-2020 Caio Chassot, released under the MIT license From d7c2ac4969edad7e24df7799d6b9e29affbbdec1 Mon Sep 17 00:00:00 2001 From: JarfigNewton Date: Thu, 22 Jul 2021 19:22:29 -0700 Subject: [PATCH 2/2] Adds filtering by request paths Follows mkrl/rack-timeout fork example --- README.md | 1 + lib/rack/timeout/core.rb | 31 +++++++++++++++++++++++++++++-- rack-timeout.gemspec | 10 +++++----- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ad57843..b14cc78 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ service_timeout: 15 # RACK_TIMEOUT_SERVICE_TIMEOUT wait_timeout: 30 # RACK_TIMEOUT_WAIT_TIMEOUT wait_overtime: 60 # RACK_TIMEOUT_WAIT_OVERTIME service_past_wait: false # RACK_TIMEOUT_SERVICE_PAST_WAIT +term_on_timeout: false # RACK_TIMEOUT_TERM_ON_TIMEOUT exclude: [] # RACK_TIMEOUT_EXCLUDE only: [] # RACK_TIMEOUT_ONLY ``` diff --git a/lib/rack/timeout/core.rb b/lib/rack/timeout/core.rb index 85a34a2..6a7b606 100644 --- a/lib/rack/timeout/core.rb +++ b/lib/rack/timeout/core.rb @@ -47,6 +47,29 @@ def ms(k) # helper method used for formatting values in milliseconds HTTP_X_REQUEST_ID = "HTTP_X_REQUEST_ID".freeze # key where request id is stored if generated by upstream client/proxy ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # key where request id is stored if generated by action dispatch + class Select < Timeout + def call(env) + if exclude_or_any?(env) + super(env) + else + @app.call(env) + end + end + + def exclude_or_any?(env) + if @exclude.empty? & @only.empty? + true + elsif @only.empty? + !(@exclude.any? { |path| env['PATH_INFO'].include?(path) }) + elsif @exclude.empty? + @only.any? { |path| env['PATH_INFO'].include?(path) } + else + both = @only - @exclude + both.any? { |path| env['PATH_INFO'].include?(path) } + end + end + end + # helper methods to read timeout properties. Ensure they're always positive numbers or false. When set to false (or 0), their behaviour is disabled. def read_timeout_property value, default case value @@ -64,14 +87,18 @@ def read_timeout_property value, default :wait_timeout, # How long the request is allowed to have waited before reaching rack. If exceeded, the request is 'expired', i.e. dropped entirely without being passed down to the application. :wait_overtime, # Additional time over @wait_timeout for requests with a body, like POST requests. These may take longer to be received by the server before being passed down to the application, but should not be expired. :service_past_wait, # when false, reduces the request's computed timeout from the service_timeout value if the complete request lifetime (wait + service) would have been longer than wait_timeout (+ wait_overtime when applicable). When true, always uses the service_timeout value. we default to false under the assumption that the router would drop a request that's not responded within wait_timeout, thus being there no point in servicing beyond seconds_service_left (see code further down) up until service_timeout. - :term_on_timeout + :term_on_timeout, + :exclude, # exclude routes with those paths in them from being processed + :only # only process requests coming from those paths - def initialize(app, service_timeout:nil, wait_timeout:nil, wait_overtime:nil, service_past_wait:"not_specified", term_on_timeout: nil) + def initialize(app, service_timeout:nil, wait_timeout:nil, wait_overtime:nil, service_past_wait:"not_specified", term_on_timeout: nil, exclude:[], only:[]) @term_on_timeout = read_timeout_property term_on_timeout, ENV.fetch("RACK_TIMEOUT_TERM_ON_TIMEOUT", 0).to_i @service_timeout = read_timeout_property service_timeout, ENV.fetch("RACK_TIMEOUT_SERVICE_TIMEOUT", 15).to_i @wait_timeout = read_timeout_property wait_timeout, ENV.fetch("RACK_TIMEOUT_WAIT_TIMEOUT", 30).to_i @wait_overtime = read_timeout_property wait_overtime, ENV.fetch("RACK_TIMEOUT_WAIT_OVERTIME", 60).to_i @service_past_wait = service_past_wait == "not_specified" ? ENV.fetch("RACK_TIMEOUT_SERVICE_PAST_WAIT", false).to_s != "false" : service_past_wait + @exclude = exclude == [] ? ENV.fetch("RACK_TIMEOUT_EXCLUDE", []) : exclude + @only = only == [] ? ENV.fetch("RACK_TIMEOUT_ONLY", []) : only Thread.main['RACK_TIMEOUT_COUNT'] ||= 0 if @term_on_timeout diff --git a/rack-timeout.gemspec b/rack-timeout.gemspec index fa24dcd..f54f1bf 100644 --- a/rack-timeout.gemspec +++ b/rack-timeout.gemspec @@ -3,18 +3,18 @@ RACK_TIMEOUT_VERSION = "0.6.0" Gem::Specification.new do |spec| spec.name = "rack-timeout" spec.summary = "Abort requests that are taking too long" - spec.description = "Rack middleware which aborts requests that have been running for longer than a specified timeout." + spec.description = "Rack middleware which aborts requests that have been running for longer than a specified timeout. This fork allows filtering by request paths." spec.version = RACK_TIMEOUT_VERSION - spec.homepage = "https://github.com/sharpstone/rack-timeout" + spec.homepage = "https://github.com/hyfn/rack-timeout" spec.author = "Caio Chassot" spec.email = "caio@heroku.com" spec.files = Dir[*%w( MIT-LICENSE CHANGELOG.md UPGRADING.md README.md lib/**/* doc/**/* )] spec.license = "MIT" spec.metadata = { - "bug_tracker_uri" => "https://github.com/sharpstone/rack-timeout/issues", - "changelog_uri" => "https://github.com/sharpstone/rack-timeout/blob/v#{RACK_TIMEOUT_VERSION}/CHANGELOG.md", + "bug_tracker_uri" => "https://github.com/hyfn/rack-timeout/issues", + "changelog_uri" => "https://github.com/hyfn/rack-timeout/blob/v#{RACK_TIMEOUT_VERSION}/CHANGELOG.md", "documentation_uri" => "https://rubydoc.info/gems/rack-timeout/#{RACK_TIMEOUT_VERSION}/", - "source_code_uri" => "https://github.com/sharpstone/rack-timeout" + "source_code_uri" => "https://github.com/hyfn/rack-timeout" } spec.test_files = Dir.glob("test/**/*").concat([