router-http 1.0.13 → 2.0.0
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.md +0 -0
- package/README.md +157 -95
- package/package.json +17 -17
- package/src/index.js +184 -103
package/LICENSE.md
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -4,117 +4,200 @@
|
|
|
4
4
|
[](https://coveralls.io/github/Kikobeats/router-http)
|
|
5
5
|
[](https://www.npmjs.org/package/router-http)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- [router-http](#router-http)
|
|
8
|
+
- [Why not Express router?](#why-not-express-router)
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Getting Started](#getting-started)
|
|
11
|
+
- [Declaring routes](#declaring-routes)
|
|
12
|
+
- [Declaring middlewares](#declaring-middlewares)
|
|
13
|
+
- [Starting the server](#starting-the-server)
|
|
14
|
+
- [Advanced](#advanced)
|
|
15
|
+
- [Request object](#request-object)
|
|
16
|
+
- [Print routes](#print-routes)
|
|
17
|
+
- [Nested routers](#nested-routers)
|
|
18
|
+
- [Skipping to parent router](#skipping-to-parent-router)
|
|
19
|
+
- [Benchmark](#benchmark)
|
|
20
|
+
- [Related](#related)
|
|
21
|
+
- [License](#license)
|
|
8
22
|
|
|
9
|
-
- Faster (~30% more requests per second than Express).
|
|
10
|
-
- Maintained and well tested.
|
|
11
|
-
- Smaller (1.8 kB).
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
A middleware-style router similar to [express router](https://github.com/pillarjs/router), with key advantages:
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
- **Predictable performance** – Backed by [find-my-way](https://github.com/delvedor/find-my-way), a trie-based router with constant O(1) lookup time.
|
|
27
|
+
- **Battle-tested** – Well maintained with comprehensive test coverage.
|
|
28
|
+
- **Lightweight** – Only 1.3 kB (minifized + gzipped)
|
|
16
29
|
|
|
17
|
-
##
|
|
30
|
+
## Why not Express router?
|
|
31
|
+
|
|
32
|
+
Express uses regex-based route matching that degrades linearly as routes increase:
|
|
33
|
+
|
|
34
|
+
| Routes | `express@router` | `router-http` |
|
|
35
|
+
|--------|-----------|---------------|
|
|
36
|
+
| 5 | ~10.7M ops/sec | **~13.7M ops/sec** |
|
|
37
|
+
| 10 | ~6.5M ops/sec | **~13.7M ops/sec** |
|
|
38
|
+
| 50 | ~1.5M ops/sec | **~11.5M ops/sec** |
|
|
39
|
+
| 1000 | ~41K ops/sec | **~10.6M ops/sec** |
|
|
40
|
+
|
|
41
|
+
In contrast, **router-http** is backed by a trie-based implementation that maintains nearly constant performance regardless of the number of routes.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
18
44
|
|
|
19
45
|
```bash
|
|
20
|
-
|
|
46
|
+
npm install router-http
|
|
21
47
|
```
|
|
22
48
|
|
|
23
|
-
##
|
|
49
|
+
## Getting Started
|
|
24
50
|
|
|
25
|
-
First,
|
|
51
|
+
First, define a handler for errors and unmatched routes:
|
|
26
52
|
|
|
27
53
|
```js
|
|
28
54
|
const createRouter = require('router-http')
|
|
29
55
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
56
|
+
const finalHandler = (error, req, res) => {
|
|
57
|
+
if (error) {
|
|
58
|
+
res.statusCode = 500
|
|
59
|
+
res.end(error.message)
|
|
60
|
+
} else {
|
|
61
|
+
res.statusCode = 404
|
|
62
|
+
res.end('Not Found')
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const router = createRouter(finalHandler)
|
|
35
67
|
```
|
|
36
68
|
|
|
37
|
-
|
|
69
|
+
You can also pass [find-my-way options](https://github.com/delvedor/find-my-way#options) as a second argument:
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
const router = createRouter(finalHandler, {
|
|
73
|
+
caseSensitive: false,
|
|
74
|
+
ignoreTrailingSlash: true
|
|
75
|
+
})
|
|
76
|
+
```
|
|
38
77
|
|
|
39
78
|
### Declaring routes
|
|
40
79
|
|
|
41
|
-
|
|
80
|
+
Use HTTP verb methods to define your routes:
|
|
42
81
|
|
|
43
82
|
```js
|
|
44
|
-
/**
|
|
45
|
-
* Declaring multiple routes based on the HTTP verb.
|
|
46
|
-
*/
|
|
47
83
|
router
|
|
48
|
-
.get('/', (req, res) =>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.post('/ping', (req, res) => res.end('pong'))
|
|
53
|
-
.get('/greetings/:name', (req, res) => {
|
|
54
|
-
const { name } = req.params
|
|
55
|
-
res.end(`Hello, ${name}!`)
|
|
56
|
-
})
|
|
84
|
+
.get('/', (req, res) => res.end('Hello World'))
|
|
85
|
+
.post('/users', (req, res) => res.end('User created'))
|
|
86
|
+
.put('/users/:id', (req, res) => res.end('User updated'))
|
|
87
|
+
.delete('/users/:id', (req, res) => res.end('User deleted'))
|
|
57
88
|
```
|
|
58
89
|
|
|
59
|
-
|
|
90
|
+
Use `.all()` to match any HTTP method:
|
|
60
91
|
|
|
61
92
|
```js
|
|
62
|
-
/**
|
|
63
|
-
* Declaring a route to match all the HTTP verbs.
|
|
64
|
-
*/
|
|
65
93
|
router.all('/ping', (req, res) => res.end('pong'))
|
|
66
94
|
```
|
|
67
95
|
|
|
96
|
+
The dynamic segments will be captured using the `:param` syntax, with parameters accessible via `req.params`:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
router.get('/users/:id', (req, res) => {
|
|
100
|
+
res.end(`User ID: ${req.params.id}`)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
router.get('/posts/:year/:month', (req, res) => {
|
|
104
|
+
const { year, month } = req.params
|
|
105
|
+
res.end(`Posts from ${month}/${year}`)
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
See [Request object](#request-object) for details on how to access route parameters and other useful properties added to `req`.
|
|
110
|
+
|
|
68
111
|
### Declaring middlewares
|
|
69
112
|
|
|
70
|
-
A middleware
|
|
113
|
+
A middleware is declared using `.use()`. it will be added globally before running any route:
|
|
71
114
|
|
|
72
115
|
```js
|
|
73
|
-
|
|
74
|
-
* Declaring a middleware that will be always executed.
|
|
75
|
-
*/
|
|
116
|
+
// Global middleware (runs on every request)
|
|
76
117
|
router
|
|
77
|
-
.use(
|
|
118
|
+
.use((req, res, next) => {
|
|
78
119
|
req.timestamp = Date.now()
|
|
79
120
|
next()
|
|
80
|
-
|
|
121
|
+
})
|
|
81
122
|
```
|
|
82
123
|
|
|
83
|
-
|
|
124
|
+
You can also declare middleware specific to routes:
|
|
84
125
|
|
|
85
126
|
```js
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
127
|
+
const auth = (req, res, next) => { /* verify token */ next() }
|
|
128
|
+
const log = (req, res, next) => { /* log request */ next() }
|
|
129
|
+
|
|
130
|
+
const protected = [auth, log]
|
|
131
|
+
|
|
89
132
|
router
|
|
90
|
-
.
|
|
91
|
-
|
|
92
|
-
next()
|
|
93
|
-
})
|
|
94
|
-
.get('/greetings/:username', (req, res) => {
|
|
95
|
-
res.end(`${req.greetings}, ${req.params.username}`)
|
|
96
|
-
})
|
|
133
|
+
.get('/admin', protected, (req, res) => res.end('Admin panel'))
|
|
134
|
+
.get('/settings', protected, (req, res) => res.end('Settings'))
|
|
97
135
|
```
|
|
98
136
|
|
|
99
|
-
|
|
137
|
+
If you want to add a middleware conditionally, just return a falsy value:
|
|
100
138
|
|
|
101
139
|
```js
|
|
102
|
-
/**
|
|
103
|
-
* Just add the middleware if it's production environment
|
|
104
|
-
*/
|
|
105
140
|
router
|
|
106
|
-
.use(process.env.NODE_ENV === 'production' &&
|
|
141
|
+
.use(process.env.NODE_ENV === 'production' && rateLimiter())
|
|
107
142
|
```
|
|
108
143
|
|
|
109
|
-
|
|
144
|
+
### Starting the server
|
|
110
145
|
|
|
111
|
-
|
|
146
|
+
The router is a standard request handler. Pass it to `http.createServer`:
|
|
112
147
|
|
|
113
|
-
|
|
148
|
+
```js
|
|
149
|
+
const http = require('http')
|
|
114
150
|
|
|
115
|
-
|
|
151
|
+
console.log(router.prettyPrint())
|
|
152
|
+
// └── / (GET)
|
|
153
|
+
// ├── favicon.ico (GET)
|
|
154
|
+
// └── user/
|
|
155
|
+
// └── :id (GET)
|
|
116
156
|
|
|
117
|
-
|
|
157
|
+
http.createServer(router).listen(3000)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Advanced
|
|
161
|
+
|
|
162
|
+
### Request object
|
|
163
|
+
|
|
164
|
+
The router adds these properties to `req`:
|
|
165
|
+
|
|
166
|
+
| Property | Description |
|
|
167
|
+
|----------|-------------|
|
|
168
|
+
| `req.path` | URL pathname |
|
|
169
|
+
| `req.params` | Route parameters object |
|
|
170
|
+
| `req.query` | Raw query string (after `?`) |
|
|
171
|
+
| `req.search` | Raw search string (including `?`) |
|
|
172
|
+
|
|
173
|
+
> `req.query` and `req.search` are only set if not already present.
|
|
174
|
+
|
|
175
|
+
### Print routes
|
|
176
|
+
|
|
177
|
+
You can visualize your router's routes in a readable tree format using the `router.prettyPrint()` method. This is especially helpful for debugging or understanding your route structure at a glance.
|
|
178
|
+
|
|
179
|
+
For example:
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
const http = require('http')
|
|
183
|
+
|
|
184
|
+
console.log(router.prettyPrint())
|
|
185
|
+
// └── / (GET)
|
|
186
|
+
// ├── favicon.ico (GET)
|
|
187
|
+
// └── user/
|
|
188
|
+
// └── :id (GET)
|
|
189
|
+
|
|
190
|
+
http.createServer(router).listen(3000)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The printed output shows the nested structure of your routes along with their registered HTTP methods. This works for both flat and deeply nested routers, including those mounted via `.use()`.
|
|
194
|
+
|
|
195
|
+
See more in [find-my-way prettyPrint documentation](https://github.com/delvedor/find-my-way#routerprettyprint).
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
### Nested routers
|
|
199
|
+
|
|
200
|
+
You can use a router as a middleware for another router. This is useful for prefixing routes or for modularizing your application:
|
|
118
201
|
|
|
119
202
|
```js
|
|
120
203
|
const createRouter = require('router-http')
|
|
@@ -144,47 +227,29 @@ router
|
|
|
144
227
|
http.createServer(router).listen(3000)
|
|
145
228
|
```
|
|
146
229
|
|
|
147
|
-
|
|
230
|
+
When a sub-router is used as a middleware, it will only handle requests that match its prefix. If no route matches inside the sub-router, it will automatically call `next()` to pass control back to the parent router.
|
|
231
|
+
|
|
232
|
+
### Skipping to parent router
|
|
148
233
|
|
|
149
|
-
|
|
234
|
+
Use `next('router')` to exit the current router and pass control back to the parent:
|
|
150
235
|
|
|
151
236
|
```js
|
|
152
|
-
const beta = createRouter(
|
|
237
|
+
const beta = createRouter(finalHandler)
|
|
153
238
|
|
|
154
|
-
// Middleware to check if the user is a beta tester
|
|
155
239
|
beta.use((req, res, next) => {
|
|
156
240
|
if (!req.isBetaTester) return next('router')
|
|
157
241
|
next()
|
|
158
242
|
})
|
|
159
243
|
|
|
160
|
-
beta.get('/
|
|
161
|
-
res.end('Using the new AI-powered search engine!')
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
const router = createRouter(final)
|
|
244
|
+
beta.get('/feature', (req, res) => res.end('Beta feature'))
|
|
165
245
|
|
|
166
|
-
// Mount the beta router
|
|
167
246
|
router.use('/v1', beta)
|
|
168
|
-
|
|
169
|
-
// This will be reached if:
|
|
170
|
-
// 1. The user is NOT a beta tester (next('router') was called)
|
|
171
|
-
// 2. Or the path didn't match anything inside the beta router
|
|
172
|
-
router.get('/v1/search', (req, res) => {
|
|
173
|
-
res.end('Using the classic search engine.')
|
|
174
|
-
})
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Using the router
|
|
178
|
-
|
|
179
|
-
After the router has been initialized, start using it as handler in your Node.js server:
|
|
180
|
-
|
|
181
|
-
```js
|
|
182
|
-
const server = http.createServer(router)
|
|
247
|
+
router.get('/v1/feature', (req, res) => res.end('Stable feature'))
|
|
183
248
|
```
|
|
184
249
|
|
|
185
250
|
## Benchmark
|
|
186
251
|
|
|
187
|
-
With all the improvements, router-http is approximately 30% faster than the express router:
|
|
252
|
+
With all the improvements, **router-http** is approximately 30% faster than the express router:
|
|
188
253
|
|
|
189
254
|
**express@5.2.1**
|
|
190
255
|
|
|
@@ -212,19 +277,16 @@ Requests/sec: 102751.65
|
|
|
212
277
|
Transfer/sec: 12.84MB
|
|
213
278
|
```
|
|
214
279
|
|
|
215
|
-
See
|
|
280
|
+
See [benchmark](/benchmark) for details.
|
|
216
281
|
|
|
217
282
|
## Related
|
|
218
283
|
|
|
219
|
-
- [send-http](https://github.com/Kikobeats/send-http) –
|
|
220
|
-
- [http-body](https://github.com/Kikobeats/http-body) – Parse
|
|
221
|
-
- [http-compression](https://github.com/Kikobeats/http-compression) –
|
|
284
|
+
- [send-http](https://github.com/Kikobeats/send-http) – `res.end` with data type detection
|
|
285
|
+
- [http-body](https://github.com/Kikobeats/http-body) – Parse request body to text/json/buffer
|
|
286
|
+
- [http-compression](https://github.com/Kikobeats/http-compression) – Gzip/Brotli compression middleware
|
|
222
287
|
|
|
223
288
|
## License
|
|
224
289
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
**router-http** © [Kiko Beats](https://kikobeats.com), released under the [MIT](https://github.com/Kikobeats/router-http/blob/master/LICENSE.md) License.<br>
|
|
228
|
-
Authored and maintained by [Kiko Beats](https://kikobeats.com) with help from [contributors](https://github.com/Kikobeats/router-http/contributors).
|
|
290
|
+
**router-http** © [Kiko Beats](https://kikobeats.com), released under the [MIT](https://github.com/Kikobeats/router-http/blob/master/LICENSE.md) License.
|
|
229
291
|
|
|
230
|
-
|
|
292
|
+
Credits to [Luke Edwards](https://github.com/lukeed) for [Polka](https://github.com/lukeed/polka) which inspired this project.
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "router-http",
|
|
3
3
|
"description": "Simple HTTP router compatible with Express",
|
|
4
4
|
"homepage": "https://github.com/Kikobeats/router-http",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "2.0.0",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"author": {
|
|
8
8
|
"email": "josefrancisco.verdu@gmail.com",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"routes"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"
|
|
33
|
-
"
|
|
32
|
+
"find-my-way": "~9.3.0",
|
|
33
|
+
"null-prototype-object": "~1.2.5"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@commitlint/cli": "latest",
|
|
@@ -55,19 +55,6 @@
|
|
|
55
55
|
"files": [
|
|
56
56
|
"src"
|
|
57
57
|
],
|
|
58
|
-
"scripts": {
|
|
59
|
-
"clean": "rm -rf node_modules",
|
|
60
|
-
"contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
|
|
61
|
-
"coverage": "c8 report --reporter=text-lcov > coverage/lcov.info",
|
|
62
|
-
"lint": "standard",
|
|
63
|
-
"postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)",
|
|
64
|
-
"prerelease": "npm run contributors",
|
|
65
|
-
"pretest": "npm run lint",
|
|
66
|
-
"release": "standard-version -a",
|
|
67
|
-
"release:github": "github-generate-release",
|
|
68
|
-
"release:tags": "git push --follow-tags origin HEAD:master",
|
|
69
|
-
"test": "c8 ava"
|
|
70
|
-
},
|
|
71
58
|
"license": "MIT",
|
|
72
59
|
"commitlint": {
|
|
73
60
|
"extends": [
|
|
@@ -91,5 +78,18 @@
|
|
|
91
78
|
"simple-git-hooks": {
|
|
92
79
|
"commit-msg": "npx commitlint --edit",
|
|
93
80
|
"pre-commit": "npx nano-staged"
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"clean": "rm -rf node_modules",
|
|
84
|
+
"contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
|
|
85
|
+
"coverage": "c8 report --reporter=text-lcov > coverage/lcov.info",
|
|
86
|
+
"lint": "standard",
|
|
87
|
+
"postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)",
|
|
88
|
+
"prerelease": "npm run contributors",
|
|
89
|
+
"pretest": "npm run lint",
|
|
90
|
+
"release": "standard-version -a",
|
|
91
|
+
"release:github": "github-generate-release",
|
|
92
|
+
"release:tags": "git push --follow-tags origin HEAD:master",
|
|
93
|
+
"test": "c8 ava"
|
|
94
94
|
}
|
|
95
|
-
}
|
|
95
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,161 +1,242 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const NullProtoObj = require('null-prototype-object')
|
|
4
|
-
const
|
|
4
|
+
const FindMyWay = require('find-my-way')
|
|
5
|
+
|
|
6
|
+
const HTTP_METHODS = [
|
|
7
|
+
'get',
|
|
8
|
+
'post',
|
|
9
|
+
'put',
|
|
10
|
+
'patch',
|
|
11
|
+
'delete',
|
|
12
|
+
'head',
|
|
13
|
+
'options',
|
|
14
|
+
'trace',
|
|
15
|
+
'connect'
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
const SLASH_CHAR_CODE = 47
|
|
19
|
+
const SYNC_ITERATION_LIMIT = 100
|
|
5
20
|
|
|
6
21
|
const requiredFinalHandler = () => {
|
|
7
22
|
throw new TypeError('You should to provide a final handler')
|
|
8
23
|
}
|
|
9
24
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
*/
|
|
13
|
-
const lead = route => (route.charCodeAt(0) === 47 ? route : `/${route}`)
|
|
25
|
+
const ensureLeadingSlash = route =>
|
|
26
|
+
route.charCodeAt(0) === SLASH_CHAR_CODE ? route : `/${route}`
|
|
14
27
|
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
return
|
|
28
|
+
const getFirstPathSegment = pathname => {
|
|
29
|
+
const secondSlashIndex = pathname.indexOf('/', 1)
|
|
30
|
+
return secondSlashIndex > 1
|
|
31
|
+
? pathname.substring(0, secondSlashIndex)
|
|
32
|
+
: pathname
|
|
18
33
|
}
|
|
19
34
|
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
|
|
35
|
+
const parseUrl = ({ url }) => {
|
|
36
|
+
const queryIndex = url.indexOf('?', 1)
|
|
37
|
+
if (queryIndex === -1) {
|
|
38
|
+
return { pathname: url, query: null, search: null }
|
|
39
|
+
}
|
|
40
|
+
const search = url.substring(queryIndex)
|
|
24
41
|
return {
|
|
25
|
-
pathname: url.substring(0,
|
|
42
|
+
pathname: url.substring(0, queryIndex),
|
|
26
43
|
query: search.substring(1),
|
|
27
44
|
search
|
|
28
45
|
}
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
const
|
|
32
|
-
req.url = req.url.substring(
|
|
33
|
-
req.path = req.path.substring(
|
|
48
|
+
const mutateRequestUrl = (prefix, req) => {
|
|
49
|
+
req.url = req.url.substring(prefix.length) || '/'
|
|
50
|
+
req.path = req.path.substring(prefix.length) || '/'
|
|
34
51
|
}
|
|
35
52
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
module.exports = (finalhandler = requiredFinalHandler(), options = {}) => {
|
|
54
|
+
const router = FindMyWay({
|
|
55
|
+
...options,
|
|
56
|
+
defaultRoute: (req, res) => finalhandler(undefined, req, res)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const globalMiddlewares = []
|
|
60
|
+
const middlewaresByPath = new NullProtoObj()
|
|
61
|
+
|
|
62
|
+
const findRoute = (method, path, constraints) => {
|
|
63
|
+
const result = router.find(method, path, constraints)
|
|
64
|
+
if (result === null) {
|
|
65
|
+
return { params: {}, handlers: [] }
|
|
66
|
+
}
|
|
67
|
+
return { params: result.params, handlers: result.handler.handlers }
|
|
40
68
|
}
|
|
41
69
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* .use(one, two)
|
|
57
|
-
* .use('/v2', two)
|
|
58
|
-
*/
|
|
59
|
-
use = (page = '/', ...fns) => {
|
|
60
|
-
if (typeof page === 'function' || typeof page === 'boolean') {
|
|
61
|
-
this.#middlewares = this.#middlewares.concat(page, fns).filter(Boolean)
|
|
62
|
-
} else if (page === '/') {
|
|
63
|
-
this.#middlewares = this.#middlewares.concat(fns).filter(Boolean)
|
|
64
|
-
} else {
|
|
65
|
-
page = lead(page)
|
|
66
|
-
fns.filter(Boolean).forEach(fn => {
|
|
67
|
-
const array = this.#middlewaresBy[page] ?? []
|
|
68
|
-
if (array.length === 0) {
|
|
69
|
-
array.push((r, _, nxt) => {
|
|
70
|
-
mutate(page, r)
|
|
71
|
-
nxt()
|
|
72
|
-
})
|
|
73
|
-
}
|
|
74
|
-
this.#middlewaresBy[page] = array.concat(fn)
|
|
75
|
-
})
|
|
70
|
+
const registerRoute = (method, path, handlers) => {
|
|
71
|
+
const routeHandler = () => {}
|
|
72
|
+
routeHandler.handlers = handlers
|
|
73
|
+
router.on(method, path, routeHandler)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const addRoute = (method, path, ...handlers) => {
|
|
77
|
+
const fns = handlers.flat().filter(Boolean)
|
|
78
|
+
if (fns.length === 0) return handler
|
|
79
|
+
|
|
80
|
+
const methods = method === '' ? HTTP_METHODS : [method]
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < methods.length; i++) {
|
|
83
|
+
registerRoute(methods[i].toUpperCase(), path, fns)
|
|
76
84
|
}
|
|
77
|
-
|
|
85
|
+
|
|
86
|
+
return handler
|
|
78
87
|
}
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
const selectMiddleware = (
|
|
90
|
+
index,
|
|
91
|
+
globalLen,
|
|
92
|
+
pathLen,
|
|
93
|
+
globalMw,
|
|
94
|
+
pathMw,
|
|
95
|
+
routeHandlers
|
|
96
|
+
) => {
|
|
97
|
+
if (index < globalLen) return globalMw[index]
|
|
98
|
+
if (index < globalLen + pathLen) return pathMw[index - globalLen]
|
|
99
|
+
return routeHandlers[index - globalLen - pathLen]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const handler = (req, res, next) => {
|
|
103
|
+
const urlInfo = parseUrl(req)
|
|
104
|
+
const pathname = urlInfo.pathname
|
|
83
105
|
req.path = pathname
|
|
84
|
-
const route = this.find(req.method, pathname)
|
|
85
|
-
const page = value(pathname)
|
|
86
106
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
const pathSegment = getFirstPathSegment(pathname)
|
|
108
|
+
|
|
109
|
+
let route = findRoute(req.method, pathname)
|
|
90
110
|
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
111
|
+
if (route.handlers.length === 0 && req.method === 'HEAD') {
|
|
112
|
+
route = findRoute('GET', pathname)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const globalMw = globalMiddlewares
|
|
116
|
+
const pathMw = middlewaresByPath[pathSegment]
|
|
117
|
+
const routeHandlers = route.handlers.length > 0 ? route.handlers : null
|
|
118
|
+
|
|
119
|
+
if (routeHandlers !== null) {
|
|
120
|
+
req.params =
|
|
121
|
+
req.params !== undefined
|
|
122
|
+
? { ...req.params, ...route.params }
|
|
123
|
+
: route.params
|
|
95
124
|
} else {
|
|
96
125
|
req.params = req.params || {}
|
|
97
126
|
}
|
|
98
127
|
|
|
99
|
-
req.search = req.query ||
|
|
100
|
-
req.query = req.query ||
|
|
128
|
+
req.search = req.query || urlInfo.search
|
|
129
|
+
req.query = req.query || urlInfo.query
|
|
101
130
|
|
|
102
131
|
let index = 0
|
|
103
|
-
let
|
|
132
|
+
let syncCount = 0
|
|
104
133
|
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
134
|
+
const globalLen = globalMw.length
|
|
135
|
+
const pathLen = pathMw !== undefined ? pathMw.length : 0
|
|
136
|
+
const routeLen = routeHandlers !== null ? routeHandlers.length : 0
|
|
137
|
+
const totalMiddlewares = globalLen + pathLen + routeLen
|
|
109
138
|
|
|
110
|
-
const
|
|
139
|
+
const handleNext = err => {
|
|
111
140
|
if (err === 'router') {
|
|
112
|
-
if (next) return next()
|
|
113
|
-
index =
|
|
141
|
+
if (next !== undefined) return next()
|
|
142
|
+
index = totalMiddlewares
|
|
114
143
|
err = undefined
|
|
115
144
|
}
|
|
116
|
-
if (err) return
|
|
117
|
-
if (++
|
|
118
|
-
|
|
119
|
-
return setImmediate(
|
|
145
|
+
if (err !== undefined) return finalhandler(err, req, res, next)
|
|
146
|
+
if (++syncCount > SYNC_ITERATION_LIMIT) {
|
|
147
|
+
syncCount = 0
|
|
148
|
+
return setImmediate(executeLoop)
|
|
120
149
|
}
|
|
121
|
-
|
|
150
|
+
executeLoop()
|
|
122
151
|
}
|
|
123
152
|
|
|
124
|
-
const
|
|
125
|
-
if (index <
|
|
153
|
+
const executeLoop = () => {
|
|
154
|
+
if (index < totalMiddlewares) {
|
|
126
155
|
if (res.writableEnded) return
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
156
|
+
|
|
157
|
+
const currentIndex = index++
|
|
158
|
+
const middleware = selectMiddleware(
|
|
159
|
+
currentIndex,
|
|
160
|
+
globalLen,
|
|
161
|
+
pathLen,
|
|
162
|
+
globalMw,
|
|
163
|
+
pathMw,
|
|
164
|
+
routeHandlers
|
|
165
|
+
)
|
|
134
166
|
|
|
135
167
|
try {
|
|
136
|
-
const result =
|
|
137
|
-
if (
|
|
138
|
-
|
|
168
|
+
const result = middleware(req, res, handleNext)
|
|
169
|
+
if (
|
|
170
|
+
result !== null &&
|
|
171
|
+
result !== undefined &&
|
|
172
|
+
typeof result.then === 'function'
|
|
173
|
+
) {
|
|
174
|
+
result.then(undefined, handleNext)
|
|
139
175
|
}
|
|
140
176
|
} catch (err) {
|
|
141
|
-
|
|
177
|
+
handleNext(err)
|
|
142
178
|
}
|
|
143
179
|
return
|
|
144
180
|
}
|
|
145
181
|
|
|
146
182
|
if (res.writableEnded) return
|
|
183
|
+
if (next !== undefined) return next()
|
|
184
|
+
finalhandler(undefined, req, res, handleNext)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
executeLoop()
|
|
188
|
+
}
|
|
147
189
|
|
|
148
|
-
|
|
190
|
+
handler.use = (path = '/', ...fns) => {
|
|
191
|
+
if (typeof path === 'function' || typeof path === 'boolean') {
|
|
192
|
+
const middlewares = [path, ...fns].filter(Boolean)
|
|
193
|
+
for (let i = 0; i < middlewares.length; i++) {
|
|
194
|
+
globalMiddlewares.push(middlewares[i])
|
|
195
|
+
}
|
|
196
|
+
} else if (path === '/') {
|
|
197
|
+
const middlewares = fns.filter(Boolean)
|
|
198
|
+
for (let i = 0; i < middlewares.length; i++) {
|
|
199
|
+
globalMiddlewares.push(middlewares[i])
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
const normalizedPath = ensureLeadingSlash(path)
|
|
203
|
+
const middlewares = fns.filter(Boolean)
|
|
204
|
+
|
|
205
|
+
if (middlewares.length > 0) {
|
|
206
|
+
let pathMiddlewares = middlewaresByPath[normalizedPath]
|
|
207
|
+
|
|
208
|
+
if (pathMiddlewares === undefined) {
|
|
209
|
+
pathMiddlewares = []
|
|
210
|
+
pathMiddlewares.push((req, _, next) => {
|
|
211
|
+
mutateRequestUrl(normalizedPath, req)
|
|
212
|
+
next()
|
|
213
|
+
})
|
|
214
|
+
middlewaresByPath[normalizedPath] = pathMiddlewares
|
|
215
|
+
}
|
|
149
216
|
|
|
150
|
-
|
|
217
|
+
for (let i = 0; i < middlewares.length; i++) {
|
|
218
|
+
pathMiddlewares.push(middlewares[i])
|
|
219
|
+
}
|
|
220
|
+
}
|
|
151
221
|
}
|
|
222
|
+
return handler
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
handler.all = addRoute.bind(null, '')
|
|
152
226
|
|
|
153
|
-
|
|
227
|
+
for (let i = 0; i < HTTP_METHODS.length; i++) {
|
|
228
|
+
const method = HTTP_METHODS[i]
|
|
229
|
+
handler[method] = addRoute.bind(null, method)
|
|
154
230
|
}
|
|
155
|
-
}
|
|
156
231
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
232
|
+
handler.del = handler.delete
|
|
233
|
+
|
|
234
|
+
handler.prettyPrint = (...args) => router.prettyPrint(...args)
|
|
235
|
+
|
|
236
|
+
Object.defineProperty(handler, 'routes', {
|
|
237
|
+
get: () => router.routes,
|
|
238
|
+
enumerable: true
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
return handler
|
|
161
242
|
}
|