http_router 0.10.2 → 0.11.0
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/.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
|