http_router 0.4.1 → 0.5.0

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 (42) hide show
  1. data/Rakefile +6 -5
  2. data/benchmarks/rack_mount.rb +16 -45
  3. data/benchmarks/rec2.rb +8 -8
  4. data/http_router.gemspec +5 -4
  5. data/lib/http_router/interface/sinatra.rb +7 -7
  6. data/lib/http_router/node.rb +106 -105
  7. data/lib/http_router/optional_compiler.rb +14 -5
  8. data/lib/http_router/path.rb +18 -28
  9. data/lib/http_router/root.rb +17 -29
  10. data/lib/http_router/route.rb +47 -16
  11. data/lib/http_router/static.rb +5 -0
  12. data/lib/http_router/variable.rb +2 -5
  13. data/lib/http_router/version.rb +1 -1
  14. data/lib/http_router.rb +38 -65
  15. data/test/helper.rb +74 -0
  16. data/test/rack/test_dispatch.rb +120 -0
  17. data/test/rack/test_route.rb +44 -0
  18. data/test/rack/test_urlmap.rb +12 -0
  19. data/{spec → test}/sinatra/recognize_spec.rb +0 -0
  20. data/test/sinatra/test_recognize.rb +150 -0
  21. data/test/test_arbitrary.rb +50 -0
  22. data/test/test_generate.rb +93 -0
  23. data/test/test_greedy.rb +24 -0
  24. data/test/test_interstitial.rb +47 -0
  25. data/test/test_misc.rb +30 -0
  26. data/test/test_mounting.rb +89 -0
  27. data/test/test_recognize.rb +56 -0
  28. data/test/test_request.rb +85 -0
  29. data/test/test_trailing_slash.rb +28 -0
  30. data/test/test_variable.rb +108 -0
  31. metadata +41 -32
  32. data/lib/http_router/response.rb +0 -46
  33. data/spec/generate_spec.rb +0 -234
  34. data/spec/misc_spec.rb +0 -65
  35. data/spec/mounting_spec.rb +0 -5
  36. data/spec/rack/dispatch_spec.rb +0 -119
  37. data/spec/rack/generate_spec.rb +0 -29
  38. data/spec/rack/middleware_spec.rb +0 -22
  39. data/spec/rack/route_spec.rb +0 -72
  40. data/spec/rack/urlmap_spec.rb +0 -13
  41. data/spec/recognize_spec.rb +0 -497
  42. data/spec/spec_helper.rb +0 -25
data/Rakefile CHANGED
@@ -1,12 +1,13 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
3
  require 'code_stats'
4
- require 'rspec/core/rake_task'
5
4
 
6
- desc "Run specs"
7
- RSpec::Core::RakeTask.new do |t|
8
- #t.rspec_opts = %w(--options spec/spec.opts)
9
- #t.ruby_opts = %w(-w)
5
+ desc "Run tests"
6
+ task :test do
7
+ $: << 'lib'
8
+ require 'http_router'
9
+ require 'test/helper'
10
+ Dir['test/**/test_*.rb'].each { |test| require test }
10
11
  end
11
12
 
12
13
  require 'rake/rdoctask'
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'rbench'
3
3
  require 'rack'
4
4
  require 'rack/mount'
5
- require '../usher/lib/usher'
5
+ #require '../usher/lib/usher'
6
6
  require 'lib/http_router'
7
7
 
8
8
  set = Rack::Mount::RouteSet.new do |set|
@@ -11,10 +11,10 @@ set = Rack::Mount::RouteSet.new do |set|
11
11
  set.add_route(proc{|env| [200, {'Content-type'=>'text/html'}, []]}, {:path => '/dynamic/:variable'}, {}, :variable)
12
12
  end
13
13
 
14
- u = Usher::Interface.for(:rack)
15
- u.add('/simple').to(proc{|env| [200, {'Content-type'=>'text/html'}, []]})
16
- u.add('/simple/again').to(proc{|env| [200, {'Content-type'=>'text/html'}, []]})
17
- u.add('/dynamic/anything').to(proc{|env| [200, {'Content-type'=>'text/html'}, []]})
14
+ #u = Usher::Interface.for(:rack)
15
+ #u.add('/simple').to(proc{|env| [200, {'Content-type'=>'text/html'}, []]})
16
+ #u.add('/simple/again').to(proc{|env| [200, {'Content-type'=>'text/html'}, []]})
17
+ #u.add('/dynamic/anything').to(proc{|env| [200, {'Content-type'=>'text/html'}, []]})
18
18
 
