router-http 1.0.12 → 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.
Files changed (4) hide show
  1. package/LICENSE.md +0 -0
  2. package/README.md +201 -89
  3. package/package.json +17 -16
  4. package/src/index.js +214 -100
package/LICENSE.md CHANGED
File without changes
package/README.md CHANGED
@@ -4,177 +4,289 @@
4
4
  [![Coverage Status](https://img.shields.io/coveralls/Kikobeats/router-http.svg?style=flat-square)](https://coveralls.io/github/Kikobeats/router-http)
5
5
  [![NPM Status](https://img.shields.io/npm/dm/router-http.svg?style=flat-square)](https://www.npmjs.org/package/router-http)
6
6
 
7
- A middleware style router, similar to [express@router](https://github.com/pillarjs/router), plus:
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 (x3 compared with Express).
10
- - Maintained and well tested.
11
- - Smaller (1.4 kB).
12
23
 
13
- Don't get me wrong: The original Express router is a piece of art. I used it for years and I just considered create this library after experienced a bug that never was addressed in the stable version due to the [lack of maintenance](https://github.com/pillarjs/router/pull/60).
24
+ A middleware-style router similar to [express router](https://github.com/pillarjs/router), with key advantages:
14
25
 
15
- While I was evaluating the market for finding an alternative, I found [polka](https://github.com/lukeed/polka/tree/master/packages/polka) was a good starting point for creating a replacement. This library is different from polka in that it only contains the code that is strictly necessary for routing, nothing else.
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
- ## Install
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
- $ npm install router-http --save
46
+ npm install router-http
21
47
  ```
22
48
 
23
- ## Usage
49
+ ## Getting Started
24
50
 
25
- First, you should to create a router:
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 router = createRouter((error, req, res) => {
31
- const hasError = error !== undefined
32
- res.statusCode = hasError ? error.statusCode ?? 500 : 404
33
- res.end(hasError ? error.message ?? 'Internal Server Error' : 'Not Found')
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
- The router requires a final handler that will be called if an error occurred or none of the routes match.
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
- The routes are declared using HTTP verbs:
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
- res.statusCode = 204
50
- res.end()
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
- Alternatively, you can call `.all` for associate a route for all the verbs:
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 can be declared at root level:
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('/', (req, res, next) => {
118
+ .use((req, res, next) => {
78
119
  req.timestamp = Date.now()
79
120
  next()
80
- })
121
+ })
81
122
  ```
82
123
 
83
- or for specific routes:
124
+ You can also declare middleware specific to routes:
84
125
 
85
126
  ```js
86
- /**
87
- * Declaring a middleware to execute for a certain route path.
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
- .use('/greetings', (req, res, next) => {
91
- req.greetings = 'Greetings'
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
- Also, you can declare conditional middlewares:
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' && authentication())
141
+ .use(process.env.NODE_ENV === 'production' && rateLimiter())
142
+ ```
143
+
144
+ ### Starting the server
145
+
146
+ The router is a standard request handler. Pass it to `http.createServer`:
147
+
148
+ ```js
149
+ const http = require('http')
150
+
151
+ console.log(router.prettyPrint())
152
+ // └── / (GET)
153
+ // ├── favicon.ico (GET)
154
+ // └── user/
155
+ // └── :id (GET)
156
+
157
+ http.createServer(router).listen(3000)
107
158
  ```
108
159
 
109
- They are only will add if the condition is satisfied.
160
+ ## Advanced
110
161
 
111
- Middlewares always run **before** route handlers, regardless of declaration order. This means you can declare `.use()` before or after `.get()` / `.post()` / etc., and the middleware will still execute first.
162
+ ### Request object
112
163
 
113
- ### Prefixing routes
164
+ The router adds these properties to `req`:
114
165
 
115
- In case you need you can prefix all the routes:
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:
116
180
 
117
181
  ```js
118
- routes.get('/', (req, res) => res.end('Welcome to my API!'))
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
+ ```
119
192
 
120
- /**
121
- * Prefix all routes with the API version
122
- */
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:
201
+
202
+ ```js
203
+ const createRouter = require('router-http')
204
+ const http = require('http')
205
+
206
+ const final = (err, req, res) => {
207
+ res.statusCode = err ? 500 : 404
208
+ res.end(err ? err.message : 'Not Found')
209
+ }
210
+
211
+ // 1. Create a sub-router for API v1
212
+ const v1 = createRouter(final)
213
+ v1.get('/info', (req, res) => res.end('v1 info'))
214
+
215
+ // 2. Create another sub-router for API v2
216
+ const v2 = createRouter(final)
217
+ v2.get('/info', (req, res) => res.end('v2 info'))
218
+
219
+ // 3. Create the main router and mount sub-routers
123
220
  const router = createRouter(final)
221
+
124
222
  router
125
- .use('/latest', routes)
126
- .use('/v1', routes)
223
+ .use('/v1', v1)
224
+ .use('/v2', v2)
225
+ .get('/', (req, res) => res.end('Welcome to the main router'))
226
+
227
+ http.createServer(router).listen(3000)
127
228
  ```
128
229
 
129
- ### Using the router
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
130
233
 
131
- After the router has been initialized, start using it as handler in your Node.js server:
234
+ Use `next('router')` to exit the current router and pass control back to the parent:
132
235
 
133
236
  ```js
134
- const server = http.createServer(router)
237
+ const beta = createRouter(finalHandler)
238
+
239
+ beta.use((req, res, next) => {
240
+ if (!req.isBetaTester) return next('router')
241
+ next()
242
+ })
243
+
244
+ beta.get('/feature', (req, res) => res.end('Beta feature'))
245
+
246
+ router.use('/v1', beta)
247
+ router.get('/v1/feature', (req, res) => res.end('Stable feature'))
135
248
  ```
136
249
 
137
250
  ## Benchmark
138
251
 
139
- **express@4.18.2**
252
+ With all the improvements, **router-http** is approximately 30% faster than the express router:
253
+
254
+ **express@5.2.1**
140
255
 
141
256
  ```
142
257
  Running 30s test @ http://localhost:3000/user/123
143
258
  8 threads and 100 connections
144
259
  Thread Stats Avg Stdev Max +/- Stdev
145
- Latency 4.12ms 653.26us 21.71ms 89.35%
146
- Req/Sec 2.93k 159.60 5.99k 84.75%
147
- 700421 requests in 30.06s, 102.87MB read
148
- Requests/sec: 23304.22
149
- Transfer/sec: 3.42MB
260
+ Latency 1.23ms 1.40ms 96.27ms 99.61%
261
+ Req/Sec 10.15k 615.89 11.07k 86.24%
262
+ 2430687 requests in 30.10s, 356.98MB read
263
+ Requests/sec: 80752.48
264
+ Transfer/sec: 11.86MB
150
265
  ```
151
266
 
152
- **router-http@1.0.0**
267
+ **router-http**
153
268
 
154
269
  ```
155
270
  Running 30s test @ http://localhost:3000/user/123
156
271
  8 threads and 100 connections
157
272
  Thread Stats Avg Stdev Max +/- Stdev
158
- Latency 1.33ms 690.36us 30.28ms 97.16%
159
- Req/Sec 9.27k 1.09k 11.76k 89.58%
160
- 2214097 requests in 30.02s, 276.61MB read
161
- Requests/sec: 73754.53
162
- Transfer/sec: 9.21MB
273
+ Latency 0.97ms 1.27ms 84.82ms 99.77%
274
+ Req/Sec 12.91k 1.07k 14.67k 71.51%
275
+ 3092927 requests in 30.10s, 386.40MB read
276
+ Requests/sec: 102751.65
277
+ Transfer/sec: 12.84MB
163
278
  ```
164
279
 
165
- See more details, check [benchmark](/benchmark) section.
280
+ See [benchmark](/benchmark) for details.
166
281
 
167
282
  ## Related
168
283
 
169
- - [send-http](https://github.com/Kikobeats/send-http) – A `res.end` with data type detection.
170
- - [http-body](https://github.com/Kikobeats/http-body) – Parse the http.IncomingMessage body into text/json/buffer.
171
- - [http-compression](https://github.com/Kikobeats/http-compression) – Adding compression (gzip/brotli) for your HTTP server in Node.js.
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
172
287
 
173
288
  ## License
174
289
 
175
- Full credits to [Luke Edwards](https://github.com/lukeed) for writing [Polka](https://github.com/lukeed/polka) and inspired this project.
176
-
177
- **router-http** © [Kiko Beats](https://kikobeats.com), released under the [MIT](https://github.com/Kikobeats/router-http/blob/master/LICENSE.md) License.<br>
178
- 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.
179
291
 
180
- > [kikobeats.com](https://kikobeats.com) · GitHub [Kiko Beats](https://github.com/Kikobeats) · X [@Kikobeats](https://x.com/Kikobeats)
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": "1.0.12",
5
+ "version": "2.0.0",
6
6
  "main": "src/index.js",
7
7
  "author": {
8
8
  "email": "josefrancisco.verdu@gmail.com",
@@ -29,7 +29,8 @@
29
29
  "routes"
30
30
  ],
31
31
  "dependencies": {
32
- "trouter": "~4.0.0"
32
+ "find-my-way": "~9.3.0",
33
+ "null-prototype-object": "~1.2.5"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@commitlint/cli": "latest",
@@ -54,19 +55,6 @@
54
55
  "files": [
55
56
  "src"
56
57
  ],
57
- "scripts": {
58
- "clean": "rm -rf node_modules",
59
- "contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
60
- "coverage": "c8 report --reporter=text-lcov > coverage/lcov.info",
61
- "lint": "standard",
62
- "postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)",
63
- "prerelease": "npm run contributors",
64
- "pretest": "npm run lint",
65
- "release": "standard-version -a",
66
- "release:github": "github-generate-release",
67
- "release:tags": "git push --follow-tags origin HEAD:master",
68
- "test": "c8 ava"
69
- },
70
58
  "license": "MIT",
71
59
  "commitlint": {
72
60
  "extends": [
@@ -90,5 +78,18 @@
90
78
  "simple-git-hooks": {
91
79
  "commit-msg": "npx commitlint --edit",
92
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"
93
94
  }
94
- }
95
+ }
package/src/index.js CHANGED
@@ -1,128 +1,242 @@
1
1
  'use strict'
2
2
 
3
- const { Trouter } = require('trouter')
3
+ const NullProtoObj = require('null-prototype-object')
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
4
20
 
5
21
  const requiredFinalHandler = () => {
6
22
  throw new TypeError('You should to provide a final handler')
7
23
  }
8
24
 
9
- /**
10
- * ensure input starts with '/'
11
- */
12
- const lead = route => (route.charCodeAt(0) === 47 ? route : `/${route}`)
25
+ const ensureLeadingSlash = route =>
26
+ route.charCodeAt(0) === SLASH_CHAR_CODE ? route : `/${route}`
13
27
 
14
- const value = x => {
15
- const y = x.indexOf('/', 1)
16
- return y > 1 ? x.substring(0, y) : x
28
+ const getFirstPathSegment = pathname => {
29
+ const secondSlashIndex = pathname.indexOf('/', 1)
30
+ return secondSlashIndex > 1
31
+ ? pathname.substring(0, secondSlashIndex)
32
+ : pathname
17
33
  }
18
34
 
19
- const parse = ({ url }) => {
20
- const index = url.indexOf('?', 1)
21
- const obj = { pathname: url, query: null, search: null }
22
- if (index !== -1) {
23
- obj.search = url.substring(index)
24
- obj.query = obj.search.substring(1)
25
- obj.pathname = url.substring(0, index)
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)
41
+ return {
42
+ pathname: url.substring(0, queryIndex),
43
+ query: search.substring(1),
44
+ search
26
45
  }
27
- return obj
28
46
  }
29
47
 
30
- const mutate = (str, req) => {
31
- req.url = req.url.substring(str.length) ?? '/'
32
- req.path = req.path.substring(str.length) ?? '/'
48
+ const mutateRequestUrl = (prefix, req) => {
49
+ req.url = req.url.substring(prefix.length) || '/'
50
+ req.path = req.path.substring(prefix.length) || '/'
33
51
  }
34
52
 
35
- class Router extends Trouter {
36
- constructor (unhandler) {
37
- super()
38
- this.unhandler = unhandler
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 }
39
68
  }
40
69
 
41
- /**
42
- * Middleware per all routes
43
- */
44
- #middlewares = []
45
-
46
- /**
47
- * Middleware for specific routes
48
- */
49
- #middlewaresBy = []
50
-
51
- /**
52
- * Middleware declaration, where the page is optional
53
- * .use(one)
54
- * .use('/v1', one)
55
- * .use(one, two)
56
- * .use('/v2', two)
57
- */
58
- use = (page = '/', ...fns) => {
59
- if (typeof page === 'function' || typeof page === 'boolean') {
60
- this.#middlewares = this.#middlewares.concat(page, fns).filter(Boolean)
61
- } else if (page === '/') {
62
- this.#middlewares = this.#middlewares.concat(fns).filter(Boolean)
63
- } else {
64
- page = lead(page)
65
- fns.filter(Boolean).forEach(fn => {
66
- const array = this.#middlewaresBy[page] ?? []
67
- // eslint-disable-next-line no-sequences
68
- array.length > 0 || array.push((r, _, nxt) => (mutate(page, r), nxt()))
69
- this.#middlewaresBy[page] = array.concat(fn)
70
- })
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)
71
84
  }
72
- return this
85
+
86
+ return handler
87
+ }
88
+
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]
73
100
  }
74
101
 
75
- handler = (req, res, info) => {
76
- info = info ?? parse(req)
77
- let fns = []
78
- let middlewares = this.#middlewares
79
- const route = this.find(req.method, info.pathname)
80
- const page = value((req.path = info.pathname))
81
- if (this.#middlewaresBy[page] !== undefined) {
82
- middlewares = middlewares.concat(this.#middlewaresBy[page])
102
+ const handler = (req, res, next) => {
103
+ const urlInfo = parseUrl(req)
104
+ const pathname = urlInfo.pathname
105
+ req.path = pathname
106
+
107
+ const pathSegment = getFirstPathSegment(pathname)
108
+
109
+ let route = findRoute(req.method, pathname)
110
+
111
+ if (route.handlers.length === 0 && req.method === 'HEAD') {
112
+ route = findRoute('GET', pathname)
83
113
  }
84
- if (route) {
85
- fns = route.handlers
86
- req.params = { ...req.params, ...route.params }
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
124
+ } else {
125
+ req.params = req.params || {}
87
126
  }
88
- fns.push(this.unhandler)
89
- req.search = req.query ?? info.search
90
- req.query = req.query ?? info.query
91
- // Exit if only a single function
127
+
128
+ req.search = req.query || urlInfo.search
129
+ req.query = req.query || urlInfo.query
130
+
92
131
  let index = 0
93
- let size = middlewares.length
94
- const num = fns.length
95
- if (size === index && num === 1) return fns[0](undefined, req, res)
96
-
97
- // Otherwise loop thru all middlware
98
- const next = err => (err ? this.unhandler(err, req, res, next) : loop())
99
-
100
- const loop = () =>
101
- res.writableEnded ||
102
- (index < size &&
103
- (async () => {
104
- try {
105
- const mware = middlewares[index++]
106
- const result =
107
- index === size
108
- ? mware(undefined, req, res, next)
109
- : mware(req, res, next)
110
- if (result && typeof result.then === 'function') {
111
- await result
112
- }
113
- } catch (err) {
114
- return this.unhandler(err, req, res, next)
132
+ let syncCount = 0
133
+
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
138
+
139
+ const handleNext = err => {
140
+ if (err === 'router') {
141
+ if (next !== undefined) return next()
142
+ index = totalMiddlewares
143
+ err = undefined
144
+ }
145
+ if (err !== undefined) return finalhandler(err, req, res, next)
146
+ if (++syncCount > SYNC_ITERATION_LIMIT) {
147
+ syncCount = 0
148
+ return setImmediate(executeLoop)
149
+ }
150
+ executeLoop()
151
+ }
152
+
153
+ const executeLoop = () => {
154
+ if (index < totalMiddlewares) {
155
+ if (res.writableEnded) return
156
+
157
+ const currentIndex = index++
158
+ const middleware = selectMiddleware(
159
+ currentIndex,
160
+ globalLen,
161
+ pathLen,
162
+ globalMw,
163
+ pathMw,
164
+ routeHandlers
165
+ )
166
+
167
+ try {
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)
115
175
  }
116
- })())
176
+ } catch (err) {
177
+ handleNext(err)
178
+ }
179
+ return
180
+ }
117
181
 
118
- middlewares = middlewares.concat(fns)
119
- size += num
120
- loop() // init
182
+ if (res.writableEnded) return
183
+ if (next !== undefined) return next()
184
+ finalhandler(undefined, req, res, handleNext)
185
+ }
186
+
187
+ executeLoop()
121
188
  }
122
- }
123
189
 
124
- module.exports = (finalhandler = requiredFinalHandler()) => {
125
- const router = new Router(finalhandler)
126
- const handler = (req, res) => router.handler(req, res)
127
- return Object.assign(handler, router)
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
+ }
216
+
217
+ for (let i = 0; i < middlewares.length; i++) {
218
+ pathMiddlewares.push(middlewares[i])
219
+ }
220
+ }
221
+ }
222
+ return handler
223
+ }
224
+
225
+ handler.all = addRoute.bind(null, '')
226
+
227
+ for (let i = 0; i < HTTP_METHODS.length; i++) {
228
+ const method = HTTP_METHODS[i]
229
+ handler[method] = addRoute.bind(null, method)
230
+ }
231
+
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
128
242
  }