urlmap.rb 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. module Rack
  2. # Rack::URLMap takes a hash mapping urls or paths to apps, and
  3. # dispatches accordingly. Support for HTTP/1.1 host names exists if
  4. # the URLs start with <tt>http://</tt> or <tt>https://</tt>.
  5. #
  6. # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
  7. # relevant for dispatch is in the SCRIPT_NAME, and the rest in the
  8. # PATH_INFO. This should be taken care of when you need to
  9. # reconstruct the URL in order to create links.
  10. #
  11. # URLMap dispatches in such a way that the longest paths are tried
  12. # first, since they are most specific.
  13. class URLMap
  14. NEGATIVE_INFINITY = -1.0 / 0.0
  15. def initialize(map = {})
  16. remap(map)
  17. end
  18. def remap(map)
  19. @mapping = map.map { |location, app|
  20. if location =~ %r{\Ahttps?://(.*?)(/.*)}
  21. host, location = $1, $2
  22. else
  23. host = nil
  24. end
  25. unless location[0] == ?/
  26. raise ArgumentError, "paths need to start with /"
  27. end
  28. location = location.chomp('/')
  29. match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
  30. [host, location, match, app]
  31. }.sort_by do |(host, location, _, _)|
  32. [host ? -host.size : NEGATIVE_INFINITY, -location.size]
  33. end
  34. end
  35. def call(env)
  36. path = env["PATH_INFO"]
  37. script_name = env['SCRIPT_NAME']
  38. hHost = env['HTTP_HOST']
  39. sName = env['SERVER_NAME']
  40. sPort = env['SERVER_PORT']
  41. @mapping.each do |host, location, match, app|
  42. unless hHost == host \
  43. || sName == host \
  44. || (!host && (hHost == sName || hHost == sName+':'+sPort))
  45. next
  46. end
  47. next unless m = match.match(path.to_s)
  48. rest = m[1]
  49. next unless !rest || rest.empty? || rest[0] == ?/
  50. env['SCRIPT_NAME'] = (script_name + location)
  51. env['PATH_INFO'] = rest
  52. return app.call(env)
  53. end
  54. [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
  55. ensure
  56. env['PATH_INFO'] = path
  57. env['SCRIPT_NAME'] = script_name
  58. end
  59. end
  60. end