http_router 0.6.5 → 0.6.6
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/Rakefile +66 -22
- data/examples/glob.ru +5 -5
- data/examples/rack_mapper.ru +7 -7
- data/examples/simple.ru +2 -2
- data/examples/static/config.ru +16 -16
- data/examples/variable.ru +3 -3
- data/examples/variable_with_regex.ru +4 -4
- data/lib/http_router/node/request.rb +3 -6
- data/lib/http_router/node.rb +10 -37
- data/lib/http_router/rack/builder.rb +69 -0
- data/lib/http_router/rack/url_map.rb +16 -0
- data/lib/http_router/rack.rb +19 -0
- data/lib/http_router/route.rb +34 -13
- data/lib/http_router/version.rb +1 -1
- data/lib/http_router.rb +22 -13
- data/test/test_variable.rb +4 -1
- metadata +7 -6
- data/examples/middleware.ru +0 -46
- data/examples/unnamed_variable.ru +0 -9
data/Rakefile
CHANGED
@@ -1,22 +1,66 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
require
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
desc "Run all tests"
|
5
|
+
task :test => ['test:integration', 'test:examples']
|
6
|
+
|
7
|
+
namespace :test do
|
8
|
+
desc "Run integration tests"
|
9
|
+
task :integration do
|
10
|
+
$: << 'lib'
|
11
|
+
require 'http_router'
|
12
|
+
require './test/helper'
|
13
|
+
Dir['./test/**/test_*.rb'].each { |test| require test }
|
14
|
+
end
|
15
|
+
desc "Run example tests"
|
16
|
+
task :examples do
|
17
|
+
$: << 'lib'
|
18
|
+
require 'http_router'
|
19
|
+
require 'thin'
|
20
|
+
Dir['./examples/**/*.ru'].each do |example|
|
21
|
+
print "running example #{example}..."
|
22
|
+
comments = File.read(example).split(/\n/).select{|l| l[0] == ?#}
|
23
|
+
pid = nil
|
24
|
+
Thin::Logging.silent = true
|
25
|
+
begin
|
26
|
+
pid = fork {
|
27
|
+
code = "Proc.new { \n#{File.read(example)}\n }"
|
28
|
+
r = eval(code, binding, example, 2)
|
29
|
+
Thin::Server.start(:signals => false, &r)
|
30
|
+
}
|
31
|
+
sleep 0.5
|
32
|
+
out = nil
|
33
|
+
assertion_count = 0
|
34
|
+
comments.each do |c|
|
35
|
+
c.gsub!(/^# ?/, '')
|
36
|
+
case c
|
37
|
+
when /^\$/
|
38
|
+
out = `#{c[1, c.size]} 2>/dev/null`.split(/\n/)
|
39
|
+
raise "#{c} produced #{out}" unless $?.success?
|
40
|
+
when /^=> ?(.*)/
|
41
|
+
c = $1
|
42
|
+
raise "out was nil" if out.nil?
|
43
|
+
test = out.shift
|
44
|
+
raise "excepted #{c.inspect}, recieved #{test.inspect}" unless c.strip == test.strip
|
45
|
+
assertion_count += 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
raise "no assertions were raised in #{example}" if assertion_count.zero?
|
49
|
+
puts "✔"
|
50
|
+
ensure
|
51
|
+
Process.kill('HUP', pid) if pid
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
require 'rake/rdoctask'
|
58
|
+
desc "Generate documentation"
|
59
|
+
Rake::RDocTask.new do |rd|
|
60
|
+
rd.main = "README.rdoc"
|
61
|
+
rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
62
|
+
rd.rdoc_dir = 'rdoc'
|
63
|
+
end
|
64
|
+
|
65
|
+
require 'code_stats'
|
66
|
+
CodeStats::Tasks.new(:reporting_depth => 3)
|
data/examples/glob.ru
CHANGED
@@ -4,8 +4,8 @@ run HttpRouter.new {
|
|
4
4
|
get('/*glob').to { |env| [200, {'Content-type' => 'text/plain'}, ["My glob is\n#{env['router.params'][:glob].map{|v| " * #{v}\n"}.join}"]]}
|
5
5
|
}
|
6
6
|
|
7
|
-
#
|
8
|
-
# My glob is
|
9
|
-
# * 123
|
10
|
-
# * 345
|
11
|
-
# * 123
|
7
|
+
# $ curl http://127.0.0.1:3000/123/345/123
|
8
|
+
# => My glob is
|
9
|
+
# => * 123
|
10
|
+
# => * 345
|
11
|
+
# => * 123
|
data/examples/rack_mapper.ru
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'http_router'
|
2
|
-
HttpRouter.
|
2
|
+
HttpRouter::Rack.override_rack_builder!
|
3
3
|
|
4
4
|
map('/get/:id') { |env|
|
5
5
|
[200, {'Content-type' => 'text/plain'}, ["My id is #{env['router.params'][:id]}\n"]]
|
@@ -14,9 +14,9 @@ map('/get/:id', :matching => {:id => /\d+/}) { |env|
|
|
14
14
|
[200, {'Content-type' => 'text/plain'}, ["My id is #{env['router.params'][:id]}, which is a number\n"]]
|
15
15
|
}
|
16
16
|
|
17
|
-
#
|
18
|
-
# My id is foo
|
19
|
-
#
|
20
|
-
# My id is foo and you posted!
|
21
|
-
#
|
22
|
-
# My id is 123, which is a number
|
17
|
+
# $ curl http://127.0.0.1:3000/get/foo
|
18
|
+
# => My id is foo
|
19
|
+
# $ curl -X POST http://127.0.0.1:3000/get/foo
|
20
|
+
# => My id is foo and you posted!
|
21
|
+
# $ curl -X POST http://127.0.0.1:3000/get/123
|
22
|
+
# => My id is 123, which is a number
|
data/examples/simple.ru
CHANGED
data/examples/static/config.ru
CHANGED
@@ -5,22 +5,22 @@ require 'http_router'
|
|
5
5
|
base = File.expand_path(File.dirname(__FILE__))
|
6
6
|
|
7
7
|
run HttpRouter.new {
|
8
|
-
|
9
|
-
|
8
|
+
add('/favicon.ico').static("#{base}/favicon.ico") # from a single file
|
9
|
+
add('/images').static("#{base}/images") # or from a directory
|
10
10
|
}
|
11
11
|
|
12
|
-
#
|
13
|
-
# HTTP/1.1 200 OK
|
14
|
-
# Last-Modified:
|
15
|
-
# Content-Type: image/vnd.microsoft.icon
|
16
|
-
# Content-Length: 1150
|
17
|
-
# Connection: keep-alive
|
18
|
-
# Server: thin 1.2.7 codename No Hup
|
12
|
+
# $ curl -I http://localhost:3000/favicon.ico
|
13
|
+
# => HTTP/1.1 200 OK
|
14
|
+
# => Last-Modified: Sat, 26 Mar 2011 18:04:26 GMT
|
15
|
+
# => Content-Type: image/vnd.microsoft.icon
|
16
|
+
# => Content-Length: 1150
|
17
|
+
# => Connection: keep-alive
|
18
|
+
# => Server: thin 1.2.7 codename No Hup
|
19
19
|
#
|
20
|
-
#
|
21
|
-
# HTTP/1.1 200 OK
|
22
|
-
# Last-Modified:
|
23
|
-
# Content-Type: image/jpeg
|
24
|
-
# Content-Length: 29817
|
25
|
-
# Connection: keep-alive
|
26
|
-
# Server: thin 1.2.7 codename No Hup
|
20
|
+
# $ curl -I http://localhost:3000/images/cat1.jpg
|
21
|
+
# => HTTP/1.1 200 OK
|
22
|
+
# => Last-Modified: Sat, 26 Mar 2011 18:04:26 GMT
|
23
|
+
# => Content-Type: image/jpeg
|
24
|
+
# => Content-Length: 29817
|
25
|
+
# => Connection: keep-alive
|
26
|
+
# => Server: thin 1.2.7 codename No Hup
|
data/examples/variable.ru
CHANGED
@@ -4,6 +4,6 @@ run HttpRouter.new {
|
|
4
4
|
get('/:variable').to { |env| [200, {'Content-type' => 'text/plain'}, ["my variables are\n#{env['router.params'].inspect}\n"]]}
|
5
5
|
}
|
6
6
|
|
7
|
-
#
|
8
|
-
# my variables are
|
9
|
-
# {:variable=>"heyguys"}
|
7
|
+
# $ curl http://127.0.0.1:3000/heyguys
|
8
|
+
# => my variables are
|
9
|
+
# => {:variable=>"heyguys"}
|
@@ -4,7 +4,7 @@ run HttpRouter.new {
|
|
4
4
|
get('/get/:id').matching(:id => /\d+/).to { |env| [200, {'Content-type' => 'text/plain'}, ["id is #{Integer(env['router.params'][:id]) * 2} * 2\n"]]}
|
5
5
|
}
|
6
6
|
|
7
|
-
#
|
8
|
-
# id is 246 * 2
|
9
|
-
#
|
10
|
-
#
|
7
|
+
# $ curl http://127.0.0.1:3000/get/123
|
8
|
+
# => id is 246 * 2
|
9
|
+
# $ curl http://127.0.0.1:3000/get/asd
|
10
|
+
# => Your request couldn't be found
|
@@ -1,10 +1,6 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Node
|
3
3
|
class Request < Node
|
4
|
-
def self.request_methods
|
5
|
-
[:host, :scheme, :request_method, :user_agent]
|
6
|
-
end
|
7
|
-
|
8
4
|
attr_reader :request_method
|
9
5
|
|
10
6
|
def initialize(router)
|
@@ -28,13 +24,14 @@ class HttpRouter
|
|
28
24
|
@request_method = meth == :method ? :request_method : meth
|
29
25
|
if @destination
|
30
26
|
next_node = add_catchall
|
31
|
-
next_node.instance_variable_set(:@destination,
|
32
|
-
@destination
|
27
|
+
next_node.instance_variable_set(:@destination, @destination)
|
28
|
+
@destination = nil
|
33
29
|
end
|
34
30
|
@request_method
|
35
31
|
end
|
36
32
|
|
37
33
|
def add_lookup(val)
|
34
|
+
@router.known_methods << val if @request_method == :request_method
|
38
35
|
@lookup[val] ||= Request.new(@router)
|
39
36
|
end
|
40
37
|
|
data/lib/http_router/node.rb
CHANGED
@@ -60,27 +60,7 @@ class HttpRouter
|
|
60
60
|
def destination(request_obj, match_partially = true)
|
61
61
|
request(request_obj)
|
62
62
|
arbitrary(request_obj)
|
63
|
-
|
64
|
-
@destination && @destination.each do |d|
|
65
|
-
if request_obj.path.empty? or d.route.match_partially? or (@router.ignore_trailing_slash? and request_obj.path.size == 1 and request_obj.path.last == '')
|
66
|
-
if request_obj.perform_call
|
67
|
-
env = request_obj.rack_request.dup.env
|
68
|
-
env['router.params'] ||= {}
|
69
|
-
env['router.params'].merge!(d.hashify_params(request_obj.params))
|
70
|
-
matched = if d.route.match_partially?
|
71
|
-
env['PATH_INFO'] = "/#{request_obj.path.join('/')}"
|
72
|
-
env['SCRIPT_NAME'] += request_obj.rack_request.path_info[0, request_obj.rack_request.path_info.size - env['PATH_INFO'].size]
|
73
|
-
else
|
74
|
-
env["PATH_INFO"] = ''
|
75
|
-
env["SCRIPT_NAME"] += request_obj.rack_request.path_info
|
76
|
-
end
|
77
|
-
throw :success, d.route.dest.call(env)
|
78
|
-
else
|
79
|
-
throw :success, Response.new(request_obj, d)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
63
|
+
@destination.call(request_obj, match_partially) if @destination
|
84
64
|
end
|
85
65
|
|
86
66
|
def add_variable
|
@@ -94,31 +74,25 @@ class HttpRouter
|
|
94
74
|
def add_request(opts)
|
95
75
|
@request ||= Request.new(@router)
|
96
76
|
next_requests = [@request]
|
97
|
-
|
98
|
-
method_index = Request.request_methods.index(method)
|
77
|
+
@router.request_methods.each_with_index do |method, method_index|
|
99
78
|
next_requests.map! do |next_request|
|
100
79
|
if opts[method].nil? && next_request.request_method.nil?
|
101
80
|
next_request
|
102
81
|
else
|
103
|
-
next_request_index = next_request.request_method &&
|
82
|
+
next_request_index = next_request.request_method && @router.request_methods.index(next_request.request_method)
|
104
83
|
rank = next_request_index ? method_index <=> next_request_index : 0
|
105
84
|
case rank
|
106
85
|
when 0
|
107
86
|
next_request.request_method = method
|
108
87
|
(opts[method].nil? ? [nil] : Array(opts[method])).map do |request_matcher|
|
109
88
|
case request_matcher
|
110
|
-
when nil
|
111
|
-
|
112
|
-
when
|
113
|
-
next_request.add_lookup(request_matcher)
|
114
|
-
when Regexp
|
115
|
-
next_request.add_linear(request_matcher)
|
89
|
+
when nil then next_request.add_catchall
|
90
|
+
when String then next_request.add_lookup(request_matcher)
|
91
|
+
when Regexp then next_request.add_linear(request_matcher)
|
116
92
|
end
|
117
93
|
end
|
118
|
-
when -1
|
119
|
-
|
120
|
-
when 1
|
121
|
-
next_request.transform_to(method)
|
94
|
+
when -1 then next_request
|
95
|
+
when 1 then next_request.transform_to(method)
|
122
96
|
end
|
123
97
|
end
|
124
98
|
end
|
@@ -147,9 +121,8 @@ class HttpRouter
|
|
147
121
|
@linear.last
|
148
122
|
end
|
149
123
|
|
150
|
-
def add_destination(
|
151
|
-
@destination
|
152
|
-
@destination << route
|
124
|
+
def add_destination(&dest)
|
125
|
+
@destination = dest
|
153
126
|
end
|
154
127
|
|
155
128
|
def add_lookup(part)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'http_router'
|
2
|
+
|
3
|
+
# Replacement for {Rack::Builder} which using HttpRouter to map requests instead of a simple Hash.
|
4
|
+
# As well, add convenience methods for the request methods.
|
5
|
+
module HttpRouter::Rack::BuilderMixin
|
6
|
+
def router
|
7
|
+
@router ||= HttpRouter.new
|
8
|
+
end
|
9
|
+
|
10
|
+
# Maps a path to a block.
|
11
|
+
# @param path [String] Path to map to.
|
12
|
+
# @param options [Hash] Options for added path.
|
13
|
+
# @see HttpRouter#add
|
14
|
+
def map(path, options = {}, method = nil, &block)
|
15
|
+
route = router.add(path, options)
|
16
|
+
route.send(method) if method
|
17
|
+
route.to(&block)
|
18
|
+
@ins << router unless @ins.last == router
|
19
|
+
route
|
20
|
+
end
|
21
|
+
|
22
|
+
# Maps a path with request methods `HEAD` and `GET` to a block.
|
23
|
+
# @param path [String] Path to map to.
|
24
|
+
# @param options [Hash] Options for added path.
|
25
|
+
# @see HttpRouter#add
|
26
|
+
def get(path, options = {}, &block)
|
27
|
+
map(path, options, :get, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Maps a path with request methods `POST` to a block.
|
31
|
+
# @param path [String] Path to map to.
|
32
|
+
# @param options [Hash] Options for added path.
|
33
|
+
# @see HttpRouter#add
|
34
|
+
def post(path, options = {}, &block)
|
35
|
+
map(path, options, :post, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Maps a path with request methods `PUT` to a block.
|
39
|
+
# @param path [String] Path to map to.
|
40
|
+
# @param options [Hash] Options for added path.
|
41
|
+
# @see HttpRouter#add
|
42
|
+
def put(path, options = {}, &block)
|
43
|
+
map(path, options, :put, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Maps a path with request methods `DELETE` to a block.
|
47
|
+
# @param path [String] Path to map to.
|
48
|
+
# @param options [Hash] Options for added path.
|
49
|
+
# @see HttpRouter#add
|
50
|
+
def delete(path, options = {}, &block)
|
51
|
+
map(path, options, :delete, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Maps a path with request methods `HEAD` to a block.
|
55
|
+
# @param path [String] Path to map to.
|
56
|
+
# @param options [Hash] Options for added path.
|
57
|
+
# @see HttpRouter#add
|
58
|
+
def head(path, options = {}, &block)
|
59
|
+
map(path, options, :head, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def options(path, options = {}, &block)
|
63
|
+
map(path, options, :options, &block)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class HttpRouter::Rack::Builder < ::Rack::Builder
|
68
|
+
include HttpRouter::Rack::BuilderMixin
|
69
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'http_router'
|
2
|
+
|
3
|
+
class HttpRouter
|
4
|
+
module Rack
|
5
|
+
class URLMap < ::Rack::URLMap
|
6
|
+
def initialize(map = {})
|
7
|
+
@router = HttpRouter.new
|
8
|
+
map.each { |path, app| (path =~ /^(https?):\/\/(.*?)(\/.*)/ ? @router.add($3).host($2).scheme($1) : @router.add(path)).partial.to(app) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
@router.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class HttpRouter
|
2
|
+
module Rack
|
3
|
+
autoload :URLMap, 'http_router/rack/url_map'
|
4
|
+
autoload :Builder, 'http_router/rack/builder'
|
5
|
+
autoload :BuilderMixin, 'http_router/rack/builder'
|
6
|
+
|
7
|
+
# Monkey-patches Rack::Builder to use HttpRouter.
|
8
|
+
# See examples/rack_mapper.rb
|
9
|
+
def self.override_rack_builder!
|
10
|
+
::Rack::Builder.class_eval("remove_method :map; include HttpRouter::Rack::BuilderMixin")
|
11
|
+
end
|
12
|
+
|
13
|
+
# Monkey-patches Rack::URLMap to use HttpRouter.
|
14
|
+
# See examples/rack_mapper.rb
|
15
|
+
def self.override_rack_urlmap!
|
16
|
+
::Rack.class_eval("OriginalURLMap = URLMap; HttpRouterURLMap = HttpRouter::Rack::URLMap; remove_const :URLMap; URLMap = HttpRouterURLMap")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/http_router/route.rb
CHANGED
@@ -11,12 +11,15 @@ class HttpRouter
|
|
11
11
|
@opts = opts
|
12
12
|
@arbitrary = opts[:arbitrary] || opts[:__arbitrary__]
|
13
13
|
@conditions = opts[:conditions] || opts[:__conditions__] || {}
|
14
|
-
name(opts
|
14
|
+
name(opts[:name]) if opts.key?(:name)
|
15
|
+
@opts.merge!(opts[:matching]) if opts[:matching]
|
15
16
|
@matches_with = {}
|
16
17
|
@default_values = opts[:default_values] || {}
|
17
18
|
if @original_path[-1] == ?*
|
18
19
|
@match_partially = true
|
19
20
|
path.slice!(-1)
|
21
|
+
elsif opts.key?(:partial)
|
22
|
+
@match_partially = opts[:partial]
|
20
23
|
end
|
21
24
|
@paths = OptionalCompiler.new(path).paths
|
22
25
|
end
|
@@ -106,11 +109,12 @@ class HttpRouter
|
|
106
109
|
self
|
107
110
|
end
|
108
111
|
|
109
|
-
def post;
|
110
|
-
def get;
|
111
|
-
def put;
|
112
|
-
def delete;
|
113
|
-
def head;
|
112
|
+
def post; request_method('POST'); end
|
113
|
+
def get; request_method('GET'); end
|
114
|
+
def put; request_method('PUT'); end
|
115
|
+
def delete; request_method('DELETE'); end
|
116
|
+
def head; request_method('HEAD'); end
|
117
|
+
def options; request_method('OPTIONS'); end
|
114
118
|
|
115
119
|
def arbitrary(blk = nil, &blk2)
|
116
120
|
arbitrary_with_continue { |req, params|
|
@@ -247,14 +251,31 @@ class HttpRouter
|
|
247
251
|
end
|
248
252
|
|
249
253
|
def add_non_path_to_tree(node, path, names)
|
250
|
-
nodes = if @conditions && !@conditions.empty?
|
251
|
-
node.add_request(@conditions)
|
252
|
-
else
|
253
|
-
[node]
|
254
|
-
end
|
255
|
-
@arbitrary.each{|a| nodes.map!{|n| n.add_arbitrary(a, match_partially?, names)} } if @arbitrary
|
256
254
|
path_obj = Path.new(self, path, names)
|
257
|
-
|
255
|
+
destination = Proc.new { |req, use_partial_matching|
|
256
|
+
if (use_partial_matching or req.path.empty?)
|
257
|
+
if req.path.empty? or match_partially? or (@router.ignore_trailing_slash? and req.path.size == 1 and req.path.last == '')
|
258
|
+
if req.perform_call
|
259
|
+
env = req.rack_request.dup.env
|
260
|
+
env['router.params'] ||= {}
|
261
|
+
env['router.params'].merge!(path_obj.hashify_params(req.params))
|
262
|
+
matched = if match_partially?
|
263
|
+
env['PATH_INFO'] = "/#{req.path.join('/')}"
|
264
|
+
env['SCRIPT_NAME'] += req.rack_request.path_info[0, req.rack_request.path_info.size - env['PATH_INFO'].size]
|
265
|
+
else
|
266
|
+
env["PATH_INFO"] = ''
|
267
|
+
env["SCRIPT_NAME"] += req.rack_request.path_info
|
268
|
+
end
|
269
|
+
throw :success, @app.call(env)
|
270
|
+
else
|
271
|
+
throw :success, Response.new(req, path_obj)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
}
|
276
|
+
nodes = @conditions && !@conditions.empty? ? node.add_request(@conditions) : [node]
|
277
|
+
@arbitrary.each{|a| nodes.map!{|n| n.add_arbitrary(a, match_partially?, names)} } if @arbitrary
|
278
|
+
nodes.map!{|n| n.add_destination(&destination)}
|
258
279
|
if dest.respond_to?(:url_mount=)
|
259
280
|
urlmount = UrlMount.new(@original_path, @default_values)
|
260
281
|
urlmount.url_mount = router.url_mount if router.url_mount
|
data/lib/http_router/version.rb
CHANGED
data/lib/http_router.rb
CHANGED
@@ -5,12 +5,13 @@ require 'http_router/request'
|
|
5
5
|
require 'http_router/response'
|
6
6
|
require 'http_router/route'
|
7
7
|
require 'http_router/path'
|
8
|
+
require 'http_router/rack'
|
8
9
|
require 'http_router/regex_route'
|
9
10
|
require 'http_router/optional_compiler'
|
10
11
|
|
11
12
|
class HttpRouter
|
12
13
|
|
13
|
-
attr_reader :root, :routes, :known_methods, :named_routes
|
14
|
+
attr_reader :root, :routes, :known_methods, :named_routes, :request_methods
|
14
15
|
attr_accessor :default_app, :url_mount
|
15
16
|
|
16
17
|
# Raised when a Route is not able to be generated.
|
@@ -27,12 +28,16 @@ class HttpRouter
|
|
27
28
|
# * :default_app -- Default application used if there is a non-match on #call. Defaults to 404 generator.
|
28
29
|
# * :ignore_trailing_slash -- Ignore a trailing / when attempting to match. Defaults to +true+.
|
29
30
|
# * :redirect_trailing_slash -- On trailing /, redirect to the same path without the /. Defaults to +false+.
|
31
|
+
# * :known_methods -- Array of http methods tested for 405s.
|
32
|
+
# * :request_methods -- Array of methods to use on request
|
30
33
|
def initialize(*args, &blk)
|
31
|
-
default_app, options
|
34
|
+
default_app, options = args.first.is_a?(Hash) ? [nil, args.first] : [args.first, args[1]]
|
32
35
|
@options = options
|
33
|
-
@default_app
|
34
|
-
@ignore_trailing_slash
|
36
|
+
@default_app = default_app || options && options[:default_app] || proc{|env| ::Rack::Response.new("Not Found", 404).finish }
|
37
|
+
@ignore_trailing_slash = options && options.key?(:ignore_trailing_slash) ? options[:ignore_trailing_slash] : true
|
35
38
|
@redirect_trailing_slash = options && options.key?(:redirect_trailing_slash) ? options[:redirect_trailing_slash] : false
|
39
|
+
@known_methods = Set.new(options && options[:known_methods] || [])
|
40
|
+
@request_methods = options && options[:request_methods] || [:host, :scheme, :request_method, :user_agent]
|
36
41
|
reset!
|
37
42
|
instance_eval(&blk) if blk
|
38
43
|
end
|
@@ -71,17 +76,17 @@ class HttpRouter
|
|
71
76
|
# Adds a path that only responds to the request method +GET+.
|
72
77
|
#
|
73
78
|
# Returns the route object.
|
74
|
-
def get(path, opts = {}, &app);
|
79
|
+
def get(path, opts = {}, &app); add_with_request_method(path, :get, opts, &app); end
|
75
80
|
|
76
81
|
# Adds a path that only responds to the request method +POST+.
|
77
82
|
#
|
78
83
|
# Returns the route object.
|
79
|
-
def post(path, opts = {}, &app);
|
84
|
+
def post(path, opts = {}, &app); add_with_request_method(path, :post, opts, &app); end
|
80
85
|
|
81
86
|
# Adds a path that only responds to the request method +HEAD+.
|
82
87
|
#
|
83
88
|
# Returns the route object.
|
84
|
-
def head(path, opts = {}, &app);
|
89
|
+
def head(path, opts = {}, &app); add_with_request_method(path, :head, opts, &app); end
|
85
90
|
|
86
91
|
# Adds a path that only responds to the request method +DELETE+.
|
87
92
|
#
|
@@ -91,7 +96,12 @@ class HttpRouter
|
|
91
96
|
# Adds a path that only responds to the request method +PUT+.
|
92
97
|
#
|
93
98
|
# Returns the route object.
|
94
|
-
def put(path, opts = {}, &app);
|
99
|
+
def put(path, opts = {}, &app); add_with_request_method(path, :put, opts, &app); end
|
100
|
+
|
101
|
+
# Adds a path that only responds to the request method +OPTIONS+.
|
102
|
+
#
|
103
|
+
# Returns the route object.
|
104
|
+
def options(path, opts = {}, &app); add_with_request_method(path, :options, opts, &app); end
|
95
105
|
|
96
106
|
def recognize(env)
|
97
107
|
call(env, false)
|
@@ -101,7 +111,7 @@ class HttpRouter
|
|
101
111
|
# the default application will be called. The router will be available in the env under the key <tt>router</tt>. And parameters matched will
|
102
112
|
# be available under the key <tt>router.params</tt>.
|
103
113
|
def call(env, perform_call = true)
|
104
|
-
rack_request = Rack::Request.new(env)
|
114
|
+
rack_request = ::Rack::Request.new(env)
|
105
115
|
if redirect_trailing_slash? && (rack_request.head? || rack_request.get?) && rack_request.path_info[-1] == ?/
|
106
116
|
response = ::Rack::Response.new
|
107
117
|
response.redirect(request.path_info[0, request.path_info.size - 1], 302)
|
@@ -111,9 +121,9 @@ class HttpRouter
|
|
111
121
|
response = catch(:success) { @root[request] }
|
112
122
|
if !response
|
113
123
|
supported_methods = (@known_methods - [env['REQUEST_METHOD']]).select do |m|
|
114
|
-
test_env = Rack::Request.new(rack_request.env.clone)
|
124
|
+
test_env = ::Rack::Request.new(rack_request.env.clone)
|
115
125
|
test_env.env['REQUEST_METHOD'] = m
|
116
|
-
test_env.env['
|
126
|
+
test_env.env['_HTTP_ROUTER_405_TESTING_ACCEPTANCE'] = true
|
117
127
|
test_request = Request.new(test_env.path_info, test_env, 405)
|
118
128
|
catch(:success) { @root[test_request] }
|
119
129
|
end
|
@@ -129,10 +139,9 @@ class HttpRouter
|
|
129
139
|
# Resets the router to a clean state.
|
130
140
|
def reset!
|
131
141
|
@root = Node.new(self)
|
132
|
-
@default_app = Proc.new{ |env| Rack::Response.new("Your request couldn't be found", 404).finish }
|
142
|
+
@default_app = Proc.new{ |env| ::Rack::Response.new("Your request couldn't be found", 404).finish }
|
133
143
|
@routes = []
|
134
144
|
@named_routes = {}
|
135
|
-
@known_methods = ['GET', "POST", "PUT", "DELETE"]
|
136
145
|
end
|
137
146
|
|
138
147
|
# Assigns the default application.
|
data/test/test_variable.rb
CHANGED
@@ -107,12 +107,15 @@ class TestVariable < MiniTest::Unit::TestCase
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def test_regex_and_greedy
|
110
|
-
with_regex, without_regex = router {
|
110
|
+
with_regex, without_regex, with_post = router {
|
111
111
|
add("/:common_variable/:matched").matching(:matched => /\d+/)
|
112
112
|
add("/:common_variable/:unmatched")
|
113
|
+
post("/:common_variable/:unmatched")
|
113
114
|
}
|
114
115
|
assert_route with_regex, '/common/123', {:common_variable => 'common', :matched => '123'}
|
115
116
|
assert_route without_regex, '/common/other', {:common_variable => 'common', :unmatched => 'other'}
|
117
|
+
assert_route with_regex, Rack::MockRequest.env_for('/common/123', :method => 'POST'), {:common_variable => 'common', :matched => '123'}
|
118
|
+
assert_route with_post, Rack::MockRequest.env_for('/common/other', :method => 'POST'), {:common_variable => 'common', :unmatched => 'other'}
|
116
119
|
end
|
117
120
|
|
118
121
|
if //.respond_to?(:names)
|
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: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 6
|
9
|
-
-
|
10
|
-
version: 0.6.
|
9
|
+
- 6
|
10
|
+
version: 0.6.6
|
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: 2011-03-
|
18
|
+
date: 2011-03-27 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -160,7 +160,6 @@ files:
|
|
160
160
|
- benchmarks/rec2.rb
|
161
161
|
- benchmarks/recognition_bm.rb
|
162
162
|
- examples/glob.ru
|
163
|
-
- examples/middleware.ru
|
164
163
|
- examples/rack_mapper.ru
|
165
164
|
- examples/simple.ru
|
166
165
|
- examples/static/config.ru
|
@@ -168,7 +167,6 @@ files:
|
|
168
167
|
- examples/static/images/cat1.jpg
|
169
168
|
- examples/static/images/cat2.jpg
|
170
169
|
- examples/static/images/cat3.jpg
|
171
|
-
- examples/unnamed_variable.ru
|
172
170
|
- examples/variable.ru
|
173
171
|
- examples/variable_with_regex.ru
|
174
172
|
- http_router.gemspec
|
@@ -184,6 +182,9 @@ files:
|
|
184
182
|
- lib/http_router/node/variable.rb
|
185
183
|
- lib/http_router/optional_compiler.rb
|
186
184
|
- lib/http_router/path.rb
|
185
|
+
- lib/http_router/rack.rb
|
186
|
+
- lib/http_router/rack/builder.rb
|
187
|
+
- lib/http_router/rack/url_map.rb
|
187
188
|
- lib/http_router/regex_route.rb
|
188
189
|
- lib/http_router/request.rb
|
189
190
|
- lib/http_router/response.rb
|
data/examples/middleware.ru
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'http_router'
|
2
|
-
|
3
|
-
use(HttpRouter, :middleware => true) {
|
4
|
-
add('/test').name(:test)
|
5
|
-
add('/:variable').name(:var)
|
6
|
-
add('/more/*glob').name(:glob)
|
7
|
-
add('/get/:id').matching(:id => /\d+/).name(:get)
|
8
|
-
}
|
9
|
-
|
10
|
-
run proc {|env|
|
11
|
-
[
|
12
|
-
200,
|
13
|
-
{'Content-type' => 'text/plain'},
|
14
|
-
[<<-HEREDOC
|
15
|
-
We matched? #{env['router.response'] && env['router.response'].matched? ? 'yes!' : 'no'}
|
16
|
-
Params are #{env['router.response'] && env['router.response'].matched? ? env['router.response'].params_as_hash.inspect : 'we had no params'}
|
17
|
-
That was fun
|
18
|
-
HEREDOC
|
19
|
-
]
|
20
|
-
]
|
21
|
-
}
|
22
|
-
|
23
|
-
# crapbook-pro:polleverywhere joshua$ curl http://127.0.0.1:3000/hi
|
24
|
-
# We matched? yes!
|
25
|
-
# Params are {:variable=>"hi"}
|
26
|
-
# That was fun
|
27
|
-
# crapbook-pro:polleverywhere joshua$ curl http://127.0.0.1:3000/test
|
28
|
-
# We matched? yes!
|
29
|
-
# Params are {}
|
30
|
-
# That was fun
|
31
|
-
# crapbook-pro:polleverywhere joshua$ curl http://127.0.0.1:3000/hey
|
32
|
-
# We matched? yes!
|
33
|
-
# Params are {:variable=>"hey"}
|
34
|
-
# That was fun
|
35
|
-
# crapbook-pro:polleverywhere joshua$ curl http://127.0.0.1:3000/more/fun/in/the/sun
|
36
|
-
# We matched? yes!
|
37
|
-
# Params are {:glob=>["fun", "in", "the", "sun"]}
|
38
|
-
# That was fun
|
39
|
-
# crapbook-pro:polleverywhere joshua$ curl http://127.0.0.1:3000/get/what
|
40
|
-
# We matched? no
|
41
|
-
# Params are we had no params
|
42
|
-
# That was fun
|
43
|
-
# crapbook-pro:polleverywhere joshua$ curl http://127.0.0.1:3000/get/123
|
44
|
-
# We matched? yes!
|
45
|
-
# Params are {:id=>"123"}
|
46
|
-
# That was fun
|
@@ -1,9 +0,0 @@
|
|
1
|
-
require 'http_router'
|
2
|
-
|
3
|
-
run HttpRouter.new {
|
4
|
-
get('/:').to { |env| [200, {'Content-type' => 'text/plain'}, ["my variables are\n#{env['router.params'].inspect}\n"]]}
|
5
|
-
}
|
6
|
-
|
7
|
-
# crapbook-pro:~ joshua$ curl http://127.0.0.1:3000/heyguys
|
8
|
-
# my variables are
|
9
|
-
# {:$1=>"heyguys"}
|