rax-radix-tree 1.0.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 igeeky.io@gmail.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,574 @@
1
+ # Name
2
+
3
+ A high-performance radix tree implementation for Node.js, providing efficient routing and pattern matching capabilities. This library is built on top of the rax radix tree implementation in ANSI C (https://github.com/antirez/rax), offering excellent performance and scalability for large-scale applications.
4
+
5
+ # Table of Contents
6
+
7
+ - [Name](#name)
8
+ - [Table of Contents](#table-of-contents)
9
+ - [Synopsis](#synopsis)
10
+ - [RadixTree](#radixtree)
11
+ - [HttpRadixTree](#httpradixtree)
12
+ - [Class Methods](#class-methods)
13
+ - [RadixTree](#radixtree-1)
14
+ - [constructor](#constructor)
15
+ - [findAllRoutes](#findallroutes)
16
+ - [findRoute](#findroute)
17
+ - [HttpRadixTree](#httpradixtree-1)
18
+ - [constructor](#constructor-1)
19
+ - [Examples](#examples)
20
+ - [Full Path Match](#full-path-match)
21
+ - [Prefix Match](#prefix-match)
22
+ - [Suffix Match](#suffix-match)
23
+ - [Domain Matching](#domain-matching)
24
+ - [Remote Address Matching](#remote-address-matching)
25
+ - [Expression Matching (GET Parameters)](#expression-matching-get-parameters)
26
+ - [Expression Matching (Request Headers)](#expression-matching-request-headers)
27
+ - [Installation](#installation)
28
+ - [Benchmarks](#benchmarks)
29
+ - [License](#license)
30
+
31
+ # Synopsis
32
+
33
+ ## RadixTree
34
+
35
+ ```js
36
+ const { RadixTree } = require('rax-radix-tree');
37
+
38
+ // Define routes
39
+ const routes = [
40
+ { paths: ['/api/users'], meta: { id: 1 } },
41
+ { paths: ['/api/users/*'], meta: { id: 2 } },
42
+ { paths: ['/api/posts/**'], meta: { id: 3 } },
43
+ { paths: ['**.jpg', '*.png'], meta: { id: 4 } },
44
+ ];
45
+
46
+ // Create RadixTree instance
47
+ const tree = new RadixTree(routes);
48
+
49
+ // Find matching routes
50
+ const result1 = tree.findAllRoutes('/api/users');
51
+ console.log(result1); // [{ meta: { id: 1 } }]
52
+
53
+ const result2 = tree.findAllRoutes('/api/users/123');
54
+ console.log(result2); // [{ meta: { id: 2 } }]
55
+
56
+ const result3 = tree.findAllRoutes('/api/posts/2023/05/01');
57
+ console.log(result3); // [{ meta: { id: 3 } }]
58
+
59
+ const result4 = tree.findAllRoutes('/images/photo.jpg');
60
+ console.log(result4); // [{ meta: { id: 4 } }]
61
+
62
+ // Use findRoute method (requires passing an object containing path)
63
+ const matchedRoute = tree.findRoute({ path: '/api/users/123', method: 'GET' });
64
+ console.log(matchedRoute); // { meta: { id: 2 } }
65
+ ```
66
+
67
+ ## HttpRadixTree
68
+
69
+ ```js
70
+ const { HttpRadixTree } = require('rax-radix-tree');
71
+
72
+ // Define HTTP routes
73
+ const httpRoutes = [
74
+ { paths: ['/api/**'], meta: { id: 1 }, hosts: ['example.com'] },
75
+ { paths: ['/api/users/**'], meta: { id: 2 }, methods: ['GET', 'POST'] },
76
+ { paths: ['/api/admin/**'], meta: { id: 3 }, remoteAddrs: ['127.0.0.1', '192.168.1.0/24'] },
77
+ { paths: ['/api/products/**'], meta: { id: 4 }, exprs: ['category == "electronics" and price > 100'] },
78
+ ];
79
+
80
+ // Create HttpRadixTree instance
81
+ const httpTree = new HttpRadixTree(httpRoutes);
82
+
83
+ // Find matching HTTP routes
84
+ const httpResult1 = httpTree.findRoute({
85
+ path: '/api/users/123',
86
+ method: 'GET',
87
+ headers: { host: 'example.com' }
88
+ });
89
+ console.log(httpResult1); // { meta: { id: 2 } }
90
+
91
+ const httpResult2 = httpTree.findRoute({
92
+ path: '/api/admin/dashboard',
93
+ method: 'GET',
94
+ remoteAddr: '127.0.0.1'
95
+ });
96
+ console.log(httpResult2); // { meta: { id: 3 } }
97
+
98
+ const httpResult3 = httpTree.findRoute({
99
+ path: '/api/products/laptop',
100
+ method: 'GET',
101
+ args: { category: 'electronics', price: '599.99' }
102
+ });
103
+ console.log(httpResult3); // { meta: { id: 4 } }
104
+
105
+ // Non-matching case
106
+ const httpResult4 = httpTree.findRoute({
107
+ path: '/api/users/123',
108
+ method: 'PUT',
109
+ headers: { host: 'other-domain.com' }
110
+ });
111
+ console.log(httpResult4); // null
112
+ ```
113
+
114
+ [Back to TOC](#table-of-contents)
115
+
116
+ # Class Methods
117
+
118
+ ## RadixTree
119
+
120
+ ### constructor
121
+
122
+ ```js
123
+ constructor(routes: Route[], matchFunc?: MatchFunction)
124
+ ```
125
+
126
+ Creates a new RadixTree instance.
127
+
128
+ Parameters:
129
+
130
+ - `routes`: Array of route configurations
131
+ - `matchFunc`: Optional custom matching function
132
+
133
+ Each route can contain the following properties:
134
+
135
+ | Name | Required? | Description | Example |
136
+ |---------|-----------|-----------------------------------------------------------------------|------------------------------|
137
+ | paths | Required | List of request paths to match. By default, performs full matching. Adding `*` at the end will perform prefix matching, but not including '/' characters. Adding `**` will perform arbitrary matching, matching all characters. For example, `/foo*` can match `/foobar`, but not `/foo/bar`. `/foo**` can match both `/foo/bar` and `/foo/car/far`. Adding a suffix pattern like `**.jpg` will match all paths ending with `.jpg`. For instance, `**.jpg` would match `/images/photo.jpg` and `/uploads/profile.jpg`. | `["/", "/foo", "/bar/*", "/baz/**", "**.jpg"]` |
138
+ | methods | Optional | List of HTTP methods to match. Valid values: "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE", and "ALL". If not specified, defaults to "ALL". | `["GET", "POST"]` |
139
+ | meta | Optional | Metadata to be returned when the route matches. | `{ id: 1 }` |
140
+
141
+ ### findAllRoutes
142
+
143
+ ```js
144
+ findAllRoutes(path: string, method?: string): Array<Route>
145
+ ```
146
+
147
+ Finds all routes that match the given path and method.
148
+
149
+ Parameters:
150
+
151
+ - `path`: The path to match against
152
+ - `method`: (Optional) The HTTP method to match
153
+
154
+ Returns:
155
+
156
+ - An array of matching routes
157
+
158
+ ### findRoute
159
+
160
+ ```js
161
+ findRoute(req: { path: string, method?: string, [key: string]: any }): Route | null
162
+ ```
163
+
164
+ Finds the first route that matches the given request object.
165
+
166
+ Parameters:
167
+
168
+ - `req`: A request object containing path, method, and other optional properties
169
+
170
+ Returns:
171
+
172
+ - The matching route object if found, or null if no match is found
173
+
174
+ ## HttpRadixTree
175
+
176
+ ### constructor
177
+
178
+ ```js
179
+ constructor(routes: HttpRoute[])
180
+ ```
181
+
182
+ Creates a new HttpRadixTree instance.
183
+
184
+ Parameters:
185
+
186
+ - `routes`: Array of HTTP route configurations
187
+
188
+ Each HTTP route can contain the following properties:
189
+
190
+ | Name | Required? | Description | Example |
191
+ |-------------|-----------|------------------------------------------------------------------------------------------------|---------------------------------------------|
192
+ | paths | Required | List of request paths to match. Supports the same matching rules as RadixTree. | `["/api/**", "/users/*"]` |
193
+ | methods | Optional | List of HTTP methods to match. Same as RadixTree. | `["GET", "POST"]` |
194
+ | hosts | Optional | List of host addresses to match. Supports wildcards. For example, `*.example.com` can match `foo.example.com` and `bar.example.com`. | `["example.com", "*.api.com"]` |
195
+ | remoteAddrs | Optional | List of remote addresses (IPv4 or IPv6) to match. Supports CIDR format. | `["127.0.0.1", "192.168.1.0/24", "::1", "fe80::/10"]` |
196
+ | exprs | Optional | List of expressions to evaluate request parameters. Uses [filtrex](https://github.com/joewalnes/filtrex) syntax. | `["age >= 18", "category in ('electronics', 'books')"]` |
197
+ | meta | Optional | Metadata to be returned when the route matches. | `{ id: 1 }` |
198
+
199
+ HttpRadixTree adds support for matching hosts, remote addresses, and custom expressions on top of RadixTree, making it more suitable for HTTP routing scenarios.
200
+
201
+ Both classes provide powerful route matching capabilities and can be chosen based on different needs. RadixTree is suitable for general path matching scenarios, while HttpRadixTree is more suitable for complex HTTP routing matching requirements.
202
+
203
+ [Back to TOC](#table-of-contents)
204
+
205
+ # Examples
206
+
207
+ ## Full Path Match
208
+
209
+ Demonstrates basic full path matching:
210
+
211
+ ```js
212
+ const { RadixTree } = require('rax-radix-tree');
213
+
214
+ const routes = [
215
+ { paths: ['/users'], meta: { handler: 'listUsers' } },
216
+ { paths: ['/users/create'], meta: { handler: 'createUser' } },
217
+ { paths: ['/posts'], meta: { handler: 'listPosts' } }
218
+ ];
219
+
220
+ const tree = new RadixTree(routes);
221
+
222
+ console.log(tree.findRoute({ path: '/users' }));
223
+ // Output: { meta: { handler: 'listUsers' } }
224
+
225
+ console.log(tree.findRoute({ path: '/users/create' }));
226
+ // Output: { meta: { handler: 'createUser' } }
227
+
228
+ console.log(tree.findRoute({ path: '/posts' }));
229
+ // Output: { meta: { handler: 'listPosts' } }
230
+
231
+ console.log(tree.findRoute({ path: '/unknown' }));
232
+ // Output: null
233
+ ```
234
+
235
+ ## Prefix Match
236
+
237
+ Shows how to use wildcard characters for prefix matching:
238
+
239
+
240
+ ```js
241
+ const { RadixTree } = require('rax-radix-tree');
242
+
243
+ const routes = [
244
+ { paths: ['/api/*'], meta: { access: 'public' } },
245
+ { paths: ['/admin/**'], meta: { access: 'private' } }
246
+ ];
247
+
248
+ const tree = new RadixTree(routes);
249
+
250
+ console.log(tree.findRoute({ path: '/api/users' }));
251
+ // Output: { meta: { access: 'public' } }
252
+
253
+ console.log(tree.findRoute({ path: '/api/posts/recent' }));
254
+ // Output: null (because '/api/*' only matches one level)
255
+
256
+ console.log(tree.findRoute({ path: '/admin/dashboard' }));
257
+ // Output: { meta: { access: 'private' } }
258
+
259
+ console.log(tree.findRoute({ path: '/admin/users/edit' }));
260
+ // Output: { meta: { access: 'private' } }
261
+ ```
262
+
263
+ ## Suffix Match
264
+
265
+ Illustrates suffix matching using double wildcards:
266
+
267
+ ```js
268
+ const { RadixTree } = require('rax-radix-tree');
269
+
270
+ const routes = [
271
+ { paths: ['**.jpg', '**.png'], meta: { type: 'image' } },
272
+ { paths: ['**.html'], meta: { type: 'webpage' } }
273
+ ];
274
+
275
+ const tree = new RadixTree(routes);
276
+
277
+ console.log(tree.findRoute({ path: '/images/photo.jpg' }));
278
+ // Output: { meta: { type: 'image' } }
279
+
280
+ console.log(tree.findRoute({ path: '/assets/icon.png' }));
281
+ // Output: { meta: { type: 'image' } }
282
+
283
+ console.log(tree.findRoute({ path: '/index.html' }));
284
+ // Output: { meta: { type: 'webpage' } }
285
+
286
+ console.log(tree.findRoute({ path: '/document.pdf' }));
287
+ // Output: null
288
+ ```
289
+
290
+ ## Domain Matching
291
+
292
+ Demonstrates domain matching using HttpRadixTree:
293
+
294
+ ```js
295
+ const { HttpRadixTree } = require('rax-radix-tree');
296
+
297
+ const routes = [
298
+ { paths: ['/**'], hosts: ['example.com'], meta: { site: 'main' } },
299
+ { paths: ['/**'], hosts: ['*.example.com'], meta: { site: 'subdomain' } },
300
+ { paths: ['/**'], hosts: ['api.example.com'], meta: { site: 'api' } }
301
+ ];
302
+
303
+ const tree = new HttpRadixTree(routes);
304
+
305
+ console.log(tree.findRoute({ path: '/', headers: { host: 'example.com' } }));
306
+ // Output: { meta: { site: 'main' } }
307
+
308
+ console.log(tree.findRoute({ path: '/about', headers: { host: 'blog.example.com' } }));
309
+ // Output: { meta: { site: 'subdomain' } }
310
+
311
+ console.log(tree.findRoute({ path: '/users', headers: { host: 'api.example.com' } }));
312
+ // Output: { meta: { site: 'api' } }
313
+ ```
314
+
315
+ ## Remote Address Matching
316
+
317
+ Shows how to match routes based on remote IP addresses:
318
+
319
+ ```js
320
+ const { HttpRadixTree } = require('rax-radix-tree');
321
+
322
+ const routes = [
323
+ { paths: ['/admin/**'], remoteAddrs: ['127.0.0.1'], meta: { access: 'local' } },
324
+ { paths: ['/api/**'], remoteAddrs: ['192.168.1.0/24'], meta: { access: 'lan' } },
325
+ { paths: ['/**'], meta: { access: 'public' } }
326
+ ];
327
+
328
+ const tree = new HttpRadixTree(routes);
329
+
330
+ console.log(tree.findRoute({ path: '/admin/dashboard', remoteAddr: '127.0.0.1' }));
331
+ // Output: { meta: { access: 'local' } }
332
+
333
+ console.log(tree.findRoute({ path: '/api/users', remoteAddr: '192.168.1.100' }));
334
+ // Output: { meta: { access: 'lan' } }
335
+
336
+ console.log(tree.findRoute({ path: '/', remoteAddr: '203.0.113.1' }));
337
+ // Output: { meta: { access: 'public' } }
338
+ ```
339
+
340
+ ## Expression Matching (GET Parameters)
341
+
342
+ Illustrates how to use expressions for matching GET parameters:
343
+
344
+ ```js
345
+ const { HttpRadixTree } = require('rax-radix-tree');
346
+
347
+ const routes = [
348
+ { paths: ['/products/**'], exprs: ['category == "electronics" and price > 100'], meta: { discount: '10%' } },
349
+ { paths: ['/products/**'], exprs: ['category == "books" and quantity >= 5'], meta: { discount: '20%' } },
350
+ { paths: ['/products/**'], meta: { discount: '0%' } }
351
+ ];
352
+
353
+ const tree = new HttpRadixTree(routes);
354
+
355
+ console.log(tree.findRoute({
356
+ path: '/products/laptop',
357
+ args: { category: 'electronics', price: '999.99' }
358
+ }));
359
+ // Output: { meta: { discount: '10%' } }
360
+
361
+ console.log(tree.findRoute({
362
+ path: '/products/novel',
363
+ args: { category: 'books', quantity: '7' }
364
+ }));
365
+ // Output: { meta: { discount: '20%' } }
366
+
367
+ console.log(tree.findRoute({
368
+ path: '/products/pencil',
369
+ args: { category: 'stationery', price: '1.99' }
370
+ }));
371
+ // Output: { meta: { discount: '0%' } }
372
+ ```
373
+
374
+ ## Expression Matching (Request Headers)
375
+
376
+ Demonstrates expression matching based on request headers:
377
+
378
+ ```js
379
+ const { HttpRadixTree } = require('rax-radix-tree');
380
+
381
+ const routes = [
382
+ { paths: ['/**'], exprs: ['contains(user_agent, "Mozilla") and accept_language == "en-US"'], meta: { version: 'v1' } },
383
+ { paths: ['/**'], exprs: ['contains(user_agent, "Chrome") and accept_language == "zh-CN"'], meta: { version: 'v2' } },
384
+ { paths: ['/**'], meta: { version: 'default' } }
385
+ ];
386
+
387
+ const tree = new HttpRadixTree(routes);
388
+
389
+ console.log(tree.findRoute({
390
+ path: '/',
391
+ headers: {
392
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
393
+ 'accept-language': 'en-US'
394
+ }
395
+ }));
396
+ // Output: { meta: { version: 'v1' } }
397
+
398
+ console.log(tree.findRoute({
399
+ path: '/about',
400
+ headers: {
401
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
402
+ 'accept-language': 'zh-CN'
403
+ }
404
+ }));
405
+ // Output: { meta: { version: 'v2' } }
406
+
407
+ console.log(tree.findRoute({
408
+ path: '/contact',
409
+ headers: {
410
+ 'user-agent': 'curl/7.64.1',
411
+ 'accept-language': 'fr-FR'
412
+ }
413
+ }));
414
+ // Output: { meta: { version: 'default' } }
415
+ ```
416
+
417
+ [Back to TOC](#table-of-contents)
418
+
419
+ # Installation
420
+
421
+ ```shell
422
+ npm install rax-radix-tree
423
+ ```
424
+
425
+
426
+ # Benchmarks
427
+
428
+ Benchmark test cases are located in the [benchmark](./benchmark) directory.
429
+
430
+ Environment: MacBook Pro (2023), Apple M3 Max
431
+
432
+ To start benchmarking, run:
433
+
434
+ ```shell
435
+ sh benchmark/run.sh
436
+ ```
437
+
438
+ Result:
439
+
440
+ ```markdown
441
+ ## Match equals
442
+
443
+ * Result: equals_50000
444
+ * Number of routes: 100000
445
+ * Number of matches: 10000000
446
+ * Execution time: 492 ms
447
+ * QPS: 20320429
448
+
449
+ ## Match suffix
450
+
451
+ * Result: suffix_50000
452
+ * Number of routes: 100000
453
+ * Number of matches: 1000000
454
+ * Execution time: 2851 ms
455
+ * QPS: 350786
456
+
457
+ ## Match prefix, route found
458
+
459
+ * Result: prefix_50000
460
+ * Number of routes: 100000
461
+ * Number of matches: 1000000
462
+ * Execution time: 2302 ms
463
+ * QPS: 434428
464
+
465
+ ## Match prefix, route not found
466
+
467
+ * Result: null
468
+ * Number of routes: 100000
469
+ * Number of matches: 1000000
470
+ * Execution time: 2231 ms
471
+ * QPS: 448280
472
+
473
+ ## Equal match in combined tree
474
+
475
+ * Result: equals_50000
476
+ * Number of routes: 300000
477
+ * Number of matches: 100000
478
+ * Execution time: 372 ms
479
+ * QPS: 269047
480
+
481
+ ## Prefix match in combined tree
482
+
483
+ * Result: prefix_50000
484
+ * Number of routes: 300000
485
+ * Number of matches: 100000
486
+ * Execution time: 453 ms
487
+ * QPS: 220543
488
+
489
+ ## Suffix match in combined tree
490
+
491
+ * Result: suffix_50000
492
+ * Number of routes: 300000
493
+ * Number of matches: 100000
494
+ * Execution time: 525 ms
495
+ * QPS: 190578
496
+
497
+ ## Route not found in combined tree
498
+
499
+ * Result: null
500
+ * Number of routes: 300000
501
+ * Number of matches: 100000
502
+ * Execution time: 444 ms
503
+ * QPS: 225436
504
+
505
+ ## Match hosts
506
+
507
+ * Result: hosts_500
508
+ * Number of routes: 1000
509
+ * Number of matches: 100000
510
+ * Execution time: 3067 ms
511
+ * QPS: 32608
512
+
513
+ ## Match wildcard hosts
514
+
515
+ * Result: wildcard_hosts_500
516
+ * Number of routes: 1000
517
+ * Number of matches: 100000
518
+ * Execution time: 3995 ms
519
+ * QPS: 25029
520
+
521
+ ## Match remote address, route found
522
+
523
+ * Result: { access: 'local' }
524
+ * Number of routes: 1000
525
+ * Number of matches: 1000
526
+ * Execution time: 172 ms
527
+ * QPS: 5804
528
+
529
+ ## Match remote address, route not found
530
+
531
+ * Result: { access: 'public' }
532
+ * Number of routes: 1000
533
+ * Number of matches: 1000
534
+ * Execution time: 300 ms
535
+ * QPS: 3327
536
+
537
+ ## Match expressions and headers, route found
538
+
539
+ * Result: { version: 'v501' }
540
+ * Number of routes: 1000
541
+ * Number of matches: 1000
542
+ * Execution time: 442 ms
543
+ * QPS: 2262
544
+
545
+ ## Match expressions and headers, route not found
546
+
547
+ * Result: { version: 'default' }
548
+ * Number of routes: 1000
549
+ * Number of matches: 1000
550
+ * Execution time: 778 ms
551
+ * QPS: 1285
552
+
553
+ ## Match expressions and parameters, route found
554
+
555
+ * Result: { discount: '10%' }
556
+ * Number of routes: 1000
557
+ * Number of matches: 1000
558
+ * Execution time: 275 ms
559
+ * QPS: 3632
560
+
561
+ ## Match expressions and parameters, route not found
562
+
563
+ * Result: { discount: '0%' }
564
+ * Number of routes: 1000
565
+ * Number of matches: 1000
566
+ * Execution time: 475 ms
567
+ * QPS: 2104
568
+ ```
569
+
570
+ [Back to TOC](#table-of-contents)
571
+
572
+ # License
573
+
574
+ [MIT](./LICENSE)
@@ -0,0 +1,21 @@
1
+
2
+ const { benchmark, md5 } = require('./utility');
3
+ const routeCount = 1000 * 100;
4
+ const matchTimes = 1000 * 1000 * 10;
5
+
6
+ const routes = [];
7
+ for (let i = 1; i <= routeCount; i++) {
8
+ routes.push({
9
+ paths: ["/" + md5(i.toString())],
10
+ meta: `equals_${i}`
11
+ });
12
+ }
13
+
14
+ const middleValue = Math.floor(routeCount / 2).toString();
15
+ const path = "/" + md5(middleValue);
16
+
17
+ const req = {
18
+ path: path,
19
+ };
20
+
21
+ benchmark("Match equals", routeCount, matchTimes, routes, req);
@@ -0,0 +1,20 @@
1
+
2
+ const { benchmark, md5 } = require('./utility');
3
+ const routeCount = 1000 * 100;
4
+ const matchTimes = 1000 * 1000;
5
+ const routes = [];
6
+ for (let i = 1; i <= routeCount; i++) {
7
+ routes.push({
8
+ paths: ["**." + md5(i.toString())],
9
+ meta: `suffix_${i}`
10
+ });
11
+ }
12
+
13
+ const middleValue = Math.floor(routeCount / 2).toString();
14
+ const path = "abc." + md5(middleValue);
15
+
16
+ const req = {
17
+ path: path,
18
+ };
19
+
20
+ benchmark("Match suffix", routeCount, matchTimes, routes, req);
@@ -0,0 +1,28 @@
1
+
2
+ const { benchmark, md5 } = require('./utility');
3
+ const routeCount = 1000 * 100;
4
+ const matchTimes = 1000 * 1000;
5
+ const routes = [];
6
+ for (let i = 1; i <= routeCount; i++) {
7
+ routes.push({
8
+ paths: ["/" + md5(i.toString()) + "/*"],
9
+ meta: `prefix_${i}`
10
+ });
11
+ }
12
+
13
+ const middleValue = Math.floor(routeCount / 2).toString();
14
+ const path = "/" + md5(middleValue) + "/a";
15
+
16
+ const req = {
17
+ path: path,
18
+ };
19
+
20
+ benchmark("Match prefix, route found", routeCount, matchTimes, routes, req);
21
+
22
+ const path2 = "/" + md5((routeCount + 1).toString()) + "/a";
23
+
24
+ const req2 = {
25
+ path: path2,
26
+ };
27
+
28
+ benchmark("Match prefix, route not found", routeCount, matchTimes, routes, req2);
@@ -0,0 +1,36 @@
1
+ const { benchmark, md5 } = require('./utility');
2
+ const routeCount = 1000 * 100;
3
+ const matchTimes = 1000 * 100;
4
+
5
+ const routes = [];
6
+ for (let i = 1; i <= routeCount; i++) {
7
+ const hash = md5(i.toString());
8
+ routes.push(
9
+ { paths: ["/" + hash], meta: `equals_${i}` },
10
+ { paths: ["/" + hash + "/*"], meta: `prefix_${i}` },
11
+ { paths: ["**." + hash], meta: `suffix_${i}` }
12
+ );
13
+ }
14
+
15
+ // Calculate middle value
16
+ const middleValue = Math.floor(routeCount / 2).toString();
17
+
18
+ // Equal match test in combined tree
19
+ const equalPath = "/" + md5(middleValue);
20
+ const equalReq = { path: equalPath };
21
+ benchmark("Equal match in combined tree", routeCount * 3, matchTimes, routes, equalReq);
22
+
23
+ // Prefix match test in combined tree
24
+ const prefixPath = "/" + md5(middleValue) + "/a";
25
+ const prefixReq = { path: prefixPath };
26
+ benchmark("Prefix match in combined tree", routeCount * 3, matchTimes, routes, prefixReq);
27
+
28
+ // Suffix match test in combined tree
29
+ const suffixPath = "abc." + md5(middleValue);
30
+ const suffixReq = { path: suffixPath };
31
+ benchmark("Suffix match in combined tree", routeCount * 3, matchTimes, routes, suffixReq);
32
+
33
+ // Route not found test in combined tree
34
+ const notFoundPath = "/" + md5((routeCount + 1).toString()) + "/notfound";
35
+ const notFoundReq = { path: notFoundPath };
36
+ benchmark("Route not found in combined tree", routeCount * 3, matchTimes, routes, notFoundReq);