http_router 0.6.5 → 0.6.6
Sign up to get free protection for your applications and to get access to all the features.
- 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"}
|