19
19
  hr = HttpRouter.new
20
20
  hr.add('/simple').to(proc{|env| [200, {'Content-type'=>'text/html'}, []]})
@@ -27,50 +27,21 @@ simple_env = Rack::MockRequest.env_for('/simple')
27
27
  simple2_env = Rack::MockRequest.env_for('/simple/again')
28
28
  simple_and_dynamic_env = Rack::MockRequest.env_for('/dynamic/anything')
29
29
 
30
- RBench.run(TIMES) do
30
+ 3.times do
31
31
 
32
- report "2 levels, static" do
33
- set.url(simple_env, :simple)
34
- end
35
-
36
- report "4 levels, static" do
37
- set.url(simple_env, :again)
38
- end
39
-
40
- report "4 levels, 1 dynamic" do
41
- set.url(simple_env, :variable, {:variable => 'onemore'})
42
- end
43
-
44
- end
32
+ RBench.run(TIMES) do
45
33
 
46
- RBench.run(TIMES) do
34
+ report "2 levels, static" do
35
+ set.url(simple_env, :simple)
36
+ end
47
37
 
48
- report "2 levels, static" do
49
- set.url(simple_env, :simple)
50
- end
51
-
52
- report "4 levels, static" do
53
- set.url(simple_env, :again)
54
- end
38
+ report "4 levels, static" do
39
+ set.url(simple_env, :again)
40
+ end
55
41
 
56
- report "4 levels, 1 dynamic" do
57
- set.url(simple_env, :variable, {:variable => 'onemore'})
58
- end
42
+ report "4 levels, 1 dynamic" do
43
+ set.url(simple_env, :variable, {:variable => 'onemore'})
44
+ end
59
45
 
60
- end
61
-
62
- RBench.run(TIMES) do
63
-
64
- report "2 levels, static" do
65
- set.url(simple_env, :simple)
66
46
  end
67
-
68
- report "4 levels, static" do
69
- set.url(simple_env, :again)
70
- end
71
-
72
- report "4 levels, 1 dynamic" do
73
- set.url(simple_env, :variable, {:variable => 'onemore'})
74
- end
75
-
76
47
  end
data/benchmarks/rec2.rb CHANGED
@@ -8,9 +8,9 @@ require 'http_router'
8
8
 
9
9
  u = HttpRouter.new
10
10
  u.add('/simple').to {|env| [200, {'Content-type'=>'text/html'}, []]}
11
- u.add('/simple/again').compile.to {|env| [200, {'Content-type'=>'text/html'}, []]}
11
+ u.add('/simple/again').to {|env| [200, {'Content-type'=>'text/html'}, []]}
12
12
  #u.add('/simple/again/and/again').compile.to {|env| [200, {'Content-type'=>'text/html'}, []]}
13
- u.add('/dynamic/:variable').compile.to {|env| [200, {'Content-type'=>'text/html'}, []]}
13
+ u.add('/dynamic/:variable').to {|env| [200, {'Content-type'=>'text/html'}, []]}
14
14
  #u.add('/rails/:controller/:action/:id').compile.to {|env| [200, {'Content-type'=>'text/html'}, []]}
15
15
  #u.add('/greedy/:greed').matching(:greed => /.*/).compile.to {|env| [200, {'Content-type'=>'text/html'}, []]}
16
16
  #u.add('/greedy/hey.:greed.html').to {|env| [200, {'Content-type'=>'text/html'}, []]}
