http_router 0.8.11 → 0.9.3
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 +32 -3
- data/js/lib/http_router.coffee +368 -0
- data/js/lib/http_router.js +668 -0
- data/js/package.json +10 -0
- data/js/test/test.coffee +136 -0
- data/js/test/test.js +229 -0
- data/lib/http_router/node/arbitrary.rb +1 -1
- data/lib/http_router/node/free_regex.rb +3 -2
- data/lib/http_router/node/glob.rb +4 -3
- data/lib/http_router/node/glob_regex.rb +3 -2
- data/lib/http_router/node/lookup.rb +2 -3
- data/lib/http_router/node/path.rb +62 -0
- data/lib/http_router/node/request.rb +13 -2
- data/lib/http_router/node/root.rb +29 -2
- data/lib/http_router/node/spanning_regex.rb +6 -5
- data/lib/http_router/node.rb +7 -12
- data/lib/http_router/rack/builder.rb +0 -8
- data/lib/http_router/regex_route.rb +13 -0
- data/lib/http_router/route.rb +57 -42
- data/lib/http_router/util.rb +41 -0
- data/lib/http_router/version.rb +1 -1
- data/lib/http_router.rb +17 -22
- data/test/common/generate.txt +8 -2
- data/test/common/http_recognize.txt +58 -0
- data/test/common/recognize.txt +12 -65
- data/test/generation.rb +5 -102
- data/test/generic.rb +111 -0
- data/test/recognition.rb +6 -100
- data/test/test_misc.rb +26 -2
- data/test/test_mounting.rb +4 -4
- data/test/test_recognition.rb +18 -0
- metadata +94 -34
- data/lib/http_router/node/destination.rb +0 -45
- data/lib/http_router/path.rb +0 -58
data/js/test/test.coffee
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require.paths.push("#{__dirname}/../lib")
|
2
|
+
fs = require('fs')
|
3
|
+
util = require('util')
|
4
|
+
sys = require('sys')
|
5
|
+
http_router = require('http_router')
|
6
|
+
assert = require('assert')
|
7
|
+
|
8
|
+
class Example
|
9
|
+
constructor: (@routes, @tests) ->
|
10
|
+
|
11
|
+
class Test
|
12
|
+
constructor: (file)->
|
13
|
+
@examples = []
|
14
|
+
contents = fs.readFileSync(file, 'utf8')
|
15
|
+
lines = contents.split(/\n/m)
|
16
|
+
currentTest = null
|
17
|
+
routes = []
|
18
|
+
tests = []
|
19
|
+
for line in lines
|
20
|
+
if line.match(/^#/)
|
21
|
+
# this is a comment, skip
|
22
|
+
else if line.match(/^\s*$/)
|
23
|
+
# empty line, skip
|
24
|
+
else if line.match(/^( |\t)/)
|
25
|
+
# this is a test
|
26
|
+
tests.push(JSON.parse(line))
|
27
|
+
else
|
28
|
+
# this is a route
|
29
|
+
if tests.length != 0
|
30
|
+
@examples.push new Example(routes, tests)
|
31
|
+
routes = []
|
32
|
+
tests = []
|
33
|
+
parsedRoutes = JSON.parse(line)
|
34
|
+
if parsedRoutes instanceof Array
|
35
|
+
for r in parsedRoutes
|
36
|
+
routes.push(r)
|
37
|
+
else
|
38
|
+
routes.push(parsedRoutes)
|
39
|
+
@examples.push new Example(routes, tests)
|
40
|
+
interpretValue: (v) ->
|
41
|
+
if v.regex? then new RegExp(v.regex) else v
|
42
|
+
invoke: -> throw("need to implement")
|
43
|
+
constructRouter: (example) ->
|
44
|
+
router = new Sherpa()
|
45
|
+
for route in example.routes
|
46
|
+
for name, vals of route
|
47
|
+
path = null
|
48
|
+
opts = {name: name}
|
49
|
+
if vals.path?
|
50
|
+
path = @interpretValue(vals.path)
|
51
|
+
delete vals.path
|
52
|
+
if vals.conditions?
|
53
|
+
conditions = {}
|
54
|
+
for k, v of vals.conditions
|
55
|
+
switch k
|
56
|
+
when 'request_method' then conditions.method = @interpretValue(v)
|
57
|
+
else conditions[k] = @interpretValue(v)
|
58
|
+
opts.conditions = conditions
|
59
|
+
delete vals.conditions
|
60
|
+
if vals.default?
|
61
|
+
opts.default = vals.default
|
62
|
+
delete vals.default
|
63
|
+
matchesWith = {}
|
64
|
+
for k, v of vals
|
65
|
+
matchesWith[k] = @interpretValue(v)
|
66
|
+
delete vals.k
|
67
|
+
opts.matchesWith = matchesWith
|
68
|
+
else
|
69
|
+
path = @interpretValue(vals)
|
70
|
+
name = "" + name
|
71
|
+
router.add(path, opts).to (req, response) ->
|
72
|
+
response.params = req.params
|
73
|
+
response.end(req.route.name)
|
74
|
+
router
|
75
|
+
|
76
|
+
class GenerationTest extends Test
|
77
|
+
constructor: -> super
|
78
|
+
invoke: ->
|
79
|
+
console.log("Running #{@examples.length} generation tests")
|
80
|
+
for example in @examples
|
81
|
+
process.stdout.write "*"
|
82
|
+
router = @constructRouter(example)
|
83
|
+
for test in example.tests
|
84
|
+
process.stdout.write "."
|
85
|
+
[expectedResult, name, params] = test
|
86
|
+
continue if params? && params instanceof Array
|
87
|
+
continue if name instanceof Object
|
88
|
+
actualResult = router.url(name, params)
|
89
|
+
assert.equal(expectedResult, actualResult)
|
90
|
+
console.log("\nDone!")
|
91
|
+
|
92
|
+
class RecognitionTest extends Test
|
93
|
+
constructor: -> super
|
94
|
+
invoke: ->
|
95
|
+
console.log("Running #{@examples.length} recognition tests")
|
96
|
+
for example in @examples
|
97
|
+
process.stdout.write "*"
|
98
|
+
router = @constructRouter(example)
|
99
|
+
for test in example.tests
|
100
|
+
mockResponse = end: (part) -> @val = part
|
101
|
+
process.stdout.write "."
|
102
|
+
[expectedRouteName, requestingPath, expectedParams] = test
|
103
|
+
mockRequest = {}
|
104
|
+
complex = false
|
105
|
+
if requestingPath.path?
|
106
|
+
complex = true
|
107
|
+
mockRequest.url = requestingPath.path
|
108
|
+
delete requestingPath.path
|
109
|
+
for k, v of requestingPath
|
110
|
+
mockRequest[k] = v
|
111
|
+
else
|
112
|
+
mockRequest.url = requestingPath
|
113
|
+
mockRequest.url = "http://host#{mockRequest.url}" unless mockRequest.url.match(/^http/)
|
114
|
+
router.match(mockRequest, mockResponse)
|
115
|
+
assert.equal(expectedRouteName, mockResponse.val)
|
116
|
+
expectedParams ||= {}
|
117
|
+
mockResponse.params ||= {}
|
118
|
+
pathInfoExcpectation = null
|
119
|
+
if expectedParams.PATH_INFO?
|
120
|
+
pathInfoExcpectation = expectedParams.PATH_INFO
|
121
|
+
delete expectedParams.PATH_INFO
|
122
|
+
assert.equal(pathInfoExcpectation, mockRequest.pathInfo) if pathInfoExcpectation
|
123
|
+
assert.deepEqual(expectedParams, mockResponse.params)
|
124
|
+
unless complex
|
125
|
+
mockResponse = end: (part) -> @val = part
|
126
|
+
router.match(requestingPath, mockResponse)
|
127
|
+
assert.equal(expectedRouteName, mockResponse.val)
|
128
|
+
expectedParams ||= {}
|
129
|
+
mockResponse.params ||= {}
|
130
|
+
assert.equal(pathInfoExcpectation, mockRequest.pathInfo) if pathInfoExcpectation
|
131
|
+
assert.deepEqual(expectedParams, mockResponse.params)
|
132
|
+
console.log("\nDone!")
|
133
|
+
|
134
|
+
new GenerationTest("#{__dirname}/../../test/common/generate.txt").invoke()
|
135
|
+
new RecognitionTest("#{__dirname}/../../test/common/recognize.txt").invoke()
|
136
|
+
|
data/js/test/test.js
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
(function() {
|
2
|
+
var Example, GenerationTest, RecognitionTest, Test, assert, fs, http_router, sys, util;
|
3
|
+
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
|
4
|
+
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
|
5
|
+
function ctor() { this.constructor = child; }
|
6
|
+
ctor.prototype = parent.prototype;
|
7
|
+
child.prototype = new ctor;
|
8
|
+
child.__super__ = parent.prototype;
|
9
|
+
return child;
|
10
|
+
};
|
11
|
+
require.paths.push("" + __dirname + "/../lib");
|
12
|
+
fs = require('fs');
|
13
|
+
util = require('util');
|
14
|
+
sys = require('sys');
|
15
|
+
http_router = require('http_router');
|
16
|
+
assert = require('assert');
|
17
|
+
Example = (function() {
|
18
|
+
function Example(routes, tests) {
|
19
|
+
this.routes = routes;
|
20
|
+
this.tests = tests;
|
21
|
+
}
|
22
|
+
return Example;
|
23
|
+
})();
|
24
|
+
Test = (function() {
|
25
|
+
function Test(file) {
|
26
|
+
var contents, currentTest, line, lines, parsedRoutes, r, routes, tests, _i, _j, _len, _len2;
|
27
|
+
this.examples = [];
|
28
|
+
contents = fs.readFileSync(file, 'utf8');
|
29
|
+
lines = contents.split(/\n/m);
|
30
|
+
currentTest = null;
|
31
|
+
routes = [];
|
32
|
+
tests = [];
|
33
|
+
for (_i = 0, _len = lines.length; _i < _len; _i++) {
|
34
|
+
line = lines[_i];
|
35
|
+
if (line.match(/^#/)) {} else if (line.match(/^\s*$/)) {} else if (line.match(/^( |\t)/)) {
|
36
|
+
tests.push(JSON.parse(line));
|
37
|
+
} else {
|
38
|
+
if (tests.length !== 0) {
|
39
|
+
this.examples.push(new Example(routes, tests));
|
40
|
+
routes = [];
|
41
|
+
tests = [];
|
42
|
+
}
|
43
|
+
parsedRoutes = JSON.parse(line);
|
44
|
+
if (parsedRoutes instanceof Array) {
|
45
|
+
for (_j = 0, _len2 = parsedRoutes.length; _j < _len2; _j++) {
|
46
|
+
r = parsedRoutes[_j];
|
47
|
+
routes.push(r);
|
48
|
+
}
|
49
|
+
} else {
|
50
|
+
routes.push(parsedRoutes);
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
this.examples.push(new Example(routes, tests));
|
55
|
+
}
|
56
|
+
Test.prototype.interpretValue = function(v) {
|
57
|
+
if (v.regex != null) {
|
58
|
+
return new RegExp(v.regex);
|
59
|
+
} else {
|
60
|
+
return v;
|
61
|
+
}
|
62
|
+
};
|
63
|
+
Test.prototype.invoke = function() {
|
64
|
+
throw "need to implement";
|
65
|
+
};
|
66
|
+
Test.prototype.constructRouter = function(example) {
|
67
|
+
var conditions, k, matchesWith, name, opts, path, route, router, v, vals, _i, _len, _ref, _ref2;
|
68
|
+
router = new Sherpa();
|
69
|
+
_ref = example.routes;
|
70
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
71
|
+
route = _ref[_i];
|
72
|
+
for (name in route) {
|
73
|
+
vals = route[name];
|
74
|
+
path = null;
|
75
|
+
opts = {
|
76
|
+
name: name
|
77
|
+
};
|
78
|
+
if (vals.path != null) {
|
79
|
+
path = this.interpretValue(vals.path);
|
80
|
+
delete vals.path;
|
81
|
+
if (vals.conditions != null) {
|
82
|
+
conditions = {};
|
83
|
+
_ref2 = vals.conditions;
|
84
|
+
for (k in _ref2) {
|
85
|
+
v = _ref2[k];
|
86
|
+
switch (k) {
|
87
|
+
case 'request_method':
|
88
|
+
conditions.method = this.interpretValue(v);
|
89
|
+
break;
|
90
|
+
default:
|
91
|
+
conditions[k] = this.interpretValue(v);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
opts.conditions = conditions;
|
95
|
+
delete vals.conditions;
|
96
|
+
}
|
97
|
+
if (vals["default"] != null) {
|
98
|
+
opts["default"] = vals["default"];
|
99
|
+
delete vals["default"];
|
100
|
+
}
|
101
|
+
matchesWith = {};
|
102
|
+
for (k in vals) {
|
103
|
+
v = vals[k];
|
104
|
+
matchesWith[k] = this.interpretValue(v);
|
105
|
+
delete vals.k;
|
106
|
+
}
|
107
|
+
opts.matchesWith = matchesWith;
|
108
|
+
} else {
|
109
|
+
path = this.interpretValue(vals);
|
110
|
+
}
|
111
|
+
name = "" + name;
|
112
|
+
router.add(path, opts).to(function(req, response) {
|
113
|
+
response.params = req.params;
|
114
|
+
return response.end(req.route.name);
|
115
|
+
});
|
116
|
+
}
|
117
|
+
}
|
118
|
+
return router;
|
119
|
+
};
|
120
|
+
return Test;
|
121
|
+
})();
|
122
|
+
GenerationTest = (function() {
|
123
|
+
__extends(GenerationTest, Test);
|
124
|
+
function GenerationTest() {
|
125
|
+
GenerationTest.__super__.constructor.apply(this, arguments);
|
126
|
+
}
|
127
|
+
GenerationTest.prototype.invoke = function() {
|
128
|
+
var actualResult, example, expectedResult, name, params, router, test, _i, _j, _len, _len2, _ref, _ref2;
|
129
|
+
console.log("Running " + this.examples.length + " generation tests");
|
130
|
+
_ref = this.examples;
|
131
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
132
|
+
example = _ref[_i];
|
133
|
+
process.stdout.write("*");
|
134
|
+
router = this.constructRouter(example);
|
135
|
+
_ref2 = example.tests;
|
136
|
+
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
|
137
|
+
test = _ref2[_j];
|
138
|
+
process.stdout.write(".");
|
139
|
+
expectedResult = test[0], name = test[1], params = test[2];
|
140
|
+
if ((params != null) && params instanceof Array) {
|
141
|
+
continue;
|
142
|
+
}
|
143
|
+
if (name instanceof Object) {
|
144
|
+
continue;
|
145
|
+
}
|
146
|
+
actualResult = router.url(name, params);
|
147
|
+
assert.equal(expectedResult, actualResult);
|
148
|
+
}
|
149
|
+
}
|
150
|
+
return console.log("\nDone!");
|
151
|
+
};
|
152
|
+
return GenerationTest;
|
153
|
+
})();
|
154
|
+
RecognitionTest = (function() {
|
155
|
+
__extends(RecognitionTest, Test);
|
156
|
+
function RecognitionTest() {
|
157
|
+
RecognitionTest.__super__.constructor.apply(this, arguments);
|
158
|
+
}
|
159
|
+
RecognitionTest.prototype.invoke = function() {
|
160
|
+
var complex, example, expectedParams, expectedRouteName, k, mockRequest, mockResponse, pathInfoExcpectation, requestingPath, router, test, v, _i, _j, _len, _len2, _ref, _ref2;
|
161
|
+
console.log("Running " + this.examples.length + " recognition tests");
|
162
|
+
_ref = this.examples;
|
163
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
164
|
+
example = _ref[_i];
|
165
|
+
process.stdout.write("*");
|
166
|
+
router = this.constructRouter(example);
|
167
|
+
_ref2 = example.tests;
|
168
|
+
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
|
169
|
+
test = _ref2[_j];
|
170
|
+
mockResponse = {
|
171
|
+
end: function(part) {
|
172
|
+
return this.val = part;
|
173
|
+
}
|
174
|
+
};
|
175
|
+
process.stdout.write(".");
|
176
|
+
expectedRouteName = test[0], requestingPath = test[1], expectedParams = test[2];
|
177
|
+
mockRequest = {};
|
178
|
+
complex = false;
|
179
|
+
if (requestingPath.path != null) {
|
180
|
+
complex = true;
|
181
|
+
mockRequest.url = requestingPath.path;
|
182
|
+
delete requestingPath.path;
|
183
|
+
for (k in requestingPath) {
|
184
|
+
v = requestingPath[k];
|
185
|
+
mockRequest[k] = v;
|
186
|
+
}
|
187
|
+
} else {
|
188
|
+
mockRequest.url = requestingPath;
|
189
|
+
}
|
190
|
+
if (!mockRequest.url.match(/^http/)) {
|
191
|
+
mockRequest.url = "http://host" + mockRequest.url;
|
192
|
+
}
|
193
|
+
router.match(mockRequest, mockResponse);
|
194
|
+
assert.equal(expectedRouteName, mockResponse.val);
|
195
|
+
expectedParams || (expectedParams = {});
|
196
|
+
mockResponse.params || (mockResponse.params = {});
|
197
|
+
pathInfoExcpectation = null;
|
198
|
+
if (expectedParams.PATH_INFO != null) {
|
199
|
+
pathInfoExcpectation = expectedParams.PATH_INFO;
|
200
|
+
delete expectedParams.PATH_INFO;
|
201
|
+
}
|
202
|
+
if (pathInfoExcpectation) {
|
203
|
+
assert.equal(pathInfoExcpectation, mockRequest.pathInfo);
|
204
|
+
}
|
205
|
+
assert.deepEqual(expectedParams, mockResponse.params);
|
206
|
+
if (!complex) {
|
207
|
+
mockResponse = {
|
208
|
+
end: function(part) {
|
209
|
+
return this.val = part;
|
210
|
+
}
|
211
|
+
};
|
212
|
+
router.match(requestingPath, mockResponse);
|
213
|
+
assert.equal(expectedRouteName, mockResponse.val);
|
214
|
+
expectedParams || (expectedParams = {});
|
215
|
+
mockResponse.params || (mockResponse.params = {});
|
216
|
+
if (pathInfoExcpectation) {
|
217
|
+
assert.equal(pathInfoExcpectation, mockRequest.pathInfo);
|
218
|
+
}
|
219
|
+
assert.deepEqual(expectedParams, mockResponse.params);
|
220
|
+
}
|
221
|
+
}
|
222
|
+
}
|
223
|
+
return console.log("\nDone!");
|
224
|
+
};
|
225
|
+
return RecognitionTest;
|
226
|
+
})();
|
227
|
+
new GenerationTest("" + __dirname + "/../../test/common/generate.txt").invoke();
|
228
|
+
new RecognitionTest("" + __dirname + "/../../test/common/recognize.txt").invoke();
|
229
|
+
}).call(this);
|
@@ -13,7 +13,7 @@ class HttpRouter
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def to_code
|
16
|
-
b, method_name = @blk, :"blk_#{
|
16
|
+
b, method_name = @blk, :"blk_#{root.next_counter}"
|
17
17
|
inject_root_methods { define_method(method_name) { b } }
|
18
18
|
"#{"if request.path_finished?" unless @allow_partial}
|
19
19
|
request.continue = proc { |state|
|
@@ -8,8 +8,9 @@ class HttpRouter
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def to_code
|
11
|
-
|
12
|
-
|
11
|
+
id = root.next_counter
|
12
|
+
"whole_path#{id} = \"/\#{request.joined_path}\"
|
13
|
+
if match = #{matcher.inspect}.match(whole_path#{id}) and match[0].size == whole_path#{id}.size
|
13
14
|
request.extra_env['router.regex_match'] = match
|
14
15
|
old_path = request.path
|
15
16
|
request.path = ['']
|
@@ -7,12 +7,13 @@ class HttpRouter
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def to_code
|
10
|
-
|
10
|
+
id = root.next_counter
|
11
|
+
"request.params << (globbed_params#{id} = [])
|
11
12
|
until request.path.empty?
|
12
|
-
globbed_params#{
|
13
|
+
globbed_params#{id} << request.path.shift
|
13
14
|
#{super}
|
14
15
|
end
|
15
|
-
request.path[0,0] = globbed_params#{
|
16
|
+
request.path[0,0] = globbed_params#{id}"
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
@@ -12,10 +12,11 @@ class HttpRouter
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def to_code
|
15
|
-
|
15
|
+
id = root.next_counter
|
16
|
+
"request.params << (globbed_params#{id} = [])
|
16
17
|
remaining_parts = request.path.dup
|
17
18
|
while !remaining_parts.empty? and match = remaining_parts.first.match(#{@matcher.inspect}) and match[0] == remaining_parts.first
|
18
|
-
globbed_params#{
|
19
|
+
globbed_params#{id} << remaining_parts.shift
|
19
20
|
request.path = remaining_parts
|
20
21
|
#{node_to_code}
|
21
22
|
end
|
@@ -15,9 +15,8 @@ class HttpRouter
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def to_code
|
18
|
-
lookup_ivar =
|
19
|
-
|
20
|
-
method_prefix = "lookup_#{router.next_counter} "
|
18
|
+
lookup_ivar = inject_root_ivar(@map)
|
19
|
+
method_prefix = "lookup_#{root.next_counter} "
|
21
20
|
inject_root_methods @map.keys.map {|k|
|
22
21
|
method = :"#{method_prefix}#{k}"
|
23
22
|
"define_method(#{method.inspect}) do |request|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class HttpRouter
|
2
|
+
class Node
|
3
|
+
class Path < Node
|
4
|
+
attr_reader :route, :param_names, :dynamic, :original_path
|
5
|
+
alias_method :dynamic?, :dynamic
|
6
|
+
def initialize(router, parent, route, path, param_names = [])
|
7
|
+
@route, @original_path, @param_names, @dynamic = route, path, param_names, !param_names.empty?
|
8
|
+
raise AmbiguousVariableException, "You have duplicate variable name present: #{param_names.join(', ')}" if param_names.uniq.size != param_names.size
|
9
|
+
Util.add_path_generation(self, route, @original_path) if @original_path.respond_to?(:split)
|
10
|
+
super router, parent
|
11
|
+
root.uncompile
|
12
|
+
end
|
13
|
+
|
14
|
+
def hashify_params(params)
|
15
|
+
@dynamic && params ? Hash[param_names.zip(params)] : {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def url(args, options)
|
19
|
+
if path = raw_url(args, options)
|
20
|
+
raise TooManyParametersException unless args.empty?
|
21
|
+
[URI.escape(path), options]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_code
|
26
|
+
path_ivar = inject_root_ivar(self)
|
27
|
+
"#{"if request.path_finished?" unless route.match_partially?}
|
28
|
+
catch(:pass) do
|
29
|
+
#{"if request.path.size == 1 && request.path.first == '' && (request.rack_request.head? || request.rack_request.get?) && request.rack_request.path_info[-1] == ?/
|
30
|
+
response = ::Rack::Response.new
|
31
|
+
response.redirect(request.rack_request.path_info[0, request.rack_request.path_info.size - 1], 302)
|
32
|
+
throw :success, response.finish
|
33
|
+
end" if router.redirect_trailing_slash?}
|
34
|
+
|
35
|
+
#{"if request.path.empty?#{" or (request.path.size == 1 and request.path.first == '')" if router.ignore_trailing_slash?}" unless route.match_partially?}
|
36
|
+
if request.perform_call
|
37
|
+
env = request.rack_request.dup.env
|
38
|
+
env['router.request'] = request
|
39
|
+
env['router.params'] ||= {}
|
40
|
+
#{"env['router.params'].merge!(Hash[#{param_names.inspect}.zip(request.params)])" if dynamic?}
|
41
|
+
rewrite#{"_partial" if route.match_partially?}_path_info(env, request)
|
42
|
+
response = @router.process_destination_path(#{path_ivar}, env)
|
43
|
+
router.pass_on_response(response) ? throw(:pass) : throw(:success, response)
|
44
|
+
else
|
45
|
+
throw :success, Response.new(request, #{path_ivar})
|
46
|
+
end
|
47
|
+
#{"end" unless route.match_partially?}
|
48
|
+
end
|
49
|
+
#{"end" unless route.match_partially?}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def usable?(other)
|
53
|
+
other == self
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def raw_url(args, options)
|
58
|
+
raise InvalidRouteException
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,11 +1,23 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Node
|
3
3
|
class Request < Node
|
4
|
+
VALID_HTTP_VERBS = %w[HEAD GET DELETE POST PUT OPTIONS PATCH TRACE CONNECT]
|
5
|
+
|
4
6
|
attr_reader :request_method, :opts
|
5
7
|
|
6
8
|
def initialize(router, parent, opts)
|
9
|
+
opts.each do |k, v|
|
10
|
+
v = [v] unless v.is_a?(Array)
|
11
|
+
case k
|
12
|
+
when :request_method
|
13
|
+
v.map!{|val| val.to_s.upcase}
|
14
|
+
v.all?{|m| VALID_HTTP_VERBS.include?(m)} or raise InvalidRequestValueError, "Invalid value for request_method #{v.inspect}"
|
15
|
+
v.each{|val| router.known_methods << val}
|
16
|
+
end
|
17
|
+
opts[k] = v
|
18
|
+
end
|
7
19
|
@opts = opts
|
8
|
-
|
20
|
+
@opts[:request_method].each { |m| router.known_methods << m } if @opts.key?(:request_method)
|
9
21
|
super(router, parent)
|
10
22
|
end
|
11
23
|
|
@@ -16,7 +28,6 @@ class HttpRouter
|
|
16
28
|
def to_code
|
17
29
|
code = "if "
|
18
30
|
code << @opts.map do |k,v|
|
19
|
-
v = [v] unless v.is_a?(Array)
|
20
31
|
case v.size
|
21
32
|
when 1 then to_code_condition(k, v.first)
|
22
33
|
else "(#{v.map{|vv| to_code_condition(k, vv)}.join(' or ')})"
|
@@ -1,21 +1,48 @@
|
|
1
1
|
class HttpRouter
|
2
2
|
class Node
|
3
3
|
class Root < Node
|
4
|
-
attr_reader :methods_module
|
4
|
+
attr_reader :methods_module, :compiled
|
5
|
+
alias_method :compiled?, :compiled
|
5
6
|
def initialize(router)
|
6
7
|
super(router, nil)
|
7
|
-
@methods_module = Module.new
|
8
|
+
@counter, @methods_module = 0, Module.new
|
8
9
|
end
|
9
10
|
|
10
11
|
def [](request)
|
11
12
|
compile
|
12
13
|
self[request]
|
13
14
|
end
|
15
|
+
alias_method :compiling_lookup, :[]
|
16
|
+
|
17
|
+
def uncompile
|
18
|
+
instance_eval "undef :[]; alias :[] :compiling_lookup", __FILE__, __LINE__ if compiled?
|
19
|
+
end
|
20
|
+
|
21
|
+
def next_counter
|
22
|
+
@counter += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def inject_root_ivar(obj)
|
26
|
+
name = :"@ivar_#{@counter += 1}"
|
27
|
+
root.instance_variable_set(name, obj)
|
28
|
+
name
|
29
|
+
end
|
14
30
|
|
15
31
|
private
|
32
|
+
def rewrite_partial_path_info(env, request)
|
33
|
+
env['PATH_INFO'] = "/#{request.path.join('/')}"
|
34
|
+
env['SCRIPT_NAME'] += request.rack_request.path_info[0, request.rack_request.path_info.size - env['PATH_INFO'].size]
|
35
|
+
end
|
36
|
+
|
37
|
+
def rewrite_path_info(env, request)
|
38
|
+
env['SCRIPT_NAME'] += request.rack_request.path_info
|
39
|
+
env['PATH_INFO'] = ''
|
40
|
+
end
|
41
|
+
|
16
42
|
def compile
|
17
43
|
root.extend(root.methods_module)
|
18
44
|
instance_eval "def [](request)\n#{to_code}\nnil\nend", __FILE__, __LINE__
|
45
|
+
@compiled = true
|
19
46
|
end
|
20
47
|
end
|
21
48
|
end
|
@@ -3,14 +3,15 @@ class HttpRouter
|
|
3
3
|
class SpanningRegex < Regex
|
4
4
|
def to_code
|
5
5
|
params_count = @ordered_indicies.size
|
6
|
-
"whole_path#{
|
7
|
-
|
8
|
-
|
6
|
+
whole_path_var = "whole_path#{root.next_counter}"
|
7
|
+
"#{whole_path_var} = request.joined_path
|
8
|
+
if match = #{@matcher.inspect}.match(#{whole_path_var}) and match.begin(0).zero?
|
9
|
+
_#{whole_path_var} = request.path.dup
|
9
10
|
" << param_capturing_code << "
|
10
|
-
remaining_path =
|
11
|
+
remaining_path = #{whole_path_var}[match[0].size + (#{whole_path_var}[match[0].size] == ?/ ? 1 : 0), #{whole_path_var}.size]
|
11
12
|
request.path = remaining_path.split('/')
|
12
13
|
#{node_to_code}
|
13
|
-
request.path =
|
14
|
+
request.path = _#{whole_path_var}
|
14
15
|
request.params.slice!(#{-params_count.size}, #{params_count})
|
15
16
|
end
|
16
17
|
"
|
data/lib/http_router/node.rb
CHANGED
@@ -11,9 +11,9 @@ class HttpRouter
|
|
11
11
|
autoload :Arbitrary, 'http_router/node/arbitrary'
|
12
12
|
autoload :Request, 'http_router/node/request'
|
13
13
|
autoload :Lookup, 'http_router/node/lookup'
|
14
|
-
autoload :
|
14
|
+
autoload :Path, 'http_router/node/path'
|
15
15
|
|
16
|
-
attr_reader :
|
16
|
+
attr_reader :router
|
17
17
|
|
18
18
|
def initialize(router, parent, matchers = [])
|
19
19
|
@router, @parent, @matchers = router, parent, matchers
|
@@ -32,6 +32,7 @@ class HttpRouter
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def add_request(opts)
|
35
|
+
raise unless opts
|
35
36
|
add(Request.new(@router, self, opts))
|
36
37
|
end
|
37
38
|
|
@@ -51,8 +52,8 @@ class HttpRouter
|
|
51
52
|
add(FreeRegex.new(@router, self, regexp))
|
52
53
|
end
|
53
54
|
|
54
|
-
def add_destination(
|
55
|
-
add(
|
55
|
+
def add_destination(route, path, param_names = [])
|
56
|
+
add(Path.new(@router, self, route, path, param_names))
|
56
57
|
end
|
57
58
|
|
58
59
|
def add_lookup(part)
|
@@ -68,8 +69,8 @@ class HttpRouter
|
|
68
69
|
code ? root.methods_module.module_eval(code) : root.methods_module.module_eval(&blk)
|
69
70
|
end
|
70
71
|
|
71
|
-
def inject_root_ivar(
|
72
|
-
root.
|
72
|
+
def inject_root_ivar(obj)
|
73
|
+
root.inject_root_ivar(obj)
|
73
74
|
end
|
74
75
|
|
75
76
|
def add(matcher)
|
@@ -85,12 +86,6 @@ class HttpRouter
|
|
85
86
|
@router.root
|
86
87
|
end
|
87
88
|
|
88
|
-
def depth
|
89
|
-
d, p = 0, @parent
|
90
|
-
d, p = d + 1, p.parent until p.nil?
|
91
|
-
d
|
92
|
-
end
|
93
|
-
|
94
89
|
def use_named_captures?
|
95
90
|
//.respond_to?(:names)
|
96
91
|
end
|
@@ -51,14 +51,6 @@ module HttpRouter::Rack::BuilderMixin
|
|
51
51
|
map(path, options, :delete, &block)
|
52
52
|
end
|
53
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
54
|
def options(path, options = {}, &block)
|
63
55
|
map(path, options, :options, &block)
|
64
56
|
end
|