http_router 0.10.2 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/Rakefile +7 -5
- data/benchmarks/gen2.rb +1 -1
- data/benchmarks/rack_mount.rb +8 -14
- data/examples/rack_mapper.ru +12 -13
- data/examples/variable_with_regex.ru +1 -1
- data/http_router.gemspec +1 -1
- data/lib/http_router.rb +159 -62
- data/lib/http_router/generation_helper.rb +29 -0
- data/lib/http_router/generator.rb +150 -0
- data/lib/http_router/node.rb +27 -17
- data/lib/http_router/node/abstract_request_node.rb +31 -0
- data/lib/http_router/node/host.rb +9 -0
- data/lib/http_router/node/lookup.rb +8 -10
- data/lib/http_router/node/path.rb +23 -38
- data/lib/http_router/node/request_method.rb +16 -0
- data/lib/http_router/node/root.rb +104 -10
- data/lib/http_router/node/scheme.rb +9 -0
- data/lib/http_router/node/user_agent.rb +9 -0
- data/lib/http_router/regex_route_generation.rb +10 -0
- data/lib/http_router/request.rb +7 -17
- data/lib/http_router/response.rb +4 -0
- data/lib/http_router/route.rb +16 -277
- data/lib/http_router/route_helper.rb +126 -0
- data/lib/http_router/util.rb +1 -37
- data/lib/http_router/version.rb +1 -1
- data/test/common/generate.txt +1 -1
- data/test/generation.rb +15 -11
- data/test/generic.rb +2 -3
- data/test/helper.rb +15 -10
- data/test/rack/test_route.rb +0 -5
- data/test/test_misc.rb +50 -40
- data/test/test_mounting.rb +27 -26
- data/test/test_recognition.rb +1 -76
- metadata +104 -161
- data/.rspec +0 -1
- data/lib/http_router/node/arbitrary.rb +0 -30
- data/lib/http_router/node/request.rb +0 -52
- data/lib/http_router/rack.rb +0 -19
- data/lib/http_router/rack/builder.rb +0 -61
- data/lib/http_router/rack/url_map.rb +0 -16
- data/lib/http_router/regex_route.rb +0 -39
data/lib/http_router/request.rb
CHANGED
@@ -1,34 +1,24 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Request
|
3
|
-
attr_accessor :path, :params, :rack_request, :extra_env, :continue, :passed_with
|
4
|
-
attr_reader :
|
3
|
+
attr_accessor :path, :params, :rack_request, :extra_env, :continue, :passed_with, :called
|
4
|
+
attr_reader :acceptable_methods
|
5
5
|
alias_method :rack, :rack_request
|
6
|
-
|
7
|
-
|
6
|
+
alias_method :called?, :called
|
7
|
+
|
8
|
+
def initialize(path, rack_request)
|
9
|
+
@rack_request = rack_request
|
8
10
|
@path = URI.unescape(path).split(/\//)
|
9
11
|
@path.shift if @path.first == ''
|
10
12
|
@path.push('') if path[-1] == ?/
|
11
13
|
@extra_env = {}
|
12
14
|
@params = []
|
13
|
-
@
|
15
|
+
@acceptable_methods = Set.new
|
14
16
|
end
|
15
17
|
|
16
18
|
def joined_path
|
17
19
|
@path * '/'
|
18
20
|
end
|
19
21
|
|
20
|
-
def matched_route(response)
|
21
|
-
@matches << response
|
22
|
-
end
|
23
|
-
|
24
|
-
def perform_call
|
25
|
-
@perform_call == true
|
26
|
-
end
|
27
|
-
|
28
|
-
def testing_405?
|
29
|
-
@perform_call == 405
|
30
|
-
end
|
31
|
-
|
32
22
|
def to_s
|
33
23
|
"request path, #{path.inspect}"
|
34
24
|
end
|
data/lib/http_router/response.rb
CHANGED
data/lib/http_router/route.rb
CHANGED
@@ -1,291 +1,30 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
class HttpRouter
|
2
4
|
class Route
|
3
|
-
|
4
|
-
alias_method :match_partially?, :match_partially
|
5
|
-
alias_method :regex?, :regex
|
6
|
-
|
7
|
-
def initialize(router, path, opts = {})
|
8
|
-
@router, @original_path, @opts = router, path, opts
|
9
|
-
if @original_path
|
10
|
-
@match_partially = true and path.slice!(-1) if @original_path[/[^\\]\*$/]
|
11
|
-
@original_path[0, 0] = '/' if @original_path[0] != ?/
|
12
|
-
else
|
13
|
-
@match_partially = true
|
14
|
-
end
|
15
|
-
process_opts
|
16
|
-
end
|
17
|
-
|
18
|
-
def process_opts
|
19
|
-
@default_values = @opts[:__default_values__] || @opts[:default_values] || {}
|
20
|
-
@arbitrary = @opts[:__arbitrary__] || @opts[:arbitrary]
|
21
|
-
@matches_with = significant_variable_names.include?(:matching) ? @opts : @opts[:__matching__] || @opts[:matching] || {}
|
22
|
-
significant_variable_names.each do |name|
|
23
|
-
@matches_with[name] = @opts[name] if @opts.key?(name) && !@matches_with.key?(name)
|
24
|
-
end
|
25
|
-
@conditions = @opts[:__conditions__] || @opts[:conditions] || {}
|
26
|
-
@match_partially = @opts[:__partial__] if @match_partially.nil? && !@opts[:__partial__].nil?
|
27
|
-
@match_partially = @opts[:partial] if @match_partially.nil? && !@opts[:partial].nil?
|
28
|
-
name(@opts[:__name__] || @opts[:name]) if @opts.key?(:__name__) || @opts.key?(:name)
|
29
|
-
@needed_keys = significant_variable_names - @default_values.keys
|
30
|
-
end
|
31
|
-
|
32
|
-
def as_options
|
33
|
-
{:__matching__ => @matches_with, :__conditions__ => @conditions, :__default_values__ => @default_values, :__name__ => @named, :__partial__ => @partially_match, :__arbitrary__ => @arbitrary}
|
34
|
-
end
|
5
|
+
VALID_HTTP_VERBS = %w{GET POST PUT DELETE HEAD OPTIONS TRACE}
|
35
6
|
|
36
|
-
|
37
|
-
|
38
|
-
|
7
|
+
attr_reader :default_values, :router, :match_partially, :other_hosts, :paths, :request_methods, :name
|
8
|
+
attr_accessor :match_partially, :router, :host, :user_agent, :ignore_trailing_slash,
|
9
|
+
:path_for_generation, :path_validation_regex, :generator, :scheme, :original_path, :dest
|
39
10
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
def to(dest = nil, &dest2)
|
46
|
-
@dest = dest || dest2
|
47
|
-
add_path_to_tree
|
48
|
-
self
|
49
|
-
end
|
50
|
-
|
51
|
-
def name(n)
|
52
|
-
@named = n
|
53
|
-
@router.named_routes[n] << self
|
54
|
-
@router.named_routes[n].sort!{|r1, r2| r2.significant_variable_names.size <=> r1.significant_variable_names.size }
|
55
|
-
self
|
56
|
-
end
|
57
|
-
|
58
|
-
def request_method(*method)
|
59
|
-
add_to_contitions(:request_method, method)
|
60
|
-
end
|
61
|
-
|
62
|
-
def host(*host)
|
63
|
-
add_to_contitions(:host, host)
|
64
|
-
end
|
65
|
-
|
66
|
-
def scheme(*scheme)
|
67
|
-
add_to_contitions(:scheme, scheme)
|
68
|
-
end
|
69
|
-
|
70
|
-
def user_agent(*user_agent)
|
71
|
-
add_to_contitions(:user_agent, user_agent)
|
72
|
-
end
|
73
|
-
|
74
|
-
def add_to_contitions(name, *vals)
|
75
|
-
((@conditions ||= {})[name] ||= []).concat(vals.flatten)
|
76
|
-
self
|
77
|
-
end
|
78
|
-
|
79
|
-
def matching(matchers)
|
80
|
-
@matches_with.merge!(matchers.is_a?(Array) ? Hash[*matchers] : matchers)
|
81
|
-
self
|
82
|
-
end
|
83
|
-
|
84
|
-
def default(defaults)
|
85
|
-
(@default_values ||= {}).merge!(defaults)
|
86
|
-
self
|
87
|
-
end
|
88
|
-
|
89
|
-
# Sets the destination of this route to redirect to an arbitrary URL.
|
90
|
-
def redirect(path, status = 302)
|
91
|
-
raise ArgumentError, "Status has to be an integer between 300 and 399" unless (300..399).include?(status)
|
92
|
-
to { |env|
|
93
|
-
params = env['router.params']
|
94
|
-
response = ::Rack::Response.new
|
95
|
-
response.redirect(eval(%|"#{path}"|), status)
|
96
|
-
response.finish
|
97
|
-
}
|
98
|
-
self
|
99
|
-
end
|
100
|
-
|
101
|
-
# Sets the destination of this route to serve static files from either a directory or a single file.
|
102
|
-
def static(root)
|
103
|
-
if File.directory?(root)
|
104
|
-
partial.to ::Rack::File.new(root)
|
105
|
-
else
|
106
|
-
to {|env| env['PATH_INFO'] = File.basename(root); ::Rack::File.new(File.dirname(root)).call(env) }
|
107
|
-
end
|
108
|
-
self
|
109
|
-
end
|
110
|
-
|
111
|
-
def post; request_method('POST'); end
|
112
|
-
def get; request_method('GET'); end
|
113
|
-
def put; request_method('PUT'); end
|
114
|
-
def delete; request_method('DELETE'); end
|
115
|
-
def head; request_method('HEAD'); end
|
116
|
-
def options; request_method('OPTIONS'); end
|
117
|
-
def patch; request_method('PATCH'); end
|
118
|
-
def trace; request_method('TRACE'); end
|
119
|
-
def conenct; request_method('CONNECT'); end
|
120
|
-
|
121
|
-
def arbitrary(blk = nil, &blk2)
|
122
|
-
arbitrary_with_continue { |req, params|
|
123
|
-
req.continue[(blk || blk2)[req, params]]
|
124
|
-
}
|
125
|
-
end
|
126
|
-
|
127
|
-
def arbitrary_with_continue(blk = nil, &blk2)
|
128
|
-
(@arbitrary ||= []) << (blk || blk2)
|
129
|
-
self
|
130
|
-
end
|
131
|
-
|
132
|
-
def url(*args)
|
133
|
-
result, extra_params = url_with_params(*args)
|
134
|
-
append_querystring(result, extra_params)
|
135
|
-
end
|
136
|
-
|
137
|
-
def clone(new_router)
|
138
|
-
Route.new(new_router, @original_path.dup, as_options)
|
139
|
-
end
|
140
|
-
|
141
|
-
def url_with_params(*a)
|
142
|
-
url_args_processing(a) do |args, options|
|
143
|
-
path = args.empty? ? matching_path(options) : matching_path(args, options)
|
144
|
-
raise InvalidRouteException unless path
|
145
|
-
path.url(args, options)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def url_args_processing(args)
|
150
|
-
options = args.last.is_a?(Hash) ? args.pop : nil
|
151
|
-
options = options.nil? ? default_values.dup : default_values.merge(options) if default_values
|
152
|
-
options.delete_if{ |k,v| v.nil? } if options
|
153
|
-
result, params = yield args, options
|
154
|
-
mount_point = router.url_mount && router.url_mount.url(options)
|
155
|
-
mount_point ? [File.join(mount_point, result), params] : [result, params]
|
156
|
-
end
|
157
|
-
|
158
|
-
def significant_variable_names
|
159
|
-
@significant_variable_names ||= @original_path.nil? ? [] : @original_path.scan(/(^|[^\\])[:\*]([a-zA-Z0-9_]+)/).map{|p| p.last.to_sym}
|
160
|
-
end
|
161
|
-
|
162
|
-
def matching_path(params, other_hash = nil)
|
163
|
-
return @paths.first if @paths.size == 1
|
164
|
-
case params
|
165
|
-
when Array
|
166
|
-
significant_keys = other_hash && significant_variable_names & other_hash.keys
|
167
|
-
@paths.find { |path| path.param_names.size == (significant_keys ? params.size + significant_keys.size : params.size) }
|
168
|
-
when Hash
|
169
|
-
@paths.find { |path| (params && !params.empty? && (path.param_names & params.keys).size == path.param_names.size) || path.param_names.empty? }
|
170
|
-
end
|
11
|
+
def create_clone(new_router)
|
12
|
+
r = clone
|
13
|
+
r.dest = (begin; dest.clone; rescue; dest; end)
|
14
|
+
r
|
171
15
|
end
|
172
16
|
|
173
17
|
def to_s
|
174
|
-
"#<HttpRouter:Route #{object_id} @
|
175
|
-
end
|
176
|
-
|
177
|
-
private
|
178
|
-
def raw_paths
|
179
|
-
return [] if @original_path.nil?
|
180
|
-
@raw_paths ||= begin
|
181
|
-
start_index, end_index = 0, 1
|
182
|
-
@raw_paths, chars = [""], @original_path.split('')
|
183
|
-
until chars.empty?
|
184
|
-
case fc = chars.first[0]
|
185
|
-
when ?(
|
186
|
-
chars.shift
|
187
|
-
(start_index...end_index).each { |path_index| raw_paths << raw_paths[path_index].dup }
|
188
|
-
start_index = end_index
|
189
|
-
end_index = raw_paths.size
|
190
|
-
when ?)
|
191
|
-
chars.shift
|
192
|
-
start_index -= end_index - start_index
|
193
|
-
else
|
194
|
-
c = if chars[0][0] == ?\\ && (chars[1][0] == ?( || chars[1][0] == ?)); chars.shift; chars.shift; else; chars.shift; end
|
195
|
-
(start_index...end_index).each { |path_index| raw_paths[path_index] << c }
|
196
|
-
end
|
197
|
-
end
|
198
|
-
@raw_paths.reverse!
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def add_normal_part(node, part, param_names)
|
203
|
-
name = part[1, part.size]
|
204
|
-
node = case part[0]
|
205
|
-
when ?\\
|
206
|
-
node.add_lookup(part[1].chr)
|
207
|
-
when ?:
|
208
|
-
param_names << name.to_sym
|
209
|
-
@matches_with[name.to_sym] ? node.add_spanning_match(@matches_with[name.to_sym]) : node.add_variable
|
210
|
-
when ?*
|
211
|
-
param_names << name.to_sym
|
212
|
-
@matches_with[name.to_sym] ? node.add_glob_regexp(@matches_with[name.to_sym]) : node.add_glob
|
213
|
-
else
|
214
|
-
node.add_lookup(part)
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
def add_complex_part(node, parts, param_names)
|
219
|
-
capturing_indicies, splitting_indicies, captures, spans = [], [], 0, false
|
220
|
-
regex = parts.inject('') do |reg, part|
|
221
|
-
reg << case part[0]
|
222
|
-
when ?\\ then Regexp.quote(part[1].chr)
|
223
|
-
when ?:, ?*
|
224
|
-
spans = true if part[0] == ?*
|
225
|
-
captures += 1
|
226
|
-
(part[0] == ?* ? splitting_indicies : capturing_indicies) << captures
|
227
|
-
name = part[1, part.size].to_sym
|
228
|
-
param_names << name
|
229
|
-
if spans
|
230
|
-
@matches_with[name] ? "((?:#{@matches_with[name]}\\/?)+)" : '(.*?)'
|
231
|
-
else
|
232
|
-
"(#{(@matches_with[name] || '[^/]*?')})"
|
233
|
-
end
|
234
|
-
else
|
235
|
-
Regexp.quote(part)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
spans ? node.add_spanning_match(Regexp.new("#{regex}$"), capturing_indicies, splitting_indicies) :
|
239
|
-
node.add_match(Regexp.new("#{regex}$"), capturing_indicies, splitting_indicies)
|
240
|
-
end
|
241
|
-
|
242
|
-
def add_path_to_tree
|
243
|
-
raise DoubleCompileError if compiled?
|
244
|
-
@paths ||= begin
|
245
|
-
if raw_paths.empty?
|
246
|
-
add_non_path_to_tree(@router.root, nil, [])
|
247
|
-
else
|
248
|
-
raw_paths.map do |path|
|
249
|
-
param_names = []
|
250
|
-
node = @router.root
|
251
|
-
path.split(/\//).each do |part|
|
252
|
-
next if part == ''
|
253
|
-
parts = part.scan(/\\.|[:*][a-z0-9_]+|[^:*\\]+/)
|
254
|
-
node = parts.size == 1 ? add_normal_part(node, part, param_names) : add_complex_part(node, parts, param_names)
|
255
|
-
end
|
256
|
-
add_non_path_to_tree(node, path, param_names)
|
257
|
-
end
|
258
|
-
end
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
def add_non_path_to_tree(node, path, names)
|
263
|
-
node = node.add_request(@conditions) unless @conditions.empty?
|
264
|
-
@arbitrary.each{|a| node = node.add_arbitrary(a, match_partially?, names)} if @arbitrary
|
265
|
-
path_obj = node.add_destination(self, path, names)
|
266
|
-
if dest.respond_to?(:url_mount=)
|
267
|
-
urlmount = UrlMount.new(@original_path, @default_values)
|
268
|
-
urlmount.url_mount = router.url_mount if router.url_mount
|
269
|
-
dest.url_mount = urlmount
|
270
|
-
end
|
271
|
-
path_obj
|
18
|
+
"#<HttpRouter:Route #{object_id} @path_for_generation=#{path_for_generation.inspect}>"
|
272
19
|
end
|
273
20
|
|
274
|
-
def
|
275
|
-
|
276
|
-
when Array then value.each{ |v| append_querystring_value(uri, "#{key}[]", v) }
|
277
|
-
when Hash then value.each{ |k, v| append_querystring_value(uri, "#{key}[#{k}]", v) }
|
278
|
-
else uri << '&' << CGI.escape(key.to_s) << '=' << CGI.escape(value.to_s)
|
279
|
-
end
|
21
|
+
def matches_with(var_name)
|
22
|
+
@match_with && @match_with[:"#{var_name}"]
|
280
23
|
end
|
281
24
|
|
282
|
-
def
|
283
|
-
|
284
|
-
|
285
|
-
params.each{ |k,v| append_querystring_value(uri, k, v) }
|
286
|
-
uri[uri_size] = ??
|
287
|
-
end
|
288
|
-
uri
|
25
|
+
def name=(name)
|
26
|
+
@name = name
|
27
|
+
router.named_routes[name] << self
|
289
28
|
end
|
290
29
|
end
|
291
30
|
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
class HttpRouter
|
2
|
+
module RouteHelper
|
3
|
+
def path
|
4
|
+
@route.path_for_generation
|
5
|
+
end
|
6
|
+
|
7
|
+
def path=(path)
|
8
|
+
@original_path = path
|
9
|
+
if path.respond_to?(:[]) and path[/[^\\]\*$/]
|
10
|
+
@match_partially = true
|
11
|
+
@path_for_generation = path[0..path.size - 2]
|
12
|
+
else
|
13
|
+
@path_for_generation = path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def name(name = nil)
|
18
|
+
if name
|
19
|
+
self.name = name
|
20
|
+
self
|
21
|
+
else
|
22
|
+
@name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_default_values(hash)
|
27
|
+
@default_values ||= {}
|
28
|
+
@default_values.merge!(hash)
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_match_with(matchers)
|
32
|
+
@match_with ||= {}
|
33
|
+
@match_with.merge!(matchers)
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_other_host(hosts)
|
37
|
+
(@other_hosts ||= []).concat(hosts)
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_path(path)
|
41
|
+
(@paths ||= []) << path
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_request_method(methods)
|
45
|
+
@request_methods ||= Set.new
|
46
|
+
methods = [methods] unless methods.is_a?(Array)
|
47
|
+
methods.each do |method|
|
48
|
+
method = method.to_s.upcase
|
49
|
+
raise unless Route::VALID_HTTP_VERBS.include?(method)
|
50
|
+
@request_methods << method
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def process_opts(opts)
|
55
|
+
if opts[:conditions]
|
56
|
+
opts.merge!(opts[:conditions])
|
57
|
+
opts.delete(:conditions)
|
58
|
+
end
|
59
|
+
opts.each do |k, v|
|
60
|
+
if respond_to?(:"#{k}=")
|
61
|
+
send(:"#{k}=", v)
|
62
|
+
elsif respond_to?(:"add_#{k}")
|
63
|
+
send(:"add_#{k}", v)
|
64
|
+
else
|
65
|
+
add_match_with(k => v)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def to(dest = nil, &dest_block)
|
71
|
+
@dest = dest || dest_block || raise("you didn't specify a destination")
|
72
|
+
if @dest.respond_to?(:url_mount=)
|
73
|
+
urlmount = UrlMount.new(@path_for_generation, @default_values || {}) # TODO url mount should accept nil here.
|
74
|
+
urlmount.url_mount = @router.url_mount if @router.url_mount
|
75
|
+
@dest.url_mount = urlmount
|
76
|
+
end
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def head
|
81
|
+
add_request_method "HEAD"
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def get
|
86
|
+
add_request_method "GET"
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def post
|
91
|
+
add_request_method "POST"
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def put
|
96
|
+
add_request_method "PUT"
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
def delete
|
101
|
+
add_request_method "DELETE"
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def redirect(path, status = 302)
|
106
|
+
raise ArgumentError, "Status has to be an integer between 300 and 399" unless (300..399).include?(status)
|
107
|
+
to { |env|
|
108
|
+
params = env['router.params']
|
109
|
+
response = ::Rack::Response.new
|
110
|
+
response.redirect(eval(%|"#{path}"|), status)
|
111
|
+
response.finish
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
# Sets the destination of this route to serve static files from either a directory or a single file.
|
116
|
+
def static(root)
|
117
|
+
@match_partially = true if File.directory?(root)
|
118
|
+
to File.directory?(root) ?
|
119
|
+
::Rack::File.new(root) :
|
120
|
+
proc {|env|
|
121
|
+
env['PATH_INFO'] = File.basename(root)
|
122
|
+
::Rack::File.new(File.dirname(root)).call(env)
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|