@@ -27,10 +27,10 @@ puts Benchmark.measure {
27
27
  #
28
28
  TIMES = 50_000
29
29
 
30
- simple_env = Rack::MockRequest.env_for('/simple')
31
- simple2_env = Rack::MockRequest.env_for('/simple/again')
30
+ #simple_env =
31
+ #simple2_env =
32
32
  #simple3_env = Rack::MockRequest.env_for('/simple/again/and/again')
33
- simple_and_dynamic_env = Rack::MockRequest.env_for('/dynamic/anything')
33
+ #simple_and_dynamic_env =
34
34
  #simple_and_dynamic_env1 = Rack::MockRequest.env_for('/rails/controller/action/id')
35
35
  #simple_and_dynamic_env2 = Rack::MockRequest.env_for('/greedy/controller/action/id')
36
36
  #simple_and_dynamic_env3 = Rack::MockRequest.env_for('/greedy/hey.hello.html')
@@ -38,11 +38,11 @@ simple_and_dynamic_env = Rack::MockRequest.env_for('/dynamic/anything')
38
38
  RBench.run(TIMES) do
39
39
 
40
40
  report "2 levels, static" do
41
- u.call(simple_env).first == 200 or raise
41
+ u.call(Rack::MockRequest.env_for('/simple')).first == 200 or raise
42
42
  end
43
43
 
44
44
  report "4 levels, static" do
45
- u.call(simple2_env).first == 200 or raise
45
+ u.call(Rack::MockRequest.env_for('/simple/again')).first == 200 or raise
46
46
  end
47
47
 
48
48
  #report "8 levels, static" do
@@ -50,7 +50,7 @@ simple_and_dynamic_env = Rack::MockRequest.env_for('/dynamic/anything')
50
50
  #end
51
51
 
52
52
  report "4 levels, 1 dynamic" do
53
- u.call(simple_and_dynamic_env).first == 200 or raise
53
+ u.call(Rack::MockRequest.env_for('/dynamic/anything')).first == 200 or raise
54
54
  end
55
55
 
56
56
  #report "8 levels, 3 dynamic" do
data/http_router.gemspec CHANGED
@@ -20,14 +20,15 @@ Gem::Specification.new do |s|
20
20
  s.rubyforge_project = 'http_router'
21
21
 
22
22
  # dependencies
23
- s.add_runtime_dependency 'rack', '>= 1.0.0'
24
- s.add_runtime_dependency 'url_mount', '~> 0.2.1'
25
- s.add_development_dependency 'rspec', '~> 2.0.0'
23
+ s.add_runtime_dependency 'rack', '>= 1.0.0'
24
+ s.add_runtime_dependency 'url_mount', '~> 0.2.1'
25
+ s.add_development_dependency 'minitest', '~> 2.0.0'
26
26
  s.add_development_dependency 'code_stats'
27
27
  s.add_development_dependency 'rake'
28
28
  s.add_development_dependency 'sinatra'
29
29
  s.add_development_dependency 'rbench'
30
- s.add_development_dependency 'bundler', "~> 1.0.0"
30
+ s.add_development_dependency 'phocus'
31
+ s.add_development_dependency 'bundler', '~> 1.0.0'
31
32
 
32
33
  if s.respond_to? :specification_version then
33
34
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -25,16 +25,16 @@ class HttpRouter
25
25
  private
26
26
  def route!(base=self.class, pass_block=nil)
27
27
  if base.router and match = base.router.recognize(@request)
28
- if match.matched?
29
- @block_params = match.params
30
- (@params ||= {}).merge!(match.params_as_hash)
28
+ if match.first.respond_to?(:path)
29
+ @block_params = match.first.param_values
30
+ (@params ||= {}).merge!(match.first.params)
31
31
  pass_block = catch(:pass) do
32
- route_eval(&match.route.dest)
32
+ route_eval(&match.first.path.route.dest)
33
33
  end
34
- else
34
+ elsif match.is_a?(Array)
35
35
  route_eval {
36
- match.headers.each{|k,v| response[k] = v}
37
- status match.status
36
+ match[1].each{|k,v| response[k] = v}
37
+ status match[0]
38
38
  }
39
39
  end
40
40
  end
@@ -1,5 +1,17 @@
1
1
  class HttpRouter
2
2
  class Node
3
+ class Response < Struct.new(:path, :param_values)
4
+ attr_reader :params
5
+ def initialize(path, param_values)
6
+ super
7
+ if path.splitting_indexes
8
+ param_values = param_values.dup
9
+ path.splitting_indexes.each{|i| param_values[i] = param_values[i].split(HttpRouter::Parts::SLASH_RX)}
10
+ end
11
+ @params = path.route.default_values ? path.route.default_values.merge(path.hashify_params(param_values)) : path.hashify_params(param_values)
12
+ end
13
+ end
14
+
3
15
  attr_accessor :value, :variable, :catchall
4
16
  attr_reader :linear, :lookup, :request_node, :arbitrary_node
5
17
 
@@ -17,9 +29,7 @@ class HttpRouter
17
29
  if val.matches_with
18
30
  add_to_linear(val)
19
31
  else
20
- @catchall ||= router.node
21
- @catchall.variable = val
22
- @catchall
32
+ add_to_catchall(val)
23
33
  end
24
34
  elsif val.is_a?(Regexp)
25
35
  add_to_linear(val)
@@ -29,20 +39,6 @@ class HttpRouter
29
39
  end
30
40
  end
31
41
 
32
- def add_request_methods(options)
33
- if !options.empty?
34
- generate_request_method_tree(options)
35
- elsif @request_node
36
- current_node = @request_node
37
- while current_node.request_method
38
- current_node = (current_node.catchall ||= router.request_node)
39
- end
40
- [current_node]
41
- else
42
- [self]
43
- end
44
- end
45
-
46
42
  def add_to_linear(val)
47
43
  create_linear
48
44
  n = if @linear.assoc(val)
@@ -52,13 +48,7 @@ class HttpRouter
52
48
  @linear << [val, new_node]
53
49
  new_node
54
50
  end
55
- @linear.sort!{|a, b|
56
- if a.first.respond_to?(:priority) and b.first.respond_to?(:priority) ; b.first.priority <=> a.first.priority
57
- elsif a.first.respond_to?(:priority) ; -1
58
- elsif b.first.respond_to?(:priority) ; 1
59
- else ; 0
60
- end
61
- }
51
+ @linear.sort!{|a, b| b.first.priority <=> a.first.priority }
62
52
  n
63
53
  end
64
54
 
@@ -80,35 +70,20 @@ class HttpRouter
80
70
  target
81
71
  end
82
72
 
83
- protected
84
-
85
- attr_reader :router
86
-
87
- def transplant_value
88
- if @value
89
- target_node = @request_node
90
- while target_node.request_method
91
- target_node = (target_node.catchall ||= router.request_node)
92
- end
93
- target_node.value = @value
94
- @value = nil
95
- end
73
+ def add_to_catchall(val)
74
+ (@catchall ||= router.node).variable = val
75
+ @catchall
96
76
  end
97
77
 
98
- def generate_request_method_tree(request_options)
99
- raise UnsupportedRequestConditionError if (request_options.keys & RequestNode::RequestMethods).size != request_options.size
78
+ def add_request_methods(request_options)
79
+ raise UnsupportedRequestConditionError if request_options && (request_options.keys & RequestNode::RequestMethods).size != request_options.size
100
80
  current_nodes = [self]
101
81
  RequestNode::RequestMethods.each do |method|
102
- if request_options.key?(method) # so, the request method we care about it ..
103
- if current_nodes == [self]
104
- current_nodes = [@request_node ||= router.request_node]
105
- end
106
-
82
+ if request_options && request_options.key?(method) # so, the request method we care about it ..
83
+ current_nodes = [@request_node ||= router.request_node] if current_nodes == [self]
107
84
  for current_node_index in (0...current_nodes.size)
108
85
  current_node = current_nodes.at(current_node_index)
109
- unless current_node.request_method
110
- current_node.request_method = method
111
- end
86
+ current_node.request_method = method unless current_node.request_method
112
87
  case RequestNode::RequestMethods.index(method) <=> RequestNode::RequestMethods.index(current_node.request_method)
113
88
  when 0 #use this node
114
89
  Array(request_options[method]).each_with_index do |request_value, index|
@@ -134,66 +109,102 @@ class HttpRouter
134
109
  end
135
110
  end
136
111
  current_nodes.flatten!
137
- elsif current_nodes.first.is_a?(RequestNode) && !current_nodes.first.request_method.nil?
138
- current_nodes.map!{|n| n.catchall ||= router.request_node}
112
+ else
113
+ current_nodes.map!{|n| n.is_a?(RequestNode) && n.request_method == method ? (n.catchall ||= router.request_node) : n}
139
114
  end
140
115
  end
141
116
  transplant_value
142
117
  current_nodes
143
118
  end
144
119
 
120
+ protected
121
+
122
+ attr_reader :router
123
+
124
+ def transplant_value
125
+ if @value && @request_node
126
+ target_node = @request_node
127
+ while target_node.request_method
128
+ target_node = (target_node.catchall ||= router.request_node)
129
+ end
130
+ target_node.value ||= @value
131
+ @value = nil
132
+ end
133
+ end
134
+
145
135
  def escape_val(val)
146
136
  val.is_a?(Array) ? val.each{|v| HttpRouter.uri_unescape!(v)} : HttpRouter.uri_unescape!(val)
147
137
  val
148
138
  end
149
139
 
150
- def find_on_parts(request, parts, params = [], routes = [])
140
+ def find_on_parts(request, parts, action = :call, params = [])
151
141
  if parts and !parts.empty?
152
- if parts.size == 1 and parts.first == ''
153
- potential, match_parts, match_params = catch(:match) { find_on_parts(request, nil, params) }
154
- process_match(potential, nil, match_params, routes) if potential and potential.value and (router.ignore_trailing_slash? or potential.value.route.trailing_slash_ignore?)
155
- end
156
- if @linear && !@linear.empty?
157
- response, dupped_parts, dupped_params = nil, nil, nil
158
- next_node = @linear.find do |(tester, node)|
142
+ find_on_parts(request, nil, :"#{action}_with_trailing_slash", params) if parts.size == 1 and parts.first == ''
143
+ if @linear
144
+ dupped_parts, dupped_params = nil, nil
145
+ response = @linear.find do |(tester, node)|
159
146
  if tester.respond_to?(:matches?) and match = tester.matches?(parts)
160
147
  dupped_parts, dupped_params = parts.dup, params.dup
161
148
  dupped_params << escape_val(tester.consume(match, dupped_parts))
162
- node.find_on_parts(request, dupped_parts, dupped_params, routes)
149
+ node.find_on_parts(request, dupped_parts, action, dupped_params)
163
150
  elsif tester.respond_to?(:match) and match = tester.match(parts.whole_path) and match.begin(0) == 0
164
151
  dupped_parts, dupped_params = router.split(parts.whole_path[match[0].size, parts.whole_path.size]), params.dup
165
- node.find_on_parts(request, dupped_parts, dupped_params, routes)
152
+ node.find_on_parts(request, dupped_parts, action, dupped_params)
166
153
  else
167
154
  nil
168
155
  end
169
156
  end
170
157
  end
171
158
  if match = @lookup && @lookup[parts.first]
172
- match.find_on_parts(request, parts[1, parts.size - 1], params, routes)
159
+ match.find_on_parts(request, parts[1, parts.size - 1], action, params)
173
160
  end
174
- if @catchall
161
+ if catchall
175
162
  dupped_parts, dupped_params = parts.dup, params.dup
176
- dupped_params << escape_val(@catchall.variable.consume(nil, dupped_parts))
177
- @catchall.find_on_parts(request, dupped_parts, dupped_params, routes)
163
+ dupped_params << escape_val(catchall.variable.consume(nil, dupped_parts))
164
+ catchall.find_on_parts(request, dupped_parts, action, dupped_params)
178
165
  end
179
166
  end
180
- if request_node
181
- request_node.find_on_request_methods(request, parts, params, routes)
182
- elsif arbitrary_node
183
- arbitrary_node.find_on_arbitrary(request, parts, params, routes)
184
- elsif @value
185
- process_match(self, parts, params, routes)
186
- else
187
- nil
188
- end
167
+ request_node.find_on_request_methods(request, parts, action, params) if request_node
168
+ arbitrary_node.find_on_arbitrary(request, parts, action, params) if arbitrary_node
169
+ response = process_match(self, parts, params, request, action) if @value
170
+ nil
189
171
  end
190
172
 
191
- def process_match(node, parts, params, routes)
192
- if node.value.route.partially_match?
193
- routes.push << [node, parts, params]
194
- node
173
+ def process_match(node, parts, params, request, action)
174
+ env = request.env
175
+ case action
176
+ when :call, :call_with_trailing_slash
177
+ node.value.each do |path|
178
+ response_struct = Response.new(path, params)
179
+ previous_params = env['router.params']
180
+ env['router'] = router
181
+ env['router.params'] ||= {}
182
+ env['router.params'].merge!(response_struct.params)
183
+ env['router.response'] = response_struct
184
+ env['SCRIPT_NAME'] ||= ''
185
+ matched = if path.route.partially_match?
186
+ env['PATH_INFO'] = "#{HttpRouter::Parts::SLASH}#{parts && parts.join(HttpRouter::Parts::SLASH)}"
187
+ env['SCRIPT_NAME'] += request.path_info[0, request.path_info.size - env['PATH_INFO'].size]
188
+ true
189
+ elsif (parts and (action == :call_with_trailing_slash) and (router.ignore_trailing_slash? or (parts.size == 1 and parts.first == ''))) or parts.nil? || parts.empty?
190
+ env["PATH_INFO"] = ''
191
+ env["SCRIPT_NAME"] += request.path_info
192
+ true
193
+ else
194
+ false
195
+ end
196
+ if matched
197
+ response = path.route.dest.call(env)
198
+ env['router.last_repsonse'] = response
199
+ if response.first != 404 and response.first != 410
200
+ throw :response, response
201
+ end
202
+ end
203
+ end if node.value
204
+ when :nocall, :nocall_with_trailing_slash
205
+ throw :response, node.value.map{|path| Response.new(path, params)}
195
206
  else
196
- throw :match, [node, parts, params]
207
+ raise
197
208
  end
198
209
  end
199
210
 
@@ -208,15 +219,17 @@ class HttpRouter
208
219
  end
209
220
 
210
221
  class ArbitraryNode < Node
211
- def find_on_arbitrary(request, parts, params, routes)
222
+ def find_on_arbitrary(request, parts, action, params)
212
223
  next_node = @linear && !@linear.empty? && @linear.find { |(procs, node)|
213
- params_hash = node.value.hashify_params(params)
214
- procs.all?{|p| p.call(request, params_hash, node.value.route.dest)}
224
+ params_hash = node.value ? node.value.first.hashify_params(params) : {}
225
+ procs.all?{|p| p.call(request, params_hash)}
215
226
  }
216
227
  if next_node
217
- process_match(next_node.last, parts, params, routes)
228
+ process_match(next_node.last, parts, params, request, action)
218
229
  elsif @catchall
219
- process_match(@catchall, parts, params, routes)
230
+ process_match(@catchall, parts, params, request, action)
231
+ elsif @value
232
+ process_match(self, parts, params, request, action)
220
233
  end
221
234
  end
222
235
  end
@@ -224,32 +237,20 @@ class HttpRouter
224
237
  class RequestNode < Node
225
238
  RequestMethods = [:request_method, :host, :port, :scheme, :user_agent, :ip, :fullpath, :query_string].freeze
226
239
  attr_accessor :request_method
227
- def find_on_request_methods(request, parts, params, routes)
228
- next_node = if @request_method
240
+ def find_on_request_methods(request, parts, action, params)
241
+ if @request_method
229
242
  request_value = request.send(request_method)
230
- linear_node(request, parts, params, request_value, routes) or
231
- lookup_node(request, parts, params, request_value, routes) or
232
- catchall_node(request, parts, params, request_value, routes)
233
- end
234
- if next_node
235
- process_match(next_node, parts, params, routes)
236
- else
237
- find_on_parts(request, parts, params, routes)
238
- end
239
- end
240
- private
241
- def linear_node(request, parts, params, request_value, routes)
242
- if @linear && !@linear.empty?
243
- node = @linear.find { |(regexp, node)| regexp === request_value }
244
- node.last.find_on_request_methods(request, parts, params, routes) if node
243
+ if @linear && !@linear.empty? && match = @linear.find { |(regexp, node)| regexp === request_value }
244
+ match.last.find_on_request_methods(request, parts, action, params)
245
245
  end
246
+ @lookup[request_value].find_on_request_methods(request, parts, action, params) if @lookup and @lookup[request_value]
247
+ @catchall.find_on_request_methods(request, parts, action, params) if @catchall
246
248
  end
247
- def lookup_node(request, parts, params, request_value, routes)
248
- @lookup[request_value].find_on_request_methods(request, parts, params, routes) if @lookup and @lookup[request_value]
249
- end
250
- def catchall_node(request, parts, params, request_value, routes)
251
- @catchall.find_on_request_methods(request, parts, params, routes) if @catchall
249
+ if @value
250
+ process_match(self, parts, params, request, action)
251
+ elsif arbitrary_node
252
+ arbitrary_node.find_on_arbitrary(request, parts, action, params)
252
253
  end
254
+ end
253
255
  end
254
-
255
256
  end
@@ -7,11 +7,20 @@ class HttpRouter
7
7
  @paths = [""]
8
8
  @chars = path.split('')
9
9
  while !@chars.empty?
10
- case @chars.first
11
- when '(' then @chars.shift and double_paths
12
- when ')' then @chars.shift and half_paths
13
- when '\\' then @chars.shift and add_to_current_set(@chars.shift)
14
- else add_to_current_set(@chars.shift)
10
+ case @chars.first[0]
11
+ when ?(
12
+ @chars.shift and double_paths
13
+ when ?)
14
+ @chars.shift and half_paths
15
+ when ?\\
16
+ if @chars[1] == ?( || @chars[1] == ?)
17
+ @chars.shift
18
+ else
19
+ add_to_current_set(@chars.shift)
20
+ end
21
+ add_to_current_set(@chars.shift)
22
+ else
23
+ add_to_current_set(@chars.shift)
15
24
  end
16
25
  end
17
26
  @paths
@@ -3,30 +3,36 @@ class HttpRouter
3
3
  attr_reader :parts, :route, :splitting_indexes, :path
4
4
  def initialize(route, path, parts, splitting_indexes)
5
5
  @route, @path, @parts, @splitting_indexes = route, path, parts, splitting_indexes
6
-
7
6
  duplicate_variable_names = variable_names.dup.uniq!
8
7
  raise AmbiguousVariableException, "You have duplicate variable name present: #{duplicate_variable_names.join(', ')}" if duplicate_variable_names
9
-
10
- @path_validation_regex = path.split(/([:\*][a-zA-Z0-9_]+)/).map{ |part|
11
- case part[0]
8
+ regex_parts = path.split(/([:\*][a-zA-Z0-9_]+)/)
9
+ @path_validation_regex, code = '', ''
10
+ regex_parts.each_with_index{ |part, index|
11
+ new_part = case part[0]
12
12
  when ?:, ?*
13
- route.matches_with[part[1, part.size].to_sym] || '.*?'
13
+ if index != 0 && regex_parts[index - 1][-1] == ?\\
14
+ @path_validation_regex << Regexp.quote(part)
15
+ code << part
16
+ else
17
+ @path_validation_regex << (route.matches_with[part[1, part.size].to_sym] || '.*?').to_s
18
+ code << "\#{args.shift || (options && options.delete(:#{part[1, part.size]})) || raise(MissingParameterException, \"missing parameter :#{part[1, part.size]}\")}"
19
+ end
14
20
  else
15
- Regexp.quote(part)
21
+ @path_validation_regex << Regexp.quote(part)
22
+ code << part
16
23
  end
17
- }.join
24
+ new_part
25
+ }
18
26
  @path_validation_regex = Regexp.new("^#{@path_validation_regex}$")
19
-
20
- eval_path = path.gsub(/[:\*]([a-zA-Z0-9_]+)/) {"\#{args.shift || (options && options.delete(:#{$1})) || raise(MissingParameterException, \"missing parameter #{$1}\")}" }
21
27
  instance_eval "
22
28
  def raw_url(args,options)
23
- \"#{eval_path}\"
29
+ \"#{code}\"
24
30
  end
25
31
  "
26
32
  end
27
33
 
28
34
  def hashify_params(params)
29
- variable_names.zip(params).inject({}) { |h, (k,v)| h[k] = v; h }
35
+ variable_names && params ? variable_names.zip(params).inject({}) { |h, (k,v)| h[k] = v; h } : {}
30
36
  end
31
37
 
32
38
  def ===(other_path)
@@ -50,23 +56,7 @@ class HttpRouter
50
56
  raise InvalidRouteException if path !~ @path_validation_regex
51
57
  raise TooManyParametersException unless args.empty?
52
58
  HttpRouter.uri_escape!(path)
53
- generate_querystring(path, options)
54
- path
55
- end
56
-
57
- def generate_querystring(uri, params)
58
- if params && !params.empty?
59
- uri_size = uri.size
60
- params.each do |k,v|
61
- case v
62
- when Array
63
- v.each { |v_part| uri << '&' << ::Rack::Utils.escape(k.to_s) << '%5B%5D=' << ::Rack::Utils.escape(v_part.to_s) }
64
- else
65
- uri << '&' << ::Rack::Utils.escape(k.to_s) << '=' << ::Rack::Utils.escape(v.to_s)
66
- end
67
- end
68
- uri[uri_size] = ??
69
- end
59
+ [path, options]
70
60
  end
71
61
 
72
62
  def static?
@@ -9,40 +9,28 @@ class HttpRouter
9
9
  node
10
10
  end
11
11
 
12
- def find(request)
13
- routes = []
14
- node, parts, params = catch(:match) { find_on_parts(request, get_parts(request), [], routes) }
15
- if !routes.empty?
16
- routes.map { |node, parts, params| process_response(node, parts, params, request) }
17
- elsif node
18
- process_response(node, parts, params, request)
19
- elsif !router.request_methods_specified.empty?
20
- alternate_methods = (router.request_methods_specified - [request.request_method]).select do |alternate_method|
21
- test_request = request.dup
22
- test_request.env['REQUEST_METHOD'] = alternate_method
23
- routes = []
24
- catch(:match) { find_on_parts(test_request, get_parts(request), [], routes) } || !routes.empty?
25
- end
26
- alternate_methods.empty? ? nil : Response.unmatched(405, {"Allow" => alternate_methods.join(", ")})
27
- else
28
- nil
29
- end
12
+ def recognize(request)
13
+ call(request, :nocall)
30
14
  end
31
15
 
32
- def get_parts(request)
33
- parts = router.split(request.path_info.dup)
34
- parts << '' if request.path_info[-1] == ?/
35
- parts
16
+ def call(request, action = :call)
17
+ request = ::Rack::Request.new(request) if request.is_a?(Hash)
18
+ catch(:response) { find_on_parts(request, get_parts(request), action) } || construct_unmatched(request)
36
19
  end
37
20
 
38
- private
39
- def process_response(node, parts, params, request)
40
- if parts.nil? || parts.empty?
41
- Response.matched(node.value, params, request.path_info)
42
- elsif node.value.route.partially_match?
43
- rest = '/' << parts.join('/')
44
- Response.matched(node.value, params, request.path_info[0, request.path_info.size - rest.size], rest)
21
+ def construct_unmatched(request)
22
+ alternate_methods = (router.request_methods_specified - [request.request_method]).select do |alternate_method|
23
+ test_request = ::Rack::Request.new(request.env.dup)
24
+ test_request.env['REQUEST_METHOD'] = alternate_method
25
+ catch(:response) { find_on_parts(test_request, get_parts(request), :nocall) }
45
26
  end
27
+ alternate_methods.empty? ? nil : ::Rack::Response.new("Method not found", 405, {"Allow" => alternate_methods.join(", ")}).finish
28
+ end
29
+
30
+ def get_parts(request)
31
+ parts = router.split(request.path_info)
32
+ parts << '' if request.path_info.size > 1 && request.path_info[-1] == ?/
33
+ parts
46
34
  end
47
35
  end
48
36
  end