Skip to content

Commit 7140281

Browse files
committed
mounted dynamic routes simplification
1 parent a8706ba commit 7140281

File tree

2 files changed

+42
-117
lines changed

2 files changed

+42
-117
lines changed

pkgs/shelf_router/lib/src/router.dart

Lines changed: 32 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import 'package:http_methods/http_methods.dart';
1919
import 'package:meta/meta.dart' show sealed;
2020
import 'package:shelf/shelf.dart';
2121

22-
import 'router_entry.dart' show ParamInfo, RouterEntry;
22+
import 'router_entry.dart' show RouterEntry;
2323

2424
/// Get a URL parameter captured by the [Router].
2525
@Deprecated('Use Request.params instead')
@@ -113,6 +113,12 @@ class Router {
113113
final List<RouterEntry> _routes = [];
114114
final Handler _notFoundHandler;
115115

116+
/// Name of the parameter used for matching the rest of te path in a mounted
117+
/// route.
118+
/// Prefixed with two underscores to avoid conflicts
119+
/// with user defined path parameters
120+
static const _kRestPathParam = '__path';
121+
116122
/// Creates a new [Router] routing requests to handlers.
117123
///
118124
/// The [notFoundHandler] will be invoked for requests where no matching route
@@ -156,100 +162,60 @@ class Router {
156162
}
157163

158164
// first slash is always in request.handlerPath
159-
final path = prefix.substring(1);
160-
161-
// Prefix it with two underscores to avoid conflicts
162-
// with user defined path parameters
163-
const pathParam = '__path';
165+
const restPathParam = _kRestPathParam;
164166

165167
if (prefix.endsWith('/')) {
166168
_all(
167-
prefix + '<$pathParam|[^]*>',
169+
prefix + '<$restPathParam|[^]*>',
168170
(Request request, RouterEntry route) {
169171
// Remove path param from extracted route params
170-
final paramsList = [...route.paramInfos]..removeLast();
171-
return _invokeMountedHandler(request, handler, path, paramsList);
172+
final paramsList = [...route.params]..removeLast();
173+
return _invokeMountedHandler(request, handler, paramsList);
172174
},
173175
mounted: true,
174176
);
175177
} else {
176178
_all(
177179
prefix,
178180
(Request request, RouterEntry route) {
179-
return _invokeMountedHandler(
180-
request, handler, path, route.paramInfos);
181+
return _invokeMountedHandler(request, handler, route.params);
181182
},
182183
mounted: true,
183184
);
184185
_all(
185-
prefix + '/<$pathParam|[^]*>',
186+
prefix + '/<$restPathParam|[^]*>',
186187
(Request request, RouterEntry route) {
187188
// Remove path param from extracted route params
188-
final paramsList = [...route.paramInfos]..removeLast();
189-
return _invokeMountedHandler(
190-
request, handler, path + '/', paramsList);
189+
final paramsList = [...route.params]..removeLast();
190+
return _invokeMountedHandler(request, handler, paramsList);
191191
},
192192
mounted: true,
193193
);
194194
}
195195
}
196196

