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.
Files changed (4) hide show
  1. package/LICENSE.md +0 -0
  2. package/README.md +157 -95
  3. package/package.json +17 -17
  4. package/src/index.js +184 -103
package/LICENSE.md CHANGED
File without changes
package/README.md CHANGED
@@ -4,117 +4,200 @@
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 (~30% more requests per second than Express).
10
- - Maintained and well tested.
11
- - Smaller (1.8 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())
107
142
  ```
108
143
 
109
- They are only will add if the condition is satisfied.
144
+ ### Starting the server
110
145
 
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.
146
+ The router is a standard request handler. Pass it to `http.createServer`:
112
147
 
113
- ### Nested routers
148
+ ```js
149
+ const http = require('http')
114
150
 
115
- You can use a router as a middleware for another router. This is useful for prefixing routes or for modularizing your application.
151
+ console.log(router.prettyPrint())
152
+ // └── / (GET)
153
+ // ├── favicon.ico (GET)
154
+ // └── user/
155
+ // └── :id (GET)
116
156
 
117
- 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.
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
- #### Exit Current 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
148
233
 
149
- You can use `next('router')` to skip all remaining handlers in the current router and pass control back to the parent router. This is useful for conditional routing, such as a "Beta" router that only handles requests for certain users:
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(final)
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('/search', (req, res) => {
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 more details, check [benchmark](/benchmark) section.
280
+ See [benchmark](/benchmark) for details.
216
281
 
217
282
  ## Related
218
283
 
219
- - [send-http](https://github.com/Kikobeats/send-http) – A `res.end` with data type detection.
220
- - [http-body](https://github.com/Kikobeats/http-body) – Parse the http.IncomingMessage body into text/json/buffer.
221
- - [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
222
287
 
223
288
  ## License
224
289
 
225
- Full credits to [Luke Edwards](https://github.com/lukeed) for writing [Polka](https://github.com/lukeed/polka) and inspired this project.
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
- > [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.13",
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
- "null-prototype-object": "~1.2.5",
33
- "trouter": "~4.0.0"
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 { Trouter } = require('trouter')
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
- * ensure input starts with '/'
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 value = x => {
16
- const y = x.indexOf('/', 1)
17
- 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
18
33
  }
19
34
 
20
- const parse = ({ url }) => {
21
- const index = url.indexOf('?', 1)
22
- if (index === -1) return { pathname: url, query: null, search: null }
23
- const search = url.substring(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)
24
41
  return {
25
- pathname: url.substring(0, index),
42
+ pathname: url.substring(0, queryIndex),
26
43
  query: search.substring(1),
27
44
  search
28
45
  }
29
46
  }
30
47
 
31
- const mutate = (str, req) => {
32
- req.url = req.url.substring(str.length) || '/'
33
- 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) || '/'
34
51
  }
35
52
 
36
- class Router extends Trouter {
37
- constructor (unhandler) {
38
- super()
39
- 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 }
40
68
  }
41
69
 
42
- /**
43
- * Middleware per all routes
44
- */
45
- #middlewares = []
46
-
47
- /**
48
- * Middleware for specific routes
49
- */
50
- #middlewaresBy = new NullProtoObj()
51
-
52
- /**
53
- * Middleware declaration, where the page is optional
54
- * .use(one)
55
- * .use('/v1', one)
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
- return this
85
+
86
+ return handler
78
87
  }
79
88
 
80
- handler = (req, res, next) => {
81
- const info = parse(req)
82
- const pathname = info.pathname
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 m = this.#middlewares
88
- const mb = this.#middlewaresBy[page]
89
- const f = route.handlers.length > 0 ? route.handlers : null
107
+ const pathSegment = getFirstPathSegment(pathname)
108
+
109
+ let route = findRoute(req.method, pathname)
90
110
 
91
- if (f) {
92
- req.params = req.params
93
- ? { ...req.params, ...route.params }
94
- : route.params
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 || info.search
100
- req.query = req.query || info.query
128
+ req.search = req.query || urlInfo.search
129
+ req.query = req.query || urlInfo.query
101
130
 
102
131
  let index = 0
103
- let sync = 0
132
+ let syncCount = 0
104
133
 
105
- const mLen = m.length
106
- const mbLen = mb ? mb.length : 0
107
- const fLen = f ? f.length : 0
108
- const total = mLen + mbLen + fLen
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 _next = err => {
139
+ const handleNext = err => {
111
140
  if (err === 'router') {
112
- if (next) return next()
113
- index = total
141
+ if (next !== undefined) return next()
142
+ index = totalMiddlewares
114
143
  err = undefined
115
144
  }
116
- if (err) return this.unhandler(err, req, res, next)
117
- if (++sync > 100) {
118
- sync = 0
119
- return setImmediate(loop)
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
- loop()
150
+ executeLoop()
122
151
  }
123
152
 
124
- const loop = () => {
125
- if (index < total) {
153
+ const executeLoop = () => {
154
+ if (index < totalMiddlewares) {
126
155
  if (res.writableEnded) return
127
- const i = index++
128
- const mware =
129
- i < mLen
130
- ? m[i]
131
- : i < mLen + mbLen
132
- ? mb[i - mLen]
133
- : f[i - mLen - mbLen]
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 = mware(req, res, _next)
137
- if (result && typeof result.then === 'function') {
138
- return result.then(undefined, _next)
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
- return _next(err)
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
- if (next) return next()
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
- this.unhandler(undefined, req, res, _next)
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
- loop()
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
- module.exports = (finalhandler = requiredFinalHandler()) => {
158
- const router = new Router(finalhandler)
159
- const handler = (req, res, next) => router.handler(req, res, next)
160
- return Object.assign(handler, router)
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
  }