router.rb 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. require 'journey/core-ext/hash'
  2. require 'journey/router/utils'
  3. require 'journey/router/strexp'
  4. require 'journey/routes'
  5. require 'journey/formatter'
  6. before = $-w
  7. $-w = false
  8. require 'journey/parser'
  9. $-w = before
  10. require 'journey/route'
  11. require 'journey/path/pattern'
  12. module Journey
  13. class Router
  14. class RoutingError < ::StandardError
  15. end
  16. VERSION = '1.0.4'
  17. class NullReq # :nodoc:
  18. attr_reader :env
  19. def initialize env
  20. @env = env
  21. end
  22. def request_method
  23. env['REQUEST_METHOD']
  24. end
  25. def path_info
  26. env['PATH_INFO']
  27. end
  28. def ip
  29. env['REMOTE_ADDR']
  30. end
  31. def [](k); env[k]; end
  32. end
  33. attr_reader :request_class, :formatter
  34. attr_accessor :routes
  35. def initialize routes, options
  36. @options = options
  37. @params_key = options[:parameters_key]
  38. @request_class = options[:request_class] || NullReq
  39. @routes = routes
  40. end
  41. def call env
  42. env['PATH_INFO'] = Utils.normalize_path env['PATH_INFO']
  43. find_routes(env).each do |match, parameters, route|
  44. script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
  45. 'PATH_INFO',
  46. @params_key)
  47. unless route.path.anchored
  48. env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
  49. env['PATH_INFO'] = Utils.normalize_path(match.post_match)
  50. end
  51. env[@params_key] = (set_params || {}).merge parameters
  52. status, headers, body = route.app.call(env)
  53. if 'pass' == headers['X-Cascade']
  54. env['SCRIPT_NAME'] = script_name
  55. env['PATH_INFO'] = path_info
  56. env[@params_key] = set_params
  57. next
  58. end
  59. return [status, headers, body]
  60. end
  61. return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
  62. end
  63. def recognize req
  64. find_routes(req.env).each do |match, parameters, route|
  65. unless route.path.anchored
  66. req.env['SCRIPT_NAME'] = match.to_s
  67. req.env['PATH_INFO'] = match.post_match.sub(/^([^\/])/, '/\1')
  68. end
  69. yield(route, nil, parameters)
  70. end
  71. end
  72. def visualizer
  73. tt = GTG::Builder.new(ast).transition_table
  74. groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
  75. asts = groups.values.map { |v| v.first }
  76. tt.visualizer asts
  77. end
  78. private
  79. def partitioned_routes
  80. routes.partitioned_routes
  81. end
  82. def ast
  83. routes.ast
  84. end
  85. def simulator
  86. routes.simulator
  87. end
  88. def custom_routes
  89. partitioned_routes.last
  90. end
  91. def filter_routes path
  92. return [] unless ast
  93. data = simulator.match(path)
  94. data ? data.memos : []
  95. end
  96. def find_routes env
  97. req = request_class.new env
  98. routes = filter_routes(req.path_info) + custom_routes.find_all { |r|
  99. r.path.match(req.path_info)
  100. }
  101. routes.sort_by(&:precedence).find_all { |r|
  102. r.constraints.all? { |k,v| v === req.send(k) } &&
  103. r.verb === req.request_method
  104. }.reject { |r| req.ip && !(r.ip === req.ip) }.map { |r|
  105. match_data = r.path.match(req.path_info)
  106. match_names = match_data.names.map { |n| n.to_sym }
  107. match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
  108. info = Hash[match_names.zip(match_values).find_all { |_,y| y }]
  109. [match_data, r.defaults.merge(info), r]
  110. }
  111. end
  112. end
  113. end