slowpoke 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b602f89b54077d5e970bb549ce1ad3e57db8d1ab6f15bf62ea3dbae868d6ca5
4
- data.tar.gz: 60cb3a0c463ebadeb4abec48794ffaa2b33b22af406c672ba2ac351f7134fbed
3
+ metadata.gz: b1f1038454b7897804fae9a78b8804de5ecd1d305929332fc544326df8ded0c4
4
+ data.tar.gz: 16c1ea1f4aea141681803f9f414620f4b12ffbf5dcb1ba5d8967ad3e6403ad6c
5
5
  SHA512:
6
- metadata.gz: 94352be6969f60055f4f6121f9b087fb5a4b2e144014b973fe6aa772cb7c4c9bd4cc5922a51dc3a41ce9a70bcd97f5f1bd0516ec764d002e410a0578cf1ffb25
7
- data.tar.gz: bf7c814b78e54a679798107f013083a596fab63fab960734fa5d32ba689e35128b33da7430aa560f789c56d3982d6e58f226f960828e985828793db11549e404
6
+ metadata.gz: 79c329a7bf360732c8ab05509b92502f7ebfea4025221568a6af4602897d7bc984a8a05f230d4bf1128221b172798ee3b436b5786e5e4b85119e737984a1fd64
7
+ data.tar.gz: 20dc8bc102e2fdcab2e3e896d0ff15a18f34009c0c18ea03ad56b1f26e37bb020c71aec4a7d8c6e66e338f0dbc63d222f65bda4e669d7c0efb4fb1ad428bfefb
data/CHANGELOG.md CHANGED
@@ -1,24 +1,43 @@
1
- ## 0.2.1
1
+ ## 0.4.0 (2022-10-01)
2
+
3
+ - Dropped support for Ruby < 2.6 and Rails < 5.2
4
+
5
+ ## 0.3.2 (2019-12-23)
6
+
7
+ - Added `on_timeout` method
8
+
9
+ ## 0.3.1 (2019-12-10)
10
+
11
+ - Added support for dynamic timeouts
12
+
13
+ ## 0.3.0 (2019-05-31)
14
+
15
+ - Use proper signal for Puma
16
+ - Dropped support for rack-timeout < 0.4
17
+ - Dropped support for migration timeouts
18
+ - Dropped support for Rails < 5
19
+
20
+ ## 0.2.1 (2018-05-21)
2
21
 
3
22
  - Don’t kill server in test environment
4
23
  - Require rack-timeout < 0.5
5
24
 
6
- ## 0.2.0
25
+ ## 0.2.0 (2017-11-05)
7
26
 
8
27
  - Fixed custom error pages for Rails 5.1
9
28
  - Fixed migration statement timeout
10
29
  - Don’t kill server in development
11
30
 
12
- ## 0.1.3
31
+ ## 0.1.3 (2016-08-03)
13
32
 
14
33
  - Fixed deprecation warning in Rails 5
15
34
  - No longer requires ActiveRecord
16
35
 
17
- ## 0.1.2
36
+ ## 0.1.2 (2016-02-10)
18
37
 
19
38
  - Updated to latest version of rack-timeout, removing the need to bubble timeouts
20
39
 
21
- ## 0.1.1
40
+ ## 0.1.1 (2015-08-02)
22
41
 
23
42
  - Fixed safer service timeouts
24
43
  - Added migration statement timeout
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Andrew Kane
1
+ Copyright (c) 2014-2021 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # Slowpoke
2
2
 
