http_router 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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