Skip to content

Commit 2e7dc54

Browse files
committed
don't create Routers when handling the request
1 parent 28b992e commit 2e7dc54

File tree

3 files changed

+134
-49
lines changed

3 files changed

+134
-49
lines changed

Diff for: pkgs/shelf_router/lib/src/router.dart

+60-8
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,40 @@ extension RouterParams on Request {
5656
}
5757
return _emptyParams;
5858
}
59+
60+
/// Get URL parameters captured by the [Router.mount].
61+
/// They can be accessed from inside the mounted routes.
62+
///
63+
/// **Example**
64+
/// ```dart
65+
/// Router createUsersRouter() {
66+
/// var router = Router();
67+
///
68+
/// String getUser(Request r) => r.mountedParams['user']!;
69+
///
70+
/// router.get('/self', (Request request) {
71+
/// return Response.ok("I'm ${getUser(request)}");
72+
/// });
73+
///
74+
/// return router;
75+
/// }
76+
///
77+
/// var app = Router();
78+
///
79+
/// final usersRouter = createUsersRouter();
80+
/// app.mount('/users/<user>', (Request r, String user) => usersRouter(r));
81+
/// ```
82+
///
83+
/// If no parameters are captured this returns an empty map.
84+
///
85+
/// The returned map is unmodifiable.
86+
Map<String, String> get mountedParams {
87+
final p = context['shelf_router/mountedParams'];
88+
if (p is Map<String, String>) {
89+
return UnmodifiableMapView(p);
90+
}
91+
return _emptyParams;
92+
}
5993
}
6094

6195
/// Middleware to remove body from request.
@@ -148,11 +182,17 @@ class Router {
148182

149183
/// Handle all request to [route] using [handler].
150184
void all(String route, Function handler) {
151-
_all(route, handler, mounted: false);
185+
_all(route, handler, applyParamsOnHandle: true);
152186
}
153187

154-
void _all(String route, Function handler, {required bool mounted}) {
155-
_routes.add(RouterEntry('ALL', route, handler, mounted: mounted));
188+
void _all(String route, Function handler,
189+
{required bool applyParamsOnHandle}) {
190+
_routes.add(RouterEntry(
191+
'ALL',
192+
route,
193+
handler,
194+
applyParamsOnHandle: applyParamsOnHandle,
195+
));
156196
}
157197

158198
/// Mount a handler below a prefix.
@@ -161,7 +201,6 @@ class Router {
161201
throw ArgumentError.value(prefix, 'prefix', 'must start with a slash');
162202
}
163203

164-
// first slash is always in request.handlerPath
165204
const restPathParam = _kRestPathParam;
166205

167206
if (prefix.endsWith('/')) {
@@ -172,15 +211,15 @@ class Router {
172211
final paramsList = [...route.params]..removeLast();
173212
return _invokeMountedHandler(request, handler, paramsList);
174213
},
175-
mounted: true,
214+
applyParamsOnHandle: false,
176215
);
177216
} else {
178217
_all(
179218
prefix,
180219
(Request request, RouterEntry route) {
181220
return _invokeMountedHandler(request, handler, route.params);
182221
},
183-
mounted: true,
222+
applyParamsOnHandle: false,
184223
);
185224
_all(
186225
prefix + '/<$restPathParam|[^]*>',
@@ -189,7 +228,7 @@ class Router {
189228
final paramsList = [...route.params]..removeLast();
190229
return _invokeMountedHandler(request, handler, paramsList);
191230
},
192-
mounted: true,
231+
applyParamsOnHandle: false,
193232
);
194233
}
195234
}
@@ -199,8 +238,21 @@ class Router {
199238
final paramsMap = request.params;
200239
final effectivePath = _getEffectiveMountPath(request.url.path, paramsMap);
201240

241+
final modifiedRequest = request.change(
242+
path: effectivePath,
243+
context: {
244+
// Include the parameters captured here as mounted parameters.
245+
// We also include previous mounted params in case there is double
246+
// nesting of `mount`s
247+
'shelf_router/mountedParams': {
248+
...request.mountedParams,
249+
...paramsMap,
250+
},
251+
},
252+
);
253+
202254
return await Function.apply(handler, [
203-
request.change(path: effectivePath),
255+
modifiedRequest,
204256
...pathParams.map((param) => paramsMap[param]),
205257
]) as Response;
206258
}

Diff for: pkgs/shelf_router/lib/src/router_entry.dart

