router-http 1.0.12 → 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 +72 -22
- package/package.json +2 -1
- 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
|
|
|
@@ -110,20 +110,68 @@ 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
114
|
|
|
115
|
-
|
|
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.
|
|
116
118
|
|
|
117
119
|
```js
|
|
118
|
-
|
|
120
|
+
const createRouter = require('router-http')
|
|
121
|
+
const http = require('http')
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
123
137
|
const router = createRouter(final)
|
|
138
|
+
|
|
124
139
|
router
|
|
125
|
-
.use('/
|
|
126
|
-
.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
|
+
})
|
|
127
175
|
```
|
|
128
176
|
|
|
129
177
|
### Using the router
|
|
@@ -136,30 +184,32 @@ const server = http.createServer(router)
|
|
|
136
184
|
|
|
137
185
|
## Benchmark
|
|
138
186
|
|
|
139
|
-
|
|
187
|
+
With all the improvements, router-http is approximately 30% faster than the express router:
|
|
188
|
+
|
|
189
|
+
**express@5.2.1**
|
|
140
190
|
|
|
141
191
|
```
|
|
142
192
|
Running 30s test @ http://localhost:3000/user/123
|
|
143
193
|
8 threads and 100 connections
|
|
144
194
|
Thread Stats Avg Stdev Max +/- Stdev
|
|
145
|
-
Latency
|
|
146
|
-
Req/Sec
|
|
147
|
-
|
|
148
|
-
Requests/sec:
|
|
149
|
-
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
|
|
150
200
|
```
|
|
151
201
|
|
|
152
|
-
**router-http
|
|
202
|
+
**router-http**
|
|
153
203
|
|
|
154
204
|
```
|
|
155
205
|
Running 30s test @ http://localhost:3000/user/123
|
|
156
206
|
8 threads and 100 connections
|
|
157
207
|
Thread Stats Avg Stdev Max +/- Stdev
|
|
158
|
-
Latency 1.
|
|
159
|
-
Req/Sec
|
|
160
|
-
|
|
161
|
-
Requests/sec:
|
|
162
|
-
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
|
|
163
213
|
```
|
|
164
214
|
|
|
165
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": {
|
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
|
}
|