| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189 |
1×
1×
1×
1×
1×
1×
1×
1×
1×
2×
2×
1×
16×
16×
16×
16×
34×
34×
34×
13×
13×
13×
13×
3×
2×
8×
4×
4×
8×
8×
1×
7×
4×
8×
8×
7×
7×
7×
1×
1×
7×
8×
8×
8×
3×
8×
11×
11×
5×
6×
2×
2×
2×
1×
| /* jshint -W084 */
const d = require("describe-property");
const objectAssign = require("object-assign");
const compileRoute = require("../utils/compileRoute");
const isRegExp = require("../utils/isRegExp");
const makeParams = require("../utils/makeParams");
const RoutingProperties = require("../utils/RoutingProperties");
const LEADING_HTTP_METHOD_MATCHER = /^(DELETE|GET|HEAD|OPTIONS|POST|PUT|TRACE)\s+(.+)$/;
const {is, not, isEmpty, compose} = require("ramda"),
isnt = compose(not, is);
/**
* A middleware that provides pattern-based routing for URLs, with optional
* support for restricting matches to a specific request method. Named segments
* of the URL are added to conn.params and take precedence over all others.
*
* app.use(mach.router, {
*
* 'GET /login': function (conn) {
* // conn.method == 'GET'
* // conn.pathname == '/login'
* },
*
* 'POST /login': function (conn) {
* // conn.method == 'POST'
* // conn.pathname == '/login'
* },
*
* 'DELETE /users/:id': function (conn) {
* // conn.method == 'DELETE'
* // conn.pathname == '/users/5'
* // conn.params == { id: 5 }
* }
*
* });
*
* This function may also be used outside the context of a middleware stack
* to create a standalone app. Routes may be given one at a time:
*
* let app = mach.router();
*
* app.get('/login', function (conn) {
* // ...
* });
*
* app.delete('/users/:id', function (conn) {
* // ...
* });
*
* Or all at once:
*
* let app = mach.router({
*
* 'GET /login': function (conn) {
* // ...
* },
*
* 'DELETE /users/:id': function (conn) {
* // ...
* }
*
* });
*
* Note: Routes are always tried in the order they were defined.
*/
function createRouter(app, map) {
// Allow mach.router(map)
Iif (typeof app === "object") {
map = app;
app = null;
}
const routes = {};
function router(conn) {
const method = conn.method;
const routesToTry = (routes[method] || []).concat(routes.ANY || []);
let route, match;
for (let i = 0, len = routesToTry.length; i < len; ++i) {
route = routesToTry[i];
// Try to match the route.
match = route.pattern.exec(conn.pathname);
if (match) {
const params = makeParams(route.keys, Array.prototype.slice.call(match, 1));
Iif (conn.params) {
// Route params take precedence above all others.
objectAssign(conn.params, params);
} else {
conn.params = params;
}
return conn.call(route.app);
}
}
return conn.call(app);
}
Object.defineProperties(router, {
/**
* Adds a new route that runs the given app when the pattern matches the
* path used in the request. If the pattern is a string, it is automatically
* compiled. The following signatures are supported:
*
* route('/users/:id', app)
* route('/users/:id', 'PUT', app)
* route('/users/:id', [ 'GET', 'PUT' ], app)
* route('GET /users/:id', app)
*/
route: d(function (pattern, methods, app) {
if (is(Function, methods)) {
app = methods;
methods = null;
}
Iif (isnt(Function, app)) {
throw new Error("Route needs an app");
}
if (is(String, methods)) {
methods = [methods];
} else if (!Array.isArray(methods)) {
methods = [];
}
const keys = [];
if (is(String, pattern)) {
let match;
match = pattern.match(LEADING_HTTP_METHOD_MATCHER);
if (match) {
methods.push(match[1]);
pattern = match[2];
}
pattern = compileRoute(pattern, keys);
}
Iif (!isRegExp(pattern)) {
throw new Error("Route pattern must be a RegExp");
}
const route = {pattern, keys, app};
if (isEmpty(methods)) {
methods.push("ANY");
}
methods.forEach(function (method) {
const upperMethod = method.toUpperCase();
if (routes[upperMethod]) {
routes[upperMethod].push(route);
} else {
routes[upperMethod] = [route];
}
});
}),
/**
* Sets the given app as the default for this router.
*/
run: d(function (downstreamApp) {
app = downstreamApp;
})
});
// Allow app.use(mach.router, map)
Iif (is(Object, map)) {
for (const route in map) {
if (map.hasOwnProperty(route)) {
router.route(route, map[route]);
}
}
}
Object.defineProperties(router, RoutingProperties);
return router;
}
module.exports = createRouter;
|