3
- [Rack::Timeout](https://github.com/heroku/rack-timeout) is great. Slowpoke makes it better with:
3
+ [Rack::Timeout](https://github.com/heroku/rack-timeout) enhancements for Rails
4
4
 
5
+ - safer service timeouts
6
+ - dynamic timeouts
5
7
  - custom error pages
6
- - [safer service timeouts](https://github.com/heroku/rack-timeout/issues/39)
7
- - wait timeouts that don’t kill your web server
8
8
 
9
9
  ## Installation
10
10
 
11
11
  Add this line to your application’s Gemfile:
12
12
 
13
13
  ```ruby
14
- gem 'slowpoke'
14
+ gem "slowpoke"
15
15
  ```
16
16
 
17
17
  And run:
@@ -22,24 +22,38 @@ rails generate slowpoke:install
22
22
 
23
23
  This creates a `public/503.html` you can customize.
24
24
 
25
- ## How to Use
25
+ ## Development
26
26
 
27
- The default timeout is 15 seconds. Change this with:
27
+ To try out custom error pages in development, temporarily add to `config/environments/development.rb`:
28
28
 
29
29
  ```ruby
30
- Slowpoke.timeout = 10
30
+ config.slowpoke.timeout = 1
31
+ config.consider_all_requests_local = false
31
32
  ```
32
33
 
33
- or set:
34
+ And add a `sleep` call to one of your actions:
34
35
 
35
36
  ```ruby
36
- ENV["REQUEST_TIMEOUT"]
37
+ sleep(2)
37
38
  ```
38
39
 
39
- Test by adding a `sleep` call to one of your actions:
40
+ The custom error page should appear.
41
+
42
+ ## Production
43
+
44
+ The default timeout is 15 seconds. You can change this in `config/environments/production.rb` with:
40
45
 
41
46
  ```ruby
42
- sleep(20)
47
+ config.slowpoke.timeout = 5
48
+ ```
49
+
50
+ For dynamic timeouts, use:
51
+
52
+ ```ruby
53
+ config.slowpoke.timeout = lambda do |env|
54
+ request = Rack::Request.new(env)
55
+ request.path.start_with?("/admin") ? 15 : 5
56
+ end
43
57
  ```
44
58
 
45
59
  Subscribe to timeouts with:
@@ -50,41 +64,65 @@ ActiveSupport::Notifications.subscribe "timeout.slowpoke" do |name, start, finis
50
64
  end
51
65
  ```
52
66
 
53
- To learn more, see the [Rack::Timeout documentation](https://github.com/heroku/rack-timeout#the-rabbit-hole).
67
+ To learn more, see the [Rack::Timeout documentation](https://github.com/heroku/rack-timeout).
68
+
69
+ ## Safer Service Timeouts
70
+
71
+ Rack::Timeout can raise an exception at any point in the code, which can leave your app in an [unclean state](https://www.schneems.com/2017/02/21/the-oldest-bug-in-ruby-why-racktimeout-might-hose-your-server/). The safest way to recover from a request timeout is to spawn a new process. This is the default behavior for Slowpoke.
72
+
73
+ For threaded servers like Puma, this means killing all threads when any one of them times out. This can have a significant impact on performance.
74
+
75
+ You can customize this behavior with:
76
+
77
+ ```ruby
78
+ Slowpoke.on_timeout do |env|
79
+ next if Rails.env.development? || Rails.env.test?
80
+
81
+ exception = env["action_dispatch.exception"]
82
+ if exception && exception.backtrace.first.include?("/active_record/")
83
+ Slowpoke.kill
84
+ end
85
+ end
86
+ ```
87
+
88
+ Note: To access `env["action_dispatch.exception"]` in development, temporarily add to `config/environments/development.rb`:
89
+
90
+ ```ruby
91
+ config.consider_all_requests_local = false
92
+ ```
54
93
 
55
94
  ## Database Timeouts
56
95
 
57
- For PostgreSQL, set connect and statement timeouts in `config/database.yml`:
96
+ It’s a good idea to set a [statement timeout](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts/#statement-timeouts-1) and a [connect timeout](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts/#activerecord). For Postgres, your `config/database.yml` should include something like:
58
97
 
59
- ```yaml
98
+ ```yml
60
99
  production:
61
- connect_timeout: 1 # sec
100
+ connect_timeout: 3 # sec
62
101
  variables:
63
- statement_timeout: 250 # ms
102
+ statement_timeout: 5s
64
103
  ```
65
104
 
66
- To use a different statement timeout for migrations, set:
105
+ ## Upgrading
67
106
 
68
- ```ruby
69
- ENV["MIGRATION_STATEMENT_TIMEOUT"] = 60000 # ms
70
- ```
107
+ ### 0.3.0
71
108
 
72
- Test connect timeouts by setting your database host to an [unroutable IP](https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error).
109
+ If you set the timeout with:
73
110
 
74
- ```yaml
75
- development:
76
- host: 10.255.255.1
111
+ ```ruby
112
+ Slowpoke.timeout = 5
77
113
  ```
78
114
 
79
- Test statement timeouts with the [pg_sleep](https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-DELAY) function.
115
+ Remove it and add to `config/environments/production.rb`:
80
116
 
81
- ```sql
82
- SELECT pg_sleep(20);
117
+ ```ruby
118
+ config.slowpoke.timeout = 5
83
119
  ```
84
120
 
85
- ## Upgrading
121
+ If you use migration timeouts, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts/#statement-timeouts-1) for how to configure them directly in `config/database.yml`.
122
+
123
+ ### 0.1.0
86
124
 
87
- `0.1.0` removes database timeouts, since Rails supports them by default. To restore the previous behavior, use:
125
+ `0.1.0` removes database timeouts, since Rails supports them by default. To restore the previous behavior, use:
88
126
 
89
127
  ```yaml
90
128
  production:
@@ -104,3 +142,11 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
104
142
  - Fix bugs and [submit pull requests](https://github.com/ankane/slowpoke/pulls)
105
143
  - Write, clarify, or fix documentation
106
144
  - Suggest or add new features
145
+
146
+ To get started with development:
147
+
148
+ ```sh
149
+ git clone https://github.com/ankane/slowpoke.git
150
+ cd slowpoke
151
+ bundle install
152
+ ```
@@ -4,7 +4,7 @@
4
4
  <title>This page took too long to load (503)</title>
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <style>
7
- body {
7
+ .rails-default-error-page {
8
8
  background-color: #EFEFEF;
9
9
  color: #2E2F30;
10
10
  text-align: center;
@@ -12,13 +12,13 @@
12
12
  margin: 0;
13
13
  }
14
14
 
15
- div.dialog {
15
+ .rails-default-error-page div.dialog {
16
16
  width: 95%;
17
17
  max-width: 33em;
18
18
  margin: 4em auto 0;
19
19
  }
20
20
 
21
- div.dialog > div {
21
+ .rails-default-error-page div.dialog > div {
22
22
  border: 1px solid #CCC;
23
23
  border-right-color: #999;
24
24
  border-left-color: #999;
@@ -31,13 +31,13 @@
31
31
  box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
32
  }
33
33
 
34
- h1 {
34
+ .rails-default-error-page h1 {
35
35
  font-size: 100%;
36
36
  color: #730E15;
37
37
  line-height: 1.5em;
38
38
  }
39
39
 
40
- div.dialog > p {
40
+ .rails-default-error-page div.dialog > p {
41
41
  margin: 0 0 1em;
42
42
  padding: 1em;
43
43
  background-color: #F7F7F7;
@@ -54,7 +54,7 @@
54
54
  </style>
55
55
  </head>
56
56
 
57
- <body>
57
+ <body class="rails-default-error-page">
58
58
  <!-- This file lives in public/503.html -->
59
59
  <div class="dialog">
60
60
  <div>
@@ -7,17 +7,11 @@ module Slowpoke
7
7
  def call(env)
8
8
  @app.call(env)
9
9
  ensure
10
- if env[Slowpoke::ENV_KEY]
11
- # extremely important
12
- # protect the process with a restart
13
- # https://github.com/heroku/rack-timeout/issues/39
14
- # can't do in timed_out state consistently
15
- if defined?(::PhusionPassenger)
16
- `passenger-config detach-process #{Process.pid}`
17
- else
18
- Process.kill("QUIT", Process.pid)
19
- end
20
- end
10
+ # extremely important
11
+ # protect the process with a restart
12
+ # https://github.com/heroku/rack-timeout/issues/39
13
+ # can't do in timed_out state consistently
14
+ Slowpoke.on_timeout.call(env) if env[Slowpoke::ENV_KEY]
21
15
  end
22
16
  end
23
17
  end
@@ -1,13 +1,26 @@
1
1
  module Slowpoke
2
2
  class Railtie < Rails::Railtie
3
+ config.slowpoke = ActiveSupport::OrderedOptions.new
4
+
5
+ # must happen outside initializer (so it runs earlier)
6
+ config.action_dispatch.rescue_responses.merge!(
7
+ "Rack::Timeout::RequestTimeoutError" => :service_unavailable,
8
+ "Rack::Timeout::RequestExpiryError" => :service_unavailable
9
+ )
10
+
3
11
  initializer "slowpoke" do |app|
4
- Rack::Timeout.service_timeout = Slowpoke.timeout
5
- if Rails::VERSION::MAJOR >= 5
6
- app.config.middleware.insert_after ActionDispatch::DebugExceptions, Rack::Timeout
12
+ service_timeout = app.config.slowpoke.timeout
13
+ service_timeout ||= ENV["RACK_TIMEOUT_SERVICE_TIMEOUT"] || ENV["REQUEST_TIMEOUT"] || ENV["TIMEOUT"] || 15
14
+
15
+ if service_timeout.respond_to?(:call)
16
+ app.config.middleware.insert_after ActionDispatch::DebugExceptions, Slowpoke::Timeout,
17
+ service_timeout: service_timeout
7
18
  else
8
- app.config.middleware.insert_before ActionDispatch::RemoteIp, Rack::Timeout
19
+ app.config.middleware.insert_after ActionDispatch::DebugExceptions, Rack::Timeout,
20
+ service_timeout: service_timeout.to_i
9
21
  end
10
- app.config.middleware.insert(0, Slowpoke::Middleware) unless Rails.env.development? || Rails.env.test?
22
+
23
+ app.config.middleware.insert(0, Slowpoke::Middleware)
11
24
  end
12
25
  end
13
26
  end
@@ -0,0 +1,18 @@
1
+ module Slowpoke
2
+ class Timeout
3
+ def initialize(app, service_timeout:)
4
+ @app = app
5
+ @service_timeout = service_timeout
6
+ @middleware = {}
7
+ end
8
+
9
+ def call(env)
10
+ service_timeout = @service_timeout.call(env)
11
+ if service_timeout
12
+ (@middleware[service_timeout] ||= Rack::Timeout.new(@app, service_timeout: service_timeout)).call(env)
13
+ else
14
+ @app.call(env)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module Slowpoke
2
- VERSION = "0.2.1"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/slowpoke.rb CHANGED
@@ -1,32 +1,40 @@
1
- require "slowpoke/version"
1
+ # dependencies
2
2
  require "rack/timeout/base"
3
+
4
+ # modules
3
5
  require "slowpoke/middleware"
4
- require "slowpoke/migration"
5
6
  require "slowpoke/railtie"
6
- require "action_dispatch/middleware/exception_wrapper"
7
- require "action_controller/base"
7
+ require "slowpoke/timeout"
8
+ require "slowpoke/version"
8
9
 
9
10
  module Slowpoke
10
11
  ENV_KEY = "slowpoke.timed_out".freeze
11
12
 
12
- def self.timeout
13
- @timeout ||= (ENV["REQUEST_TIMEOUT"] || ENV["TIMEOUT"] || 15).to_i
13
+ def self.kill
14
+ if defined?(::PhusionPassenger)
15
+ `passenger-config detach-process #{Process.pid}`
16
+ elsif defined?(::Puma)
17
+ Process.kill("TERM", Process.pid)
18
+ else
19
+ Process.kill("QUIT", Process.pid)
20
+ end
14
21
  end
15
22
 
16
- def self.timeout=(timeout)
17
- timeout = timeout.to_i if timeout.respond_to?(:to_i)
18
- @timeout = Rack::Timeout.service_timeout = timeout
23
+ def self.on_timeout(&block)
24
+ if block_given?
25
+ @on_timeout = block
26
+ else
27
+ @on_timeout
28
+ end
19
29
  end
20
30
 
21
- def self.migration_statement_timeout
22
- ENV["MIGRATION_STATEMENT_TIMEOUT"]
31
+ on_timeout do |env|
32
+ next if Rails.env.development? || Rails.env.test?
33
+
34
+ Slowpoke.kill
23
35
  end
24
36
  end
25
37
 
26
- # custom error page
27
- ActionDispatch::ExceptionWrapper.rescue_responses["Rack::Timeout::RequestTimeoutError"] = :service_unavailable
28
- ActionDispatch::ExceptionWrapper.rescue_responses["Rack::Timeout::RequestExpiryError"] = :service_unavailable
29
-
30
38
  # remove noisy logger
31
39
  Rack::Timeout.unregister_state_change_observer(:logger)
32
40
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slowpoke
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-21 00:00:00.000000000 Z
11
+ date: 2022-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '5.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '5.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: actionpack
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,74 +44,35 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 0.3.0
48
- - - "<"
49
- - !ruby/object:Gem::Version
50
- version: 0.5.0
47
+ version: '0.4'
51
48
  type: :runtime
52
49
  prerelease: false
53
50
  version_requirements: !ruby/object:Gem::Requirement
54
51
  requirements:
55
52
  - - ">="
56
53
  - !ruby/object:Gem::Version
57
- version: 0.3.0
58
- - - "<"
59
- - !ruby/object:Gem::Version
60
- version: 0.5.0
61
- - !ruby/object:Gem::Dependency
62
- name: bundler
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: '0'
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: '0'
75
- - !ruby/object:Gem::Dependency
76
- name: rake
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- version: '0'
82
- type: :development
83
- prerelease: false
84
- version_requirements: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- version: '0'
89
- description:
90
- email:
91
- - andrew@chartkick.com
54
+ version: '0.4'
55
+ description:
56
+ email: andrew@ankane.org
92
57
  executables: []
93
58
  extensions: []
94
59
  extra_rdoc_files: []
95
60
  files:
96
- - ".gitignore"
97
61
  - CHANGELOG.md
98
- - Gemfile
99
62
  - LICENSE.txt
100
63
  - README.md
101
- - Rakefile
102
64
  - lib/generators/slowpoke/install_generator.rb
103
65
  - lib/generators/slowpoke/templates/503.html
104
66
  - lib/slowpoke.rb
105
67
  - lib/slowpoke/middleware.rb
106
- - lib/slowpoke/migration.rb
107
68
  - lib/slowpoke/railtie.rb
69
+ - lib/slowpoke/timeout.rb
108
70
  - lib/slowpoke/version.rb
109
- - slowpoke.gemspec
110
71
  homepage: https://github.com/ankane/slowpoke
111
72
  licenses:
112
73
  - MIT
113
74
  metadata: {}
114
- post_install_message:
75
+ post_install_message:
115
76
  rdoc_options: []
116
77
  require_paths:
117
78
  - lib
@@ -119,16 +80,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
119
80
  requirements:
120
81
  - - ">="
121
82
  - !ruby/object:Gem::Version
122
- version: '0'
83
+ version: '2.6'
123
84
  required_rubygems_version: !ruby/object:Gem::Requirement
124
85
  requirements:
125
86
  - - ">="
126
87
  - !ruby/object:Gem::Version
127
88
  version: '0'
128
89
  requirements: []
129
- rubyforge_project:
130
- rubygems_version: 2.7.6
131
- signing_key:
90
+ rubygems_version: 3.3.3
91
+ signing_key:
132
92
  specification_version: 4
133
- summary: Rack::Timeout is great. Slowpoke makes it better.
93
+ summary: Rack::Timeout enhancements for Rails
134
94
  test_files: []
data/.gitignore DELETED
@@ -1,14 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- *.bundle
11
- *.so
12
- *.o
13
- *.a
14
- mkmf.log
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in slowpoke.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1 +0,0 @@
1
- require "bundler/gem_tasks"
@@ -1,16 +0,0 @@
1
- module Slowpoke
2
- module Migration
3
- def connection
4
- connection = super
5
- if Slowpoke.migration_statement_timeout && !@migration_statement_timeout_set
6
- connection.execute("SET statement_timeout = #{Slowpoke.migration_statement_timeout.to_i}")
7
- @migration_statement_timeout_set = true
8
- end
9
- connection
10
- end
11
- end
12
- end
13
-
14
- ActiveSupport.on_load(:active_record) do
15
- ActiveRecord::Migration.prepend(Slowpoke::Migration)
16
- end
data/slowpoke.gemspec DELETED
@@ -1,26 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "slowpoke/version"
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "slowpoke"
8
- spec.version = Slowpoke::VERSION
9
- spec.authors = ["Andrew Kane"]
10
- spec.email = ["andrew@chartkick.com"]
11
- spec.summary = "Rack::Timeout is great. Slowpoke makes it better."
12
- spec.homepage = "https://github.com/ankane/slowpoke"
13
- spec.license = "MIT"
14
-
15
- spec.files = `git ls-files -z`.split("\x0")
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
- spec.require_paths = ["lib"]
19
-
20
- spec.add_dependency "railties"
21
- spec.add_dependency "actionpack"
22
- spec.add_dependency "rack-timeout", ">= 0.3.0", "< 0.5.0"
23
-
24
- spec.add_development_dependency "bundler"
25
- spec.add_development_dependency "rake"
26
- end