router-http 1.0.11 → 1.0.13
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/README.md +74 -22
- package/package.json +3 -6
- package/src/index.js +87 -54
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
A middleware style router, similar to [express@router](https://github.com/pillarjs/router), plus:
|
|
8
8
|
|
|
9
|
-
- Faster (
|
|
9
|
+
- Faster (~30% more requests per second than Express).
|
|
10
10
|
- Maintained and well tested.
|
|
11
|
-
- Smaller (1.
|
|
11
|
+
- Smaller (1.8 kB).
|
|
12
12
|
|
|
13
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).
|
|
14
14
|
|
|
@@ -108,20 +108,70 @@ router
|
|
|
108
108
|
|
|
109
109
|
They are only will add if the condition is satisfied.
|
|
110
110
|
|
|
111
|
-
|
|
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.
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
### Nested routers
|
|
114
|
+
|
|
115
|
+
You can use a router as a middleware for another router. This is useful for prefixing routes or for modularizing your application.
|
|
116
|
+
|
|
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.
|
|
114
118
|
|
|
115
119
|
```js
|
|
116
|
-
|
|
120
|
+
const createRouter = require('router-http')
|
|
121
|
+
const http = require('http')
|
|
117
122
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
const final = (err, req, res) => {
|
|
124
|
+
res.statusCode = err ? 500 : 404
|
|
125
|
+
res.end(err ? err.message : 'Not Found')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 1. Create a sub-router for API v1
|
|
129
|
+
const v1 = createRouter(final)
|
|
130
|
+
v1.get('/info', (req, res) => res.end('v1 info'))
|
|
131
|
+
|
|
132
|
+
// 2. Create another sub-router for API v2
|
|
133
|
+
const v2 = createRouter(final)
|
|
134
|
+
v2.get('/info', (req, res) => res.end('v2 info'))
|
|
135
|
+
|
|
136
|
+
// 3. Create the main router and mount sub-routers
|
|
121
137
|
const router = createRouter(final)
|
|
138
|
+
|
|
122
139
|
router
|
|
123
|
-
.use('/
|
|
124
|
-
.use('/
|
|
140
|
+
.use('/v1', v1)
|
|
141
|
+
.use('/v2', v2)
|
|
142
|
+
.get('/', (req, res) => res.end('Welcome to the main router'))
|
|
143
|
+
|
|
144
|
+
http.createServer(router).listen(3000)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Exit Current Router
|
|
148
|
+
|
|
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:
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
const beta = createRouter(final)
|
|
153
|
+
|
|
154
|
+
// Middleware to check if the user is a beta tester
|
|
155
|
+
beta.use((req, res, next) => {
|
|
156
|
+
if (!req.isBetaTester) return next('router')
|
|
157
|
+
next()
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
beta.get('/search', (req, res) => {
|
|
161
|
+
res.end('Using the new AI-powered search engine!')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const router = createRouter(final)
|
|
165
|
+
|
|
166
|
+
// Mount the beta router
|
|
167
|
+
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
|
+
})
|
|
125
175
|
```
|
|
126
176
|
|
|
127
177
|
### Using the router
|
|
@@ -134,30 +184,32 @@ const server = http.createServer(router)
|
|
|
134
184
|
|
|
135
185
|
## Benchmark
|
|
136
186
|
|
|
137
|
-
|
|
187
|
+
With all the improvements, router-http is approximately 30% faster than the express router:
|
|
188
|
+
|
|
189
|
+
**express@5.2.1**
|
|
138
190
|
|
|
139
191
|
```
|
|
140
192
|
Running 30s test @ http://localhost:3000/user/123
|
|
141
193
|
8 threads and 100 connections
|
|
142
194
|
Thread Stats Avg Stdev Max +/- Stdev
|
|
143
|
-
Latency
|
|
144
|
-
Req/Sec
|
|
145
|
-
|
|
146
|
-
Requests/sec:
|
|
147
|
-
Transfer/sec:
|
|
195
|
+
Latency 1.23ms 1.40ms 96.27ms 99.61%
|
|
196
|
+
Req/Sec 10.15k 615.89 11.07k 86.24%
|
|
197
|
+
2430687 requests in 30.10s, 356.98MB read
|
|
198
|
+
Requests/sec: 80752.48
|
|
199
|
+
Transfer/sec: 11.86MB
|
|
148
200
|
```
|
|
149
201
|
|
|
150
|
-
**router-http
|
|
202
|
+
**router-http**
|
|
151
203
|
|
|
152
204
|
```
|
|
153
205
|
Running 30s test @ http://localhost:3000/user/123
|
|
154
206
|
8 threads and 100 connections
|
|
155
207
|
Thread Stats Avg Stdev Max +/- Stdev
|
|
156
|
-
Latency 1.
|
|
157
|
-
Req/Sec
|
|
158
|
-
|
|
159
|
-
Requests/sec:
|
|
160
|
-
Transfer/sec:
|
|
208
|
+
Latency 0.97ms 1.27ms 84.82ms 99.77%
|
|
209
|
+
Req/Sec 12.91k 1.07k 14.67k 71.51%
|
|
210
|
+
3092927 requests in 30.10s, 386.40MB read
|
|
211
|
+
Requests/sec: 102751.65
|
|
212
|
+
Transfer/sec: 12.84MB
|
|
161
213
|
```
|
|
162
214
|
|
|
163
215
|
See more details, check [benchmark](/benchmark) section.
|
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.
|
|
5
|
+
"version": "1.0.13",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"author": {
|
|
8
8
|
"email": "josefrancisco.verdu@gmail.com",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"routes"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"null-prototype-object": "~1.2.5",
|
|
32
33
|
"trouter": "~4.0.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
@@ -46,7 +47,6 @@
|
|
|
46
47
|
"nano-staged": "latest",
|
|
47
48
|
"simple-git-hooks": "latest",
|
|
48
49
|
"standard": "latest",
|
|
49
|
-
"standard-markdown": "latest",
|
|
50
50
|
"standard-version": "latest"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"clean": "rm -rf node_modules",
|
|
60
60
|
"contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
|
|
61
61
|
"coverage": "c8 report --reporter=text-lcov > coverage/lcov.info",
|
|
62
|
-
"lint": "standard
|
|
62
|
+
"lint": "standard",
|
|
63
63
|
"postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)",
|
|
64
64
|
"prerelease": "npm run contributors",
|
|
65
65
|
"pretest": "npm run lint",
|
|
@@ -84,9 +84,6 @@
|
|
|
84
84
|
"prettier-standard",
|
|
85
85
|
"standard --fix"
|
|
86
86
|
],
|
|
87
|
-
"*.md": [
|
|
88
|
-
"standard-markdown"
|
|
89
|
-
],
|
|
90
87
|
"package.json": [
|
|
91
88
|
"finepack"
|
|
92
89
|
]
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const NullProtoObj = require('null-prototype-object')
|
|
3
4
|
const { Trouter } = require('trouter')
|
|
4
5
|
|
|
5
6
|
const requiredFinalHandler = () => {
|
|
@@ -18,18 +19,18 @@ const value = x => {
|
|
|
18
19
|
|
|
19
20
|
const parse = ({ url }) => {
|
|
20
21
|
const index = url.indexOf('?', 1)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
if (index === -1) return { pathname: url, query: null, search: null }
|
|
23
|
+
const search = url.substring(index)
|
|
24
|
+
return {
|
|
25
|
+
pathname: url.substring(0, index),
|
|
26
|
+
query: search.substring(1),
|
|
27
|
+
search
|
|
26
28
|
}
|
|
27
|
-
return obj
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
const mutate = (str, req) => {
|
|
31
|
-
req.url = req.url.substring(str.length)
|
|
32
|
-
req.path = req.path.substring(str.length)
|
|
32
|
+
req.url = req.url.substring(str.length) || '/'
|
|
33
|
+
req.path = req.path.substring(str.length) || '/'
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
class Router extends Trouter {
|
|
@@ -46,7 +47,7 @@ class Router extends Trouter {
|
|
|
46
47
|
/**
|
|
47
48
|
* Middleware for specific routes
|
|
48
49
|
*/
|
|
49
|
-
#middlewaresBy =
|
|
50
|
+
#middlewaresBy = new NullProtoObj()
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
53
|
* Middleware declaration, where the page is optional
|
|
@@ -64,65 +65,97 @@ class Router extends Trouter {
|
|
|
64
65
|
page = lead(page)
|
|
65
66
|
fns.filter(Boolean).forEach(fn => {
|
|
66
67
|
const array = this.#middlewaresBy[page] ?? []
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
if (array.length === 0) {
|
|
69
|
+
array.push((r, _, nxt) => {
|
|
70
|
+
mutate(page, r)
|
|
71
|
+
nxt()
|
|
72
|
+
})
|
|
73
|
+
}
|
|
69
74
|
this.#middlewaresBy[page] = array.concat(fn)
|
|
70
75
|
})
|
|
71
76
|
}
|
|
72
77
|
return this
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
handler = (req, res,
|
|
76
|
-
info =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const route = this.find(req.method,
|
|
80
|
-
const page = value(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
handler = (req, res, next) => {
|
|
81
|
+
const info = parse(req)
|
|
82
|
+
const pathname = info.pathname
|
|
83
|
+
req.path = pathname
|
|
84
|
+
const route = this.find(req.method, pathname)
|
|
85
|
+
const page = value(pathname)
|
|
86
|
+
|
|
87
|
+
const m = this.#middlewares
|
|
88
|
+
const mb = this.#middlewaresBy[page]
|
|
89
|
+
const f = route.handlers.length > 0 ? route.handlers : null
|
|
90
|
+
|
|
91
|
+
if (f) {
|
|
92
|
+
req.params = req.params
|
|
93
|
+
? { ...req.params, ...route.params }
|
|
94
|
+
: route.params
|
|
95
|
+
} else {
|
|
96
|
+
req.params = req.params || {}
|
|
87
97
|
}
|
|
88
|
-
|
|
89
|
-
req.search = req.query
|
|
90
|
-
req.query = req.query
|
|
91
|
-
|
|
98
|
+
|
|
99
|
+
req.search = req.query || info.search
|
|
100
|
+
req.query = req.query || info.query
|
|
101
|
+
|
|
92
102
|
let index = 0
|
|
93
|
-
let
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
let sync = 0
|
|
104
|
+
|
|
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
|
|
109
|
+
|
|
110
|
+
const _next = err => {
|
|
111
|
+
if (err === 'router') {
|
|
112
|
+
if (next) return next()
|
|
113
|
+
index = total
|
|
114
|
+
err = undefined
|
|
115
|
+
}
|
|
116
|
+
if (err) return this.unhandler(err, req, res, next)
|
|
117
|
+
if (++sync > 100) {
|
|
118
|
+
sync = 0
|
|
119
|
+
return setImmediate(loop)
|
|
120
|
+
}
|
|
121
|
+
loop()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const loop = () => {
|
|
125
|
+
if (index < total) {
|
|
126
|
+
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]
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const result = mware(req, res, _next)
|
|
137
|
+
if (result && typeof result.then === 'function') {
|
|
138
|
+
return result.then(undefined, _next)
|
|
115
139
|
}
|
|
116
|
-
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
return _next(err)
|
|
142
|
+
}
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (res.writableEnded) return
|
|
147
|
+
|
|
148
|
+
if (next) return next()
|
|
149
|
+
|
|
150
|
+
this.unhandler(undefined, req, res, _next)
|
|
151
|
+
}
|
|
117
152
|
|
|
118
|
-
|
|
119
|
-
size += num
|
|
120
|
-
loop() // init
|
|
153
|
+
loop()
|
|
121
154
|
}
|
|
122
155
|
}
|
|
123
156
|
|
|
124
157
|
module.exports = (finalhandler = requiredFinalHandler()) => {
|
|
125
158
|
const router = new Router(finalhandler)
|
|
126
|
-
const handler = (req, res) => router.handler(req, res)
|
|
159
|
+
const handler = (req, res, next) => router.handler(req, res, next)
|
|
127
160
|
return Object.assign(handler, router)
|
|
128
161
|
}
|