http_router 0.3.13 → 0.3.14
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.
- data/README.rdoc +0 -8
- data/benchmarks/rec2.rb +2 -1
- data/lib/http_router/glob.rb +2 -1
- data/lib/http_router/interface/sinatra.rb +0 -1
- data/lib/http_router/node.rb +61 -61
- data/lib/http_router/path.rb +8 -9
- data/lib/http_router/route.rb +4 -4
- data/lib/http_router/version.rb +1 -1
- data/lib/http_router.rb +1 -1
- data/spec/recognize_spec.rb +45 -0
- metadata +4 -4
data/README.rdoc
CHANGED
@@ -1,13 +1,5 @@
|
|
1
1
|
= HTTP Router
|
2
2
|
|
3
|
-
== Introduction
|
4
|
-
|
5
|
-
When I wrote Usher, I made a few compromises in design that I wasn't totally happy with. More and more features got added to it, and eventually, it became harder to maintain. I took a few moments to work in Node.js, and wrote a router there called Sherpa, which I was happier with. But I felt that by losing more abstraction, and tackling just the problem of HTTP routing, I could come up with something even better.
|
6
|
-
|
7
|
-
== Warning
|
8
|
-
|
9
|
-
This is very new code. Lots of stuff probably doesn't work right. I will likely never support all the features I had in Usher. Documentation is super-sparse.
|
10
|
-
|
11
3
|
== Features
|
12
4
|
|
13
5
|
* Supports variables, and globbing, both named and unnamed.
|
data/benchmarks/rec2.rb
CHANGED
@@ -2,9 +2,10 @@ require 'rubygems'
|
|
2
2
|
require 'rbench'
|
3
3
|
|
4
4
|
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
5
|
-
|
6
5
|
require 'lib/http_router'
|
7
6
|
|
7
|
+
#require 'http_router'
|
8
|
+
|
8
9
|
u = HttpRouter.new
|
9
10
|
u.add('/simple').to {|env| [200, {'Content-type'=>'text/html'}, []]}
|
10
11
|
u.add('/simple/again').compile.to {|env| [200, {'Content-type'=>'text/html'}, []]}
|
data/lib/http_router/glob.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Glob < Variable
|
3
3
|
def matches?(parts)
|
4
|
-
@matches_with.nil? or
|
4
|
+
return if @matches_with.nil? or parts.empty? or !match.begin(0)
|
5
|
+
@matches_with.match(parts.first)
|
5
6
|
end
|
6
7
|
|
7
8
|
def consume(match, parts)
|
data/lib/http_router/node.rb
CHANGED
@@ -88,7 +88,7 @@ class HttpRouter
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def generate_request_method_tree(request_options)
|
91
|
-
raise
|
91
|
+
raise UnsupportedRequestConditionError if (request_options.keys & RequestNode::RequestMethods).size != request_options.size
|
92
92
|
current_nodes = [self]
|
93
93
|
RequestNode::RequestMethods.each do |method|
|
94
94
|
if request_options.key?(method) # so, the request method we care about it ..
|
@@ -136,47 +136,44 @@ class HttpRouter
|
|
136
136
|
|
137
137
|
def find_on_parts(request, parts, params)
|
138
138
|
if parts and !parts.empty?
|
139
|
-
if
|
140
|
-
potential_match = find_on_parts(request, [], params)
|
141
|
-
if potential_match and (router.ignore_trailing_slash? or potential_match.value && potential_match.value.route.trailing_slash_ignore?)
|
142
|
-
parts.shift
|
143
|
-
return potential_match
|
144
|
-
end
|
145
|
-
end
|
139
|
+
return potential if potential = potential_match(request, parts, params)
|
146
140
|
if @linear && !@linear.empty?
|
147
141
|
response = nil
|
148
142
|
dupped_parts = nil
|
143
|
+
dupped_params = nil
|
149
144
|
next_node = @linear.find do |(tester, node)|
|
150
145
|
if tester.respond_to?(:matches?) and match = tester.matches?(parts)
|
151
146
|
dupped_parts = parts.dup
|
152
|
-
|
153
|
-
|
147
|
+
dupped_params = params.dup
|
148
|
+
dupped_params.push((val = tester.consume(match, dupped_parts) and val.is_a?(Array)) ? val.map{|v| HttpRouter.uri_unescape(v)} : HttpRouter.uri_unescape(val))
|
149
|
+
parts.replace(dupped_parts) if response = node.find_on_parts(request, dupped_parts, dupped_params)
|
154
150
|
elsif tester.respond_to?(:match) and match = tester.match(parts.whole_path) and match.begin(0) == 0
|
155
151
|
dupped_parts = router.split(parts.whole_path[match[0].size, parts.whole_path.size])
|
156
|
-
|
152
|
+
dupped_params = params.dup
|
153
|
+
parts.replace(dupped_parts) if response = node.find_on_parts(request, dupped_parts, dupped_params)
|
157
154
|
else
|
158
155
|
nil
|
159
156
|
end
|
160
157
|
end
|
161
|
-
|
158
|
+
if response
|
159
|
+
params.replace(dupped_params)
|
160
|
+
return response
|
161
|
+
end
|
162
162
|
end
|
163
163
|
if match = @lookup && @lookup[parts.first]
|
164
|
-
parts.shift
|
165
|
-
|
166
|
-
|
164
|
+
part = parts.shift
|
165
|
+
if match = match.find_on_parts(request, parts, params)
|
166
|
+
return match
|
167
|
+
else
|
168
|
+
parts.unshift(part)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
if @catchall
|
167
172
|
params.push((val = @catchall.variable.consume(nil, parts) and val.is_a?(Array)) ? val.map{|v| HttpRouter.uri_unescape(v)} : HttpRouter.uri_unescape(val))
|
168
173
|
return @catchall.find_on_parts(request, parts, params)
|
169
174
|
end
|
170
175
|
end
|
171
|
-
|
172
|
-
request_node.find_on_request_methods(request)
|
173
|
-
elsif arbitrary_node
|
174
|
-
arbitrary_node.find_on_arbitrary(request)
|
175
|
-
elsif @value
|
176
|
-
self
|
177
|
-
else
|
178
|
-
nil
|
179
|
-
end
|
176
|
+
request_node and request_node.find_on_request_methods(request) or resolve_node(request)
|
180
177
|
end
|
181
178
|
|
182
179
|
def create_linear
|
@@ -186,57 +183,60 @@ class HttpRouter
|
|
186
183
|
def create_lookup
|
187
184
|
@lookup ||= {}
|
188
185
|
end
|
186
|
+
|
187
|
+
protected
|
188
|
+
def resolve_node(request)
|
189
|
+
if arbitrary_node
|
190
|
+
arbitrary_node.find_on_arbitrary(request)
|
191
|
+
elsif @value
|
192
|
+
self
|
193
|
+
else
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def potential_match(request, parts, params)
|
199
|
+
if parts.size == 1 and parts.first == ''
|
200
|
+
potential = find_on_parts(request, [], params)
|
201
|
+
if potential and (router.ignore_trailing_slash? or potential.value && potential.value.route.trailing_slash_ignore?)
|
202
|
+
parts.shift
|
203
|
+
potential
|
204
|
+
end
|
205
|
+
end
|
206
|
+
nil
|
207
|
+
end
|
189
208
|
end
|
190
209
|
|
191
210
|
class ArbitraryNode < Node
|
192
211
|
def find_on_arbitrary(request)
|
193
|
-
|
194
|
-
|
195
|
-
procs.all?{|p| p.call(request)}
|
196
|
-
end
|
197
|
-
return next_node.last if next_node
|
198
|
-
end
|
199
|
-
@catchall
|
212
|
+
next_node = @linear && !@linear.empty? && @linear.find { |(procs, node)| procs.all?{|p| p.call(request)} }
|
213
|
+
next_node && next_node.last || @catchall
|
200
214
|
end
|
201
215
|
end
|
202
216
|
|
203
217
|
class RequestNode < Node
|
204
|
-
RequestMethods =
|
218
|
+
RequestMethods = [:request_method, :host, :port, :scheme, :user_agent, :ip, :fullpath, :query_string].freeze
|
205
219
|
attr_accessor :request_method
|
206
|
-
|
207
|
-
|
208
|
-
if @request_method
|
209
|
-
request_method_satisfied = false
|
220
|
+
def find_on_request_methods(request)
|
221
|
+
next_node = if @request_method
|
210
222
|
request_value = request.send(request_method)
|
223
|
+
linear_node(request, request_value) or lookup_node(request, request_value) or catchall_node(request)
|
224
|
+
end
|
225
|
+
next_node or resolve_node(request)
|
226
|
+
end
|
227
|
+
private
|
228
|
+
def linear_node(request, request_value)
|
211
229
|
if @linear && !@linear.empty?
|
212
|
-
|
213
|
-
|
214
|
-
end
|
215
|
-
request_method_satisfied = true if next_node
|
216
|
-
next_node &&= next_node.last.find_on_request_methods(request)
|
217
|
-
return next_node if next_node
|
218
|
-
end
|
219
|
-
if @lookup and next_node = @lookup[request_value]
|
220
|
-
request_method_satisfied = true
|
221
|
-
next_node = next_node.find_on_request_methods(request)
|
222
|
-
return next_node if next_node
|
223
|
-
end
|
224
|
-
if @catchall
|
225
|
-
request_method_satisfied = true
|
226
|
-
next_node = @catchall.find_on_request_methods(request)
|
227
|
-
return next_node if next_node
|
230
|
+
node = @linear.find { |(regexp, node)| regexp === request_value }
|
231
|
+
node.last.find_on_request_methods(request) if node
|
228
232
|
end
|
229
233
|
end
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
else
|
236
|
-
nil
|
234
|
+
def lookup_node(request, request_value)
|
235
|
+
@lookup[request_value].find_on_request_methods(request) if @lookup and @lookup[request_value]
|
236
|
+
end
|
237
|
+
def catchall_node(request)
|
238
|
+
@catchall.find_on_request_methods(request) if @catchall
|
237
239
|
end
|
238
|
-
end
|
239
|
-
|
240
240
|
end
|
241
241
|
|
242
242
|
end
|
data/lib/http_router/path.rb
CHANGED
@@ -3,9 +3,9 @@ class HttpRouter
|
|
3
3
|
attr_reader :parts, :route, :splitting_indexes
|
4
4
|
def initialize(route, path, parts, splitting_indexes)
|
5
5
|
@route, @path, @parts, @splitting_indexes = route, path, parts, splitting_indexes
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
|
7
|
+
duplicate_variable_names = variable_names.dup.uniq!
|
8
|
+
raise AmbiguousVariableException, "You have duplicate variable name present: #{duplicate_variable_names.join(', ')}" if duplicate_variable_names
|
9
9
|
|
10
10
|
@path_validation_regex = path.split(/([:\*][a-zA-Z0-9_]+)/).map{ |part|
|
11
11
|
case part[0]
|
@@ -17,7 +17,7 @@ class HttpRouter
|
|
17
17
|
}.join
|
18
18
|
@path_validation_regex = Regexp.new("^#{@path_validation_regex}$")
|
19
19
|
|
20
|
-
eval_path = path.gsub(/[:\*]([a-zA-Z0-9_]+)/) {"\#{args.shift || (options && options.delete(:#{$1})) || raise(MissingParameterException
|
20
|
+
eval_path = path.gsub(/[:\*]([a-zA-Z0-9_]+)/) {"\#{args.shift || (options && options.delete(:#{$1})) || raise(MissingParameterException, \"missing parameter #{$1}\")}" }
|
21
21
|
instance_eval "
|
22
22
|
def raw_url(args,options)
|
23
23
|
\"#{eval_path}\"
|
@@ -35,17 +35,16 @@ class HttpRouter
|
|
35
35
|
|
36
36
|
def compare_parts(p1, p2)
|
37
37
|
case p1
|
38
|
-
when Glob
|
38
|
+
when Glob then p2.is_a?(Glob)
|
39
39
|
when Variable then p2.is_a?(Variable)
|
40
|
-
else
|
41
|
-
p1 == p2
|
40
|
+
else p1 == p2
|
42
41
|
end
|
43
42
|
end
|
44
43
|
|
45
44
|
def url(args, options)
|
46
45
|
path = raw_url(args, options)
|
47
|
-
raise InvalidRouteException
|
48
|
-
raise TooManyParametersException
|
46
|
+
raise InvalidRouteException if path !~ @path_validation_regex
|
47
|
+
raise TooManyParametersException unless args.empty?
|
49
48
|
HttpRouter.uri_escape!(path)
|
50
49
|
generate_querystring(path, options)
|
51
50
|
path
|
data/lib/http_router/route.rb
CHANGED
@@ -190,7 +190,7 @@ class HttpRouter
|
|
190
190
|
@paths = compile_paths
|
191
191
|
@paths.each_with_index do |p1, i|
|
192
192
|
@paths[i+1, @paths.size].each do |p2|
|
193
|
-
raise AmbiguousRouteException
|
193
|
+
raise AmbiguousRouteException if p1 === p2
|
194
194
|
end
|
195
195
|
end
|
196
196
|
@paths.each do |path|
|
@@ -207,7 +207,7 @@ class HttpRouter
|
|
207
207
|
|
208
208
|
# Sets the destination of this route to redirect to an arbitrary URL.
|
209
209
|
def redirect(path, status = 302)
|
210
|
-
raise
|
210
|
+
raise ArgumentError, "Status has to be an integer between 300 and 399" unless (300..399).include?(status)
|
211
211
|
to { |env|
|
212
212
|
params = env['router.params']
|
213
213
|
response = ::Rack::Response.new
|
@@ -247,7 +247,7 @@ class HttpRouter
|
|
247
247
|
else
|
248
248
|
matching_path(args, options)
|
249
249
|
end
|
250
|
-
raise UngeneratableRouteException
|
250
|
+
raise UngeneratableRouteException unless path
|
251
251
|
|
252
252
|
mount_point = nil
|
253
253
|
if !router.url_mount.nil?
|
@@ -363,7 +363,7 @@ class HttpRouter
|
|
363
363
|
end
|
364
364
|
|
365
365
|
def guard_compiled
|
366
|
-
raise AlreadyCompiledException
|
366
|
+
raise AlreadyCompiledException if compiled?
|
367
367
|
end
|
368
368
|
|
369
369
|
def significant_variable_names
|
data/lib/http_router/version.rb
CHANGED
data/lib/http_router.rb
CHANGED
data/spec/recognize_spec.rb
CHANGED
@@ -223,6 +223,13 @@ describe "HttpRouter#recognize" do
|
|
223
223
|
@router.recognize(Rack::MockRequest.env_for('/foo/id')).params_as_hash[:$1].should == 'id'
|
224
224
|
end
|
225
225
|
|
226
|
+
it "should use a static part as a variable if no further match is available" do
|
227
|
+
@router.add("/foo/foo").to(:test1)
|
228
|
+
@router.add("/:foo/foo2").to(:test2)
|
229
|
+
@router.recognize(Rack::MockRequest.env_for('/foo/foo')).dest.should == :test1
|
230
|
+
@router.recognize(Rack::MockRequest.env_for('/foo/foo2')).dest.should == :test2
|
231
|
+
end
|
232
|
+
|
226
233
|
it "should recognize /foo/:/: and map it to $1 and $2" do
|
227
234
|
@router.add("/foo/:/:").to(:test2)
|
228
235
|
@router.recognize(Rack::MockRequest.env_for('/foo/id/what')).dest.should == :test2
|
@@ -400,5 +407,43 @@ describe "HttpRouter#recognize" do
|
|
400
407
|
@router.recognize(Rack::MockRequest.env_for('/test.html')).params.first.should == 'test.html'
|
401
408
|
end
|
402
409
|
|
410
|
+
# BUG: http://gist.github.com/554909
|
411
|
+
context "when there is an additional route for the case of regex matching failure" do
|
412
|
+
before :each do
|
413
|
+
@matched = @router.add("/:common_variable/:matched").matching(:matched => /\d+/).to(:something)
|
414
|
+
@unmatched = @router.add("/:common_variable/:unmatched").to(:something_unmatched)
|
415
|
+
end
|
416
|
+
|
417
|
+
it "should use main route if pattern is matched" do
|
418
|
+
response = @router.recognize(Rack::MockRequest.env_for('/common/123'))
|
419
|
+
response.route.should == @matched
|
420
|
+
response.params.should == ['common', '123']
|
421
|
+
end
|
422
|
+
|
423
|
+
it "should use additional route if pattern is not matched" do
|
424
|
+
response = @router.recognize(Rack::MockRequest.env_for('/common/other'))
|
425
|
+
response.route.should == @unmatched
|
426
|
+
response.params.should == ['common', 'other']
|
427
|
+
end
|
428
|
+
|
429
|
+
context "when delimiter is not a slash" do
|
430
|
+
before :each do
|
431
|
+
@matched = @router.add("/:common_variable.:matched").matching(:matched => /\d+/).to(:something)
|
432
|
+
@unmatched = @router.add("/:common_variable.:unmatched").to(:something_unmatched)
|
433
|
+
end
|
434
|
+
|
435
|
+
it "should use main route if pattern is matched" do
|
436
|
+
response = @router.recognize(Rack::MockRequest.env_for('/common.123'))
|
437
|
+
response.route.should == @matched
|
438
|
+
response.params.should == ['common', '123']
|
439
|
+
end
|
440
|
+
|
441
|
+
it "should use additional route if pattern is not matched" do
|
442
|
+
response = @router.recognize(Rack::MockRequest.env_for('/common.other'))
|
443
|
+
response.route.should == @unmatched
|
444
|
+
response.params.should == ['common', 'other']
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
403
448
|
end
|
404
449
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_router
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 0.3.
|
9
|
+
- 14
|
10
|
+
version: 0.3.14
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Joshua Hull
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-09-01 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|