+19-10
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ class RouterEntry {
3434
final Function _handler;
3535
final Middleware _middleware;
3636

37-
/// This router entry is used
38-
/// as a mount point
39-
final bool _mounted;
37+
/// If the arguments should be applied or not to the handler function.
38+
/// This is useful to have as false when there is
39+
/// internal logic that registers routes and the number of expected arguments
40+
/// by the user is unknown. i.e: [Router.mount]
41+
/// When this is false, this [RouterEntry] is provided as an argument along
42+
/// the [Request] so that the caller can read information from the route.
43+
final bool _applyParamsOnHandle;
4044

4145
/// Expression that the request path must match.
4246
///
@@ -50,14 +54,14 @@ class RouterEntry {
5054
List<String> get params => _params.toList(); // exposed for using generator.
5155

5256
RouterEntry._(this.verb, this.route, this._handler, this._middleware,
53-
this._routePattern, this._params, this._mounted);
57+
this._routePattern, this._params, this._applyParamsOnHandle);
5458

5559
factory RouterEntry(
5660
String verb,
5761
String route,
5862
Function handler, {
5963
Middleware? middleware,
60-
bool mounted = false,
64+
bool applyParamsOnHandle = true,
6165
}) {
6266
middleware = middleware ?? ((Handler fn) => fn);
6367

@@ -82,7 +86,14 @@ class RouterEntry {
8286
final routePattern = RegExp('^$pattern\$');
8387

8488
return RouterEntry._(
85-
verb, route, handler, middleware, routePattern, params, mounted);
89+
verb,
90+
route,
91+
handler,
92+
middleware,
93+
routePattern,
94+
params,
95+
applyParamsOnHandle,
96+
);
8697
}
8798

8899
/// Returns a map from parameter name to value, if the path matches the
@@ -107,10 +118,8 @@ class RouterEntry {
107118
request = request.change(context: {'shelf_router/params': params});
108119

109120
return await _middleware((request) async {
110-
if (_mounted) {
111-
// if this route is mounted, we include
112-
// the route itself as a parameter so
113-
// that the mount can extract the parameters
121+
if (!_applyParamsOnHandle) {
122+
// We handle the request just providing this route
114123
return await _handler(request, this) as Response;
115124
}
116125

Diff for: pkgs/shelf_router/test/router_test.dart

+55-31
Original file line numberDiff line numberDiff line change
@@ -204,34 +204,40 @@ void main() {
204204
});
205205

206206
test('can mount dynamic routes', () async {
207-
// routes for an [user] to [other]. This gets nested
208-
// parameters from previous mounts
209-
Handler createUserToOtherHandler(String user, String other) {
207+
// routes for a specific [user]. The user value
208+
// is extracted from the mount
209+
Router createUsersRouter() {
210210
var router = Router();
211211

212-
router.get('/<action>', (Request request, String action) {
213-
return Response.ok('$user to $other: $action');
214-
});
212+
String getUser(Request r) => r.mountedParams['user']!;
215213

216-
return router;
217-
}
214+
// Nested mount
215+
// Routes for an [user] to [other]. This gets nested
216+
// parameters from previous mounts
217+
Router createUserToOtherRouter() {
218+
var router = Router();
218219

219-
// routes for a specific [user]. The user value
220-
// is extracted from the mount
221-
Handler createUserHandler(String user) {
222-
var router = Router();
220+
String getOtherUser(Request r) => r.mountedParams['other']!;
223221

224-
router.mount('/to/<other>/', (Request request, String other) {
225-
final handler = createUserToOtherHandler(user, other);
226-
return handler(request);
227-
});
222+
router.get('/<action>', (Request request, String action) {
223+
return Response.ok(
224+
'${getUser(request)} to ${getOtherUser(request)}: $action',
225+
);
226+
});
227+
228+
return router;
229+
}
230+
231+
final userToOtherRouter = createUserToOtherRouter();
232+
router.mount(
233+
'/to/<other>/', (Request r, String other) => userToOtherRouter(r));
228234

229235
router.get('/self', (Request request) {
230-
return Response.ok("I'm $user");
236+
return Response.ok("I'm ${getUser(request)}");
231237
});
232238

233239
router.get('/', (Request request) {
234-
return Response.ok('$user root');
240+
return Response.ok('${getUser(request)} root');
235241
});
236242
return router;
237243
}
@@ -241,10 +247,8 @@ void main() {
241247
return Response.ok('hello-world');
242248
});
243249

244-
app.mount('/users/<user>', (Request request, String user) {
245-
final handler = createUserHandler(user);
246-
return handler(request);
247-
});
250+
final usersRouter = createUsersRouter();
251+
app.mount('/users/<user>', (Request r, String user) => usersRouter(r));
248252

249253
app.all('/<_|[^]*>', (Request request) {
250254
return Response.ok('catch-all-handler');
@@ -262,12 +266,24 @@ void main() {
262266

263267
test('can mount dynamic routes with multiple parameters', () async {
264268
var app = Router();
265-
app.mount(r'/first/<second>/third/<fourth|\d+>/last',
266-
(Request request, String second, String fourthNum) {
269+
270+
final mountedRouter = () {
267271
var router = Router();
268-
router.get('/', (r) => Response.ok('$second ${int.parse(fourthNum)}'));
269-
return router(request);
270-
});
272+
273+
String getSecond(Request r) => r.mountedParams['second']!;
274+
int getFourth(Request r) => int.parse(r.mountedParams['fourth']!);
275+
276+
router.get(
277+
'/',
278+
(Request r) => Response.ok('${getSecond(r)} ${getFourth(r)}'),
279+
);
280+
return router;
281+
}();
282+
283+
app.mount(
284+
r'/first/<second>/third/<fourth|\d+>/last',
285+
(Request r, String second, String fourth) => mountedRouter(r),
286+
);
271287

272288
server.mount(app);
273289

@@ -277,11 +293,19 @@ void main() {
277293
test('can mount dynamic routes with regexp', () async {
278294
var app = Router();
279295

280-
app.mount(r'/before/<bookId|\d+>/after', (Request request, String bookId) {
296+
final mountedRouter = () {
281297
var router = Router();
282-
router.get('/', (r) => Response.ok('book ${int.parse(bookId)}'));
283-
return router(request);
284-
});
298+
299+
int getBookId(Request r) => int.parse(r.mountedParams['bookId']!);
300+
301+
router.get('/', (Request r) => Response.ok('book ${getBookId(r)}'));
302+
return router;
303+
}();
304+
305+
app.mount(
306+
r'/before/<bookId|\d+>/after',
307+
(Request r, String bookId) => mountedRouter(r),
308+
);
285309

286310
app.all('/<_|[^]*>', (Request request) {
287311
return Response.ok('catch-all-handler');

0 commit comments

Comments
 (0)