sidekiq 7.3.0 → 8.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +158 -0
  3. data/README.md +16 -13
  4. data/bin/sidekiqload +31 -22
  5. data/bin/webload +69 -0
  6. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +120 -0
  7. data/lib/generators/sidekiq/job_generator.rb +2 -0
  8. data/lib/sidekiq/api.rb +184 -71
  9. data/lib/sidekiq/capsule.rb +11 -9
  10. data/lib/sidekiq/cli.rb +16 -20
  11. data/lib/sidekiq/client.rb +28 -11
  12. data/lib/sidekiq/component.rb +62 -2
  13. data/lib/sidekiq/config.rb +42 -18
  14. data/lib/sidekiq/deploy.rb +2 -0
  15. data/lib/sidekiq/embedded.rb +4 -1
  16. data/lib/sidekiq/iterable_job.rb +3 -0
  17. data/lib/sidekiq/job/interrupt_handler.rb +2 -0
  18. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +3 -3
  19. data/lib/sidekiq/job/iterable.rb +82 -7
  20. data/lib/sidekiq/job_logger.rb +15 -27
  21. data/lib/sidekiq/job_retry.rb +17 -5
  22. data/lib/sidekiq/job_util.rb +7 -1
  23. data/lib/sidekiq/launcher.rb +3 -2
  24. data/lib/sidekiq/logger.rb +19 -70
  25. data/lib/sidekiq/manager.rb +0 -1
  26. data/lib/sidekiq/metrics/query.rb +73 -45
  27. data/lib/sidekiq/metrics/shared.rb +23 -9
  28. data/lib/sidekiq/metrics/tracking.rb +22 -12
  29. data/lib/sidekiq/middleware/current_attributes.rb +12 -4
  30. data/lib/sidekiq/middleware/modules.rb +2 -0
  31. data/lib/sidekiq/monitor.rb +2 -1
  32. data/lib/sidekiq/paginator.rb +14 -1
  33. data/lib/sidekiq/processor.rb +26 -19
  34. data/lib/sidekiq/profiler.rb +72 -0
  35. data/lib/sidekiq/rails.rb +44 -55
  36. data/lib/sidekiq/redis_client_adapter.rb +0 -1
  37. data/lib/sidekiq/redis_connection.rb +22 -4
  38. data/lib/sidekiq/ring_buffer.rb +2 -0
  39. data/lib/sidekiq/systemd.rb +2 -0
  40. data/lib/sidekiq/testing.rb +7 -7
  41. data/lib/sidekiq/version.rb +6 -2
  42. data/lib/sidekiq/web/action.rb +124 -69
  43. data/lib/sidekiq/web/application.rb +355 -377
  44. data/lib/sidekiq/web/config.rb +120 -0
  45. data/lib/sidekiq/web/helpers.rb +64 -33
  46. data/lib/sidekiq/web/router.rb +61 -74
  47. data/lib/sidekiq/web.rb +52 -150
  48. data/lib/sidekiq.rb +5 -4
  49. data/sidekiq.gemspec +6 -6
  50. data/web/assets/javascripts/application.js +6 -13
  51. data/web/assets/javascripts/base-charts.js +30 -16
  52. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  53. data/web/assets/javascripts/dashboard-charts.js +2 -0
  54. data/web/assets/javascripts/dashboard.js +7 -1
  55. data/web/assets/javascripts/metrics.js +16 -34
  56. data/web/assets/stylesheets/style.css +766 -0
  57. data/web/locales/ar.yml +1 -0
  58. data/web/locales/cs.yml +1 -0
  59. data/web/locales/da.yml +1 -0
  60. data/web/locales/de.yml +1 -0
  61. data/web/locales/el.yml +1 -0
  62. data/web/locales/en.yml +9 -1
  63. data/web/locales/es.yml +24 -2
  64. data/web/locales/fa.yml +1 -0
  65. data/web/locales/fr.yml +1 -1
  66. data/web/locales/gd.yml +1 -1
  67. data/web/locales/he.yml +1 -0
  68. data/web/locales/hi.yml +1 -0
  69. data/web/locales/it.yml +40 -1
  70. data/web/locales/ja.yml +1 -1
  71. data/web/locales/ko.yml +1 -0
  72. data/web/locales/lt.yml +1 -0
  73. data/web/locales/nb.yml +1 -0
  74. data/web/locales/nl.yml +1 -0
  75. data/web/locales/pl.yml +1 -0
  76. data/web/locales/{pt-br.yml → pt-BR.yml} +3 -3
  77. data/web/locales/pt.yml +1 -0
  78. data/web/locales/ru.yml +1 -0
  79. data/web/locales/sv.yml +1 -0
  80. data/web/locales/ta.yml +1 -0
  81. data/web/locales/tr.yml +2 -2
  82. data/web/locales/uk.yml +25 -1
  83. data/web/locales/ur.yml +1 -0
  84. data/web/locales/vi.yml +1 -0
  85. data/web/locales/{zh-cn.yml → zh-CN.yml} +85 -74
  86. data/web/locales/{zh-tw.yml → zh-TW.yml} +2 -2
  87. data/web/views/_footer.erb +31 -34
  88. data/web/views/_job_info.erb +91 -89
  89. data/web/views/_metrics_period_select.erb +13 -10
  90. data/web/views/_nav.erb +14 -21
  91. data/web/views/_paging.erb +23 -21
  92. data/web/views/_poll_link.erb +2 -2
  93. data/web/views/_summary.erb +16 -16
  94. data/web/views/busy.erb +124 -122
  95. data/web/views/dashboard.erb +63 -64
  96. data/web/views/dead.erb +31 -27
  97. data/web/views/filtering.erb +3 -4
  98. data/web/views/layout.erb +13 -29
  99. data/web/views/metrics.erb +75 -82
  100. data/web/views/metrics_for_job.erb +45 -46
  101. data/web/views/morgue.erb +61 -70
  102. data/web/views/profiles.erb +43 -0
  103. data/web/views/queue.erb +54 -52
  104. data/web/views/queues.erb +43 -41
  105. data/web/views/retries.erb +66 -75
  106. data/web/views/retry.erb +32 -27
  107. data/web/views/scheduled.erb +59 -55
  108. data/web/views/scheduled_job_info.erb +1 -1
  109. metadata +27 -29
  110. data/web/assets/stylesheets/application-dark.css +0 -147
  111. data/web/assets/stylesheets/application-rtl.css +0 -163
  112. data/web/assets/stylesheets/application.css +0 -758
  113. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  114. data/web/assets/stylesheets/bootstrap.css +0 -5
  115. data/web/views/_status.erb +0 -4
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/web/csrf_protection"
4
+
5
+ module Sidekiq
6
+ class Web
7
+ ##
8
+ # Configure the Sidekiq::Web instance in this process:
9
+ #
10
+ # require "sidekiq/web"
11
+ # Sidekiq::Web.configure do |config|
12
+ # config.register(MyExtension, name: "myext", tab: "TabName", index: "tabpage/")
13
+ # end
14
+ #
15
+ # This should go in your `config/routes.rb` or similar. It
16
+ # does not belong in your initializer since Web should not be
17
+ # loaded in some processes (like an actual Sidekiq process).
18
+ # See `examples/webui-ext` for a sample web extension.
19
+ class Config
20
+ extend Forwardable
21
+
22
+ OPTIONS = {
23
+ # By default we support direct uploads to p.f.c since the UI is a JS SPA
24
+ # and very difficult for us to vendor or provide ourselves. If you are worried
25
+ # about data security and wish to self-host, you can change these URLs.
26
+ profile_view_url: "https://profiler.firefox.com/public/%s",
27
+ profile_store_url: "https://api.profiler.firefox.com/compressed-store",
28
+ # Will be false in Sidekiq 9.0.
29
+ # CSRF is unnecessary if you are using SameSite=(Strict|Lax) cookies.
30
+ csrf: true
31
+ }
32
+
33
+ ##
34
+ # Allows users to add custom rows to all of the Job
35
+ # tables, e.g. Retries, Dead, Scheduled, with custom
36
+ # links to other systems, see _job_info.erb and test
37
+ # in web_test.rb
38
+ #
39
+ # Sidekiq::Web.configure do |cfg|
40
+ # cfg.custom_job_info_rows << JobLogLink.new
41
+ # end
42
+ #
43
+ # class JobLogLink
44
+ # def add_pair(job)
45
+ # yield "External Logs", "<a href='https://example.com/logs/#{job.jid}'>Logs for #{job.jid}</a>"
46
+ # end
47
+ # end
48
+ attr_accessor :custom_job_info_rows
49
+
50
+ attr_reader :tabs
51
+ attr_reader :locales
52
+ attr_reader :views
53
+ attr_reader :middlewares
54
+
55
+ # Adds the "Back to App" link in the header
56
+ attr_accessor :app_url
57
+
58
+ def initialize
59
+ @options = OPTIONS.dup
60
+ @locales = LOCALES
61
+ @views = VIEWS
62
+ @tabs = DEFAULT_TABS.dup
63
+ @middlewares = []
64
+ @custom_job_info_rows = []
65
+ end
66
+
67
+ def_delegators :@options, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
68
+
69
+ def use(*args, &block)
70
+ middlewares << [args, block]
71
+ end
72
+
73
+ # Register a class as a Sidekiq Web UI extension. The class should
74
+ # provide one or more tabs which map to an index route. Options:
75
+ #
76
+ # @param extclass [Class] Class which contains the HTTP actions, required
77
+ # @param name [String] the name of the extension, used to namespace assets
78
+ # @param tab [String | Array] labels(s) of the UI tabs
79
+ # @param index [String | Array] index route(s) for each tab
80
+ # @param root_dir [String] directory location to find assets, locales and views, typically `web/` within the gemfile
81
+ # @param asset_paths [Array] one or more directories under {root}/assets/{name} to be publicly served, e.g. ["js", "css", "img"]
82
+ # @param cache_for [Integer] amount of time to cache assets, default one day
83
+ #
84
+ # Web extensions will have a root `web/` directory with `locales/`, `assets/`
85
+ # and `views/` subdirectories.
86
+ def register_extension(extclass, name:, tab:, index:, root_dir: nil, cache_for: 86400, asset_paths: nil)
87
+ tab = Array(tab)
88
+ index = Array(index)
89
+ tab.zip(index).each do |tab, index|
90
+ tabs[tab] = index
91
+ end
92
+ if root_dir
93
+ locdir = File.join(root_dir, "locales")
94
+ locales << locdir if File.directory?(locdir)
95
+
96
+ if asset_paths && name
97
+ # if you have {root}/assets/{name}/js/scripts.js
98
+ # and {root}/assets/{name}/css/styles.css
99
+ # you would pass in:
100
+ # asset_paths: ["js", "css"]
101
+ # See script_tag and style_tag in web/helpers.rb
102
+ assdir = File.join(root_dir, "assets")
103
+ assurls = Array(asset_paths).map { |x| "/#{name}/#{x}" }
104
+ assetprops = {
105
+ urls: assurls,
106
+ root: assdir,
107
+ cascade: true
108
+ }
109
+ assetprops[:header_rules] = [[:all, {"cache-control" => "private, max-age=#{cache_for.to_i}"}]] if cache_for
110
+ middlewares << [[Rack::Static, assetprops], nil]
111
+ end
112
+ end
113
+
114
+ yield self if block_given?
115
+ extclass.registered(Web::Application)
116
+ end
117
+ alias_method :register, :register_extension
118
+ end
119
+ end
120
+ end
@@ -1,14 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "uri"
4
- require "set"
5
4
  require "yaml"
6
- require "cgi"
5
+ require "cgi/escape"
7
6
 
8
7
  module Sidekiq
9
8
  # These methods are available to pages within the Web UI and UI extensions.
10
9
  # They are not public APIs for applications to use.
11
10
  module WebHelpers
11
+ def store_name
12
+ hash = redis_info
13
+ return "Dragonfly" if hash.has_key?("dragonfly_version")
14
+ return "Valkey" if hash.has_key?("valkey_version")
15
+ "Redis"
16
+ end
17
+
18
+ def store_version
19
+ hash = redis_info
20
+ return hash["dragonfly_version"] if hash.has_key?("dragonfly_version")
21
+ return hash["valkey_version"] if hash.has_key?("valkey_version")
22
+ hash["redis_version"]
23
+ end
24
+
12
25
  def style_tag(location, **kwargs)
13
26
  global = location.match?(/:\/\//)
14
27
  location = root_path + location if !global && !location.start_with?(root_path)
@@ -19,7 +32,9 @@ module Sidekiq
19
32
  nonce: csp_nonce,
20
33
  href: location
21
34
  }
22
- html_tag(:link, attrs.merge(kwargs))
35
+ add_to_head do
36
+ html_tag(:link, attrs.merge(kwargs))
37
+ end
23
38
  end