197-
Future<Response> _invokeMountedHandler(Request request, Function handler,
198-
String path, List<ParamInfo> paramInfos) async {
199-
final params = _getParamsFromRequest(request);
200-
final resolvedPath =
201-
_replaceParamsInPath(request, path, params, paramInfos);
197+
Future<Response> _invokeMountedHandler(
198+
Request request, Function handler, List<String> pathParams) async {
199+
final paramsMap = request.params;
200+
201+
final pathParamSegment = paramsMap[_kRestPathParam];
202+
final urlPath = request.url.path;
203+
late final String effectivePath;
204+
if (pathParamSegment != null && pathParamSegment.isNotEmpty) {
205+
/// If we encounter the "rest path" parameter we remove it
206+
/// from the request path that shelf will handle.
207+
effectivePath =
208+
urlPath.substring(0, urlPath.length - pathParamSegment.length);
209+
} else {
210+
effectivePath = urlPath;
211+
}
202212

203213
return await Function.apply(handler, [
204-
request.change(path: resolvedPath),
205-
...paramInfos.map((info) => params[info.name]),
214+
request.change(path: effectivePath),
215+
...pathParams.map((param) => paramsMap[param]),
206216
]) as Response;
207217
}
208218

209-
Map<String, String> _getParamsFromRequest(Request request) {
210-
return request.context['shelf_router/params'] as Map<String, String>;
211-
}
212-
213-
/// Replaces the variable slots (<someVar>) from [path] with the
214-
/// values from [params]
215-
String _replaceParamsInPath(
216-
Request request,
217-
String path,
218-
Map<String, String> params,
219-
List<ParamInfo> paramInfos,
220-
) {
221-
// we iterate the non-resolved path and we write to a StringBuffer
222-
// resolving ther parameters along the way
223-
final resolvedPathBuff = StringBuffer();
224-
var paramIndex = 0;
225-
var charIndex = 0;
226-
while (charIndex < path.length) {
227-
if (paramIndex < paramInfos.length) {
228-
final paramInfo = paramInfos[paramIndex];
229-
if (charIndex < paramInfo.startIdx - 1) {
230-
// Add up until the param slot starts
231-
final part = path.substring(charIndex, paramInfo.startIdx - 1);
232-
resolvedPathBuff.write(part);
233-
charIndex += part.length;
234-
} else {
235-
// Add the resolved value of the parameter
236-
final paramName = paramInfo.name;
237-
final paramValue = params[paramName]!;
238-
resolvedPathBuff.write(paramValue);
239-
charIndex = paramInfo.endIdx - 1;
240-
paramIndex++;
241-
}
242-
} else {
243-
// All params looped, so add up until the end of the path
244-
final part = path.substring(charIndex, path.length);
245-
resolvedPathBuff.write(part);
246-
charIndex += part.length;
247-
}
248-
}
249-
var resolvedPath = resolvedPathBuff.toString();
250-
return resolvedPath;
251-
}
252-
253219
/// Route incoming requests to registered handlers.
254220
///
255221
/// This method allows a Router instance to be a [Handler].

pkgs/shelf_router/lib/src/router_entry.dart

Lines changed: 10 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,13 @@ class RouterEntry {
4444
final RegExp _routePattern;
4545

4646
/// Names for the parameters in the route pattern.
47-
final List<ParamInfo> _paramInfos;
48-
49-
List<ParamInfo> get paramInfos => _paramInfos.toList();
47+
final List<String> _params;
5048

5149
/// List of parameter names in the route pattern.
52-
// exposed for using generator.
53-
List<String> get params => _paramInfos.map((p) => p.name).toList();
50+
List<String> get params => _params.toList(); // exposed for using generator.
5451

5552
RouterEntry._(this.verb, this.route, this._handler, this._middleware,
56-
this._routePattern, this._paramInfos, this._mounted);
53+
this._routePattern, this._params, this._mounted);
5754

5855
factory RouterEntry(
5956
String verb,
@@ -69,26 +66,12 @@ class RouterEntry {
6966
route, 'route', 'expected route to start with a slash');
7067
}
7168

72-
final params = <ParamInfo>[];
69+
final params = <String>[];
7370
var pattern = '';
74-
// Keep the index where the matches are located
75-
// so that we can calculate the positioning of
76-
// the extracted parameter
77-
var prevMatchIndex = 0;
7871
for (var m in _parser.allMatches(route)) {
79-
final firstGroup = m[1]!;
80-
pattern += RegExp.escape(firstGroup);
72+
pattern += RegExp.escape(m[1]!);
8173
if (m[2] != null) {
82-
final paramName = m[2]!;
83-
final startIdx = prevMatchIndex + firstGroup.length;
84-
final paramInfo = ParamInfo(
85-
name: paramName,
86-
startIdx: startIdx,
87-
endIdx: m.end,
88-
);
89-
params.add(paramInfo);
90-
prevMatchIndex = m.end;
91-
74+
params.add(m[2]!);
9275
if (m[3] != null && !_isNoCapture(m[3]!)) {
9376
throw ArgumentError.value(
9477
route, 'route', 'expression for "${m[2]}" is capturing');
@@ -112,10 +95,9 @@ class RouterEntry {
11295
}
11396
// Construct map from parameter name to matched value
11497
var params = <String, String>{};
115-
for (var i = 0; i < _paramInfos.length; i++) {
98+
for (var i = 0; i < _params.length; i++) {
11699
// first group is always the full match, we ignore this group.
117-
final paramInfo = _paramInfos[i];
118-
params[paramInfo.name] = m[i + 1]!;
100+
params[_params[i]] = m[i + 1]!;
119101
}
120102
return params;
121103
}
@@ -132,37 +114,14 @@ class RouterEntry {
132114
return await _handler(request, this) as Response;
133115
}
134116

135-
if (_handler is Handler || _paramInfos.isEmpty) {
117+
if (_handler is Handler || _params.isEmpty) {
136118
return await _handler(request) as Response;
137119
}
138120

139121
return await Function.apply(_handler, [
140122
request,
141-
..._paramInfos.map((info) => params[info.name]),
123+
..._params.map((n) => params[n]),
142124
]) as Response;
143125
})(request);
144126
}
145127
}
146-
147-
/// This class holds information about a parameter extracted
148-
/// from the route path.
149-
/// The indexes can by used by the mount logic to resolve the
150-
/// parametrized path when handling the request.
151-
class ParamInfo {
152-
/// This is the name of the parameter, without <, >
153-
final String name;
154-
155-
/// The index in the route String where the parameter
156-
/// expression starts (inclusive)
157-
final int startIdx;
158-
159-
/// The index in the route String where the parameter
160-
/// expression ends (exclusive)
161-
final int endIdx;
162-
163-
const ParamInfo({
164-
required this.name,
165-
required this.startIdx,
166-
required this.endIdx,
167-
});
168-
}

0 commit comments

Comments
 (0)