lib/sidekiq/web/router.rb
# frozen_string_literal: true require "rack" module Sidekiq class Web # Provides an API to declare endpoints, along with a match # API to dynamically route a request to an endpoint. module Router def head(path, &) = route(:head, path, &) def get(path, &) = route(:get, path, &) def post(path, &) = route(:post, path, &) def put(path, &) = route(:put, path, &) def patch(path, &) = route(:patch, path, &) def delete(path, &) = route(:delete, path, &) def route(*methods, path, &block) methods.each do |method| raise ArgumentError, "Invalid method #{method}. Must be one of #{@routes.keys.join(",")}" unless route_cache.has_key?(method) route_cache[method] << Route.new(method, path, block) end end def match(env) request_method = env["REQUEST_METHOD"].downcase.to_sym path_info = ::Rack::Utils.unescape_path env["PATH_INFO"] # There are servers which send an empty string when requesting the root. # These servers should be ashamed of themselves. path_info = "/" if path_info == "" route_cache[request_method].each do |route| params = route.match(request_method, path_info) if params env["rack.route_params"] = params return Action.new(env, route.block) end end nil end def route_cache @@routes ||= {get: [], post: [], put: [], patch: [], delete: [], head: []} end end class Route attr_accessor :request_method, :pattern, :block, :name NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^.:$\/]+)/ def initialize(request_method, pattern, block) @request_method = request_method @pattern = pattern @block = block end def matcher @matcher ||= compile end def compile if pattern.match?(NAMED_SEGMENTS_PATTERN) p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)') Regexp.new("\\A#{p}\\Z") else pattern end end EMPTY = {}.freeze def match(request_method, path) case matcher when String EMPTY if path == matcher else path_match = path.match(matcher) path_match&.named_captures&.transform_keys(&:to_sym) end end end end end