24
39
 
25
40
  def script_tag(location, **kwargs)
@@ -52,13 +67,13 @@ module Sidekiq
52
67
  end
53
68
 
54
69
  def strings(lang)
55
- @strings ||= {}
70
+ @@strings ||= {}
56
71
 
57
72
  # Allow sidekiq-web extensions to add locale paths
58
73
  # so extensions can be localized
59
- @strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
74
+ @@strings[lang] ||= config.locales.each_with_object({}) do |path, global|
60
75
  find_locale_files(lang).each do |file|
61
- strs = YAML.safe_load(File.read(file))
76
+ strs = YAML.safe_load_file(file)
62
77
  global.merge!(strs[lang])
63
78
  end
64
79
  end
@@ -77,25 +92,29 @@ module Sidekiq
77
92
  end
78
93
 
79
94
  def clear_caches
80
- @strings = nil
81
- @locale_files = nil
82
- @available_locales = nil
95
+ @@strings = nil
96
+ @@locale_files = nil
97
+ @@available_locales = nil
83
98
  end
84
99
 
85
100
  def locale_files
86
- @locale_files ||= settings.locales.flat_map { |path|
101
+ @@locale_files ||= config.locales.flat_map { |path|
87
102
  Dir["#{path}/*.yml"]
88
103
  }
89
104
  end
90
105
 
91
106
  def available_locales
92
- @available_locales ||= Set.new(locale_files.map { |path| File.basename(path, ".yml") })
107
+ @@available_locales ||= Set.new(locale_files.map { |path| File.basename(path, ".yml") })
93
108
  end
94
109
 
95
110
  def find_locale_files(lang)
96
111
  locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
97
112
  end
98
113
 
114
+ def language_name(locale)
115
+ strings(locale).fetch("LanguageName", locale)
116
+ end
117
+
99
118
  def search(jobset, substr)
100
119
  resultset = jobset.scan(substr).to_a
101
120
  @current_page = 1
@@ -111,13 +130,13 @@ module Sidekiq
111
130
  if within.nil?
112
131
  ::Rack::Utils.escape_html(jid)
113
132
  else
114
- "<a href='#{root_path}filter/#{within}?substr=#{jid}'>#{::Rack::Utils.escape_html(jid)}</a>"
133
+ "<a href='#{root_path}#{within}?substr=#{jid}'>#{::Rack::Utils.escape_html(jid)}</a>"
115
134
  end
116
135
  end
117
136
 
118
137
  def display_tags(job, within = "retries")
119
138
  job.tags.map { |tag|
120
- "<span class='label label-info jobtag'>#{filter_link(tag, within)}</span>"
139
+ "<span class='label label-info jobtag jobtag-#{Rack::Utils.escape_html(tag)}'>#{filter_link(tag, within)}</span>"
121
140
  }.join(" ")
122
141
  end
123
142
 
@@ -146,10 +165,13 @@ module Sidekiq
146
165
  text_direction == "rtl"
147
166
  end
148
167
 
149
- # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
168
+ # See https://www.rfc-editor.org/rfc/rfc9110.html#section-12.5.4
169
+ # Returns an array of language tags ordered by their quality value
170
+ #
171
+ # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
150
172
  def user_preferred_languages
151
173
  languages = env["HTTP_ACCEPT_LANGUAGE"]
152
- languages.to_s.downcase.gsub(/\s+/, "").split(",").map { |language|
174
+ languages.to_s.gsub(/\s+/, "").split(",").map { |language|
153
175
  locale, quality = language.split(";q=", 2)
154
176
  locale = nil if locale == "*" # Ignore wildcards
155
177
  quality = quality ? quality.to_f : 1.0
@@ -161,22 +183,30 @@ module Sidekiq
161
183
 
162
184
  # Given an Accept-Language header like "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2"
163
185
  # this method will try to best match the available locales to the user's preferred languages.
164
- #
165
- # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
166
186
  def locale
167
187
  # session[:locale] is set via the locale selector from the footer
168
188
  @locale ||= if (l = session&.fetch(:locale, nil)) && available_locales.include?(l)
169
189
  l
170
190
  else
171
- matched_locale = user_preferred_languages.map { |preferred|
172
- preferred_language = preferred.split("-", 2).first
191
+ matched_locale = nil
192
+ # Attempt to find a case-insensitive exact match first
193
+ user_preferred_languages.each do |preferred|
194
+ # We only care about the language and primary subtag
195
+ # "en-GB-oxendict" becomes "en-GB"
196
+ language_tag = preferred.split("-")[0..1].join("-")
197
+ matched_locale = available_locales.find { |available_locale| available_locale.casecmp?(language_tag) }
198
+ break if matched_locale
199
+ end
173
200
 
174
- lang_group = available_locales.select { |available|
175
- preferred_language == available.split("-", 2).first
176
- }
201
+ return matched_locale if matched_locale
177
202
 
178
- lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
179
- }.compact.first
203
+ # Find the first base language match
204
+ # "en-US,es-MX;q=0.9" matches "en"
205
+ user_preferred_languages.each do |preferred|
206
+ base_language = preferred.split("-", 2).first
207
+ matched_locale = available_locales.find { |available_locale| available_locale.casecmp?(base_language) }
208
+ break if matched_locale
209
+ end
180
210
 
181
211
  matched_locale || "en"
182
212
  end
@@ -184,7 +214,8 @@ module Sidekiq
184
214
 
185
215
  # sidekiq/sidekiq#3243
186
216
  def unfiltered?
187
- yield unless env["PATH_INFO"].start_with?("/filter/")
217
+ s = url_params("substr")
218
+ yield unless s && s.size > 0
188
219
  end
189
220
 
190
221
  def get_locale
@@ -201,7 +232,7 @@ module Sidekiq
201
232
  end
202
233
 
203
234
  def sort_direction_label
204
- (params[:direction] == "asc") ? "&uarr;" : "&darr;"
235
+ (url_params("direction") == "asc") ? "&uarr;" : "&darr;"
205
236
  end
206
237
 
207
238
  def workset
@@ -244,7 +275,7 @@ module Sidekiq
244
275
  end
245
276
 
246
277
  def redis_info
247
- Sidekiq.default_configuration.redis_info
278
+ @info ||= Sidekiq.default_configuration.redis_info
248
279
  end
249
280
 
250
281
  def root_path
@@ -268,8 +299,8 @@ module Sidekiq
268
299
  "#{score}-#{job["jid"]}"
269
300
  end
270
301
 
271
- def parse_params(params)
272
- score, jid = params.split("-", 2)
302
+ def parse_key(key)
303
+ score, jid = key.split("-", 2)
273
304
  [score.to_f, jid]
274
305
  end
275
306
 
@@ -279,11 +310,11 @@ module Sidekiq
279
310
  def qparams(options)
280
311
  stringified_options = options.transform_keys(&:to_s)
281
312
 
282
- to_query_string(params.merge(stringified_options))
313
+ to_query_string(request.params.merge(stringified_options))
283
314
  end
284
315
 
285
- def to_query_string(params)
286
- params.map { |key, value|
316
+ def to_query_string(hash)
317
+ hash.map { |key, value|
287
318
  SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
288
319
  }.compact.join("&")
289
320
  end
@@ -346,7 +377,7 @@ module Sidekiq
346
377
  elsif rss_kb < 10_000_000
347
378
  "#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
348
379
  else
349
- "#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)), precision: 1)} GB"
380
+ "#{number_with_delimiter(rss_kb / (1024.0 * 1024.0), precision: 1)} GB"
350
381
  end
351
382
  end
352
383
 
@@ -3,101 +3,88 @@
3
3
  require "rack"
4
4
 
5
5
  module Sidekiq
6
- module WebRouter
7
- GET = "GET"
8
- DELETE = "DELETE"
9
- POST = "POST"
10
- PUT = "PUT"
11
- PATCH = "PATCH"
12
- HEAD = "HEAD"
13
-
14
- ROUTE_PARAMS = "rack.route_params"
15
- REQUEST_METHOD = "REQUEST_METHOD"
16
- PATH_INFO = "PATH_INFO"
17
-
18
- def head(path, &block)
19
- route(HEAD, path, &block)
20
- end
6
+ class Web
7
+ # Provides an API to declare endpoints, along with a match
8
+ # API to dynamically route a request to an endpoint.
9
+ module Router
10
+ def head(path, &) = route(:head, path, &)
21
11
 
22
- def get(path, &block)
23
- route(GET, path, &block)
24
- end
12
+ def get(path, &) = route(:get, path, &)
25
13
 
26
- def post(path, &block)
27
- route(POST, path, &block)
28
- end
14
+ def post(path, &) = route(:post, path, &)
29
15
 
30
- def put(path, &block)
31
- route(PUT, path, &block)
32
- end
16
+ def put(path, &) = route(:put, path, &)
33
17
 
34
- def patch(path, &block)
35
- route(PATCH, path, &block)
36
- end
18
+ def patch(path, &) = route(:patch, path, &)
37
19
 
38
- def delete(path, &block)
39
- route(DELETE, path, &block)
40
- end
20
+ def delete(path, &) = route(:delete, path, &)
41
21
 
42
- def route(method, path, &block)
43
- @routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
44
-
45
- @routes[method] << WebRoute.new(method, path, block)
46
- end
47
-
48
- def match(env)
49
- request_method = env[REQUEST_METHOD]
50
- path_info = ::Rack::Utils.unescape env[PATH_INFO]
22
+ def route(*methods, path, &block)
23
+ methods.each do |method|
24
+ raise ArgumentError, "Invalid method #{method}. Must be one of #{@routes.keys.join(",")}" unless route_cache.has_key?(method)
25
+ route_cache[method] << Route.new(method, path, block)
26
+ end
27
+ end
51
28
 
52
- # There are servers which send an empty string when requesting the root.
53
- # These servers should be ashamed of themselves.
54
- path_info = "/" if path_info == ""
29
+ def match(env)
30
+ request_method = env["REQUEST_METHOD"].downcase.to_sym
31
+ path_info = ::Rack::Utils.unescape_path env["PATH_INFO"]
55
32
 
56
- @routes[request_method].each do |route|
57
- params = route.match(request_method, path_info)
58
- if params
59
- env[ROUTE_PARAMS] = params
33
+ # There are servers which send an empty string when requesting the root.
34
+ # These servers should be ashamed of themselves.
35
+ path_info = "/" if path_info == ""
60
36
 
61
- return WebAction.new(env, route.block)
37
+ route_cache[request_method].each do |route|
38
+ params = route.match(request_method, path_info)
39
+ if params
40
+ env["rack.route_params"] = params
41
+ return Action.new(env, route.block)
42
+ end
62
43
  end
44
+
45
+ nil
63
46
  end
64
47
 
65
- nil
48
+ def route_cache
49
+ @@routes ||= {get: [], post: [], put: [], patch: [], delete: [], head: []}
50
+ end
66
51
  end
67
- end
68
52
 
69
- class WebRoute
70
- attr_accessor :request_method, :pattern, :block, :name
53
+ class Route
54
+ attr_accessor :request_method, :pattern, :block, :name
71
55
 
72
- NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^.:$\/]+)/
56
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^.:$\/]+)/
73
57
 
74
- def initialize(request_method, pattern, block)
75
- @request_method = request_method
76
- @pattern = pattern
77
- @block = block
78
- end
58
+ def initialize(request_method, pattern, block)
59
+ @request_method = request_method
60
+ @pattern = pattern
61
+ @block = block
62
+ end
79
63
 
80
- def matcher
81
- @matcher ||= compile
82
- end
64
+ def matcher
65
+ @matcher ||= compile
66
+ end
83
67
 
84
- def compile
85
- if pattern.match?(NAMED_SEGMENTS_PATTERN)
86
- p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
68
+ def compile
69
+ if pattern.match?(NAMED_SEGMENTS_PATTERN)
70
+ p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
87
71
 
88
- Regexp.new("\\A#{p}\\Z")
89
- else
90
- pattern
72
+ Regexp.new("\\A#{p}\\Z")
73
+ else
74
+ pattern
75
+ end
91
76
  end
92
- end
93
77
 
94
- def match(request_method, path)
95
- case matcher
96
- when String
97
- {} if path == matcher
98
- else
99
- path_match = path.match(matcher)
100
- path_match&.named_captures&.transform_keys(&:to_sym)
78
+ EMPTY = {}.freeze
79
+
80
+ def match(request_method, path)
81
+ case matcher
82
+ when String
83
+ EMPTY if path == matcher
84
+ else
85
+ path_match = path.match(matcher)
86
+ path_match&.named_captures&.transform_keys(&:to_sym)
87
+ end
101
88
  end
102
89
  end
103
90
  end