Comparing version 1.2.4 to 2.0.0
@@ -8,3 +8,3 @@ const sequential = require('../lib/router/sequential') | ||
router.on('GET', '/hi', (req, res) => { | ||
router.get('/hi', (req, res) => { | ||
res.end('Hello World!') | ||
@@ -11,0 +11,0 @@ }) |
const cero = require('./../index') | ||
const { router, server } = cero() | ||
router.on('GET', '/hi', (req, res) => { | ||
router.get('/hi', (req, res) => { | ||
res.end('Hello World!') | ||
@@ -7,0 +6,0 @@ }) |
const http = require('http') | ||
module.exports = (config = {}) => { | ||
const router = config.router || require('find-my-way')() | ||
const router = config.router || require('./lib/router/sequential')() | ||
const server = config.server || http.createServer() | ||
@@ -6,0 +6,0 @@ |
@@ -1,6 +0,6 @@ | ||
function next (middlewares, req, res, middlewareIndex = 0) { | ||
const middleware = middlewares[middlewareIndex] | ||
function next (middlewares, req, res, index, routerPatterns = {}, defaultRoute) { | ||
const middleware = middlewares[index] | ||
if (!middleware) { | ||
if (!res.finished) { | ||
return res.end() | ||
return defaultRoute(req, res) | ||
} | ||
@@ -12,9 +12,19 @@ | ||
function step (err) { | ||
if (err) return err | ||
return next(middlewares, req, res, ++middlewareIndex) | ||
if (err) throw err | ||
return next(middlewares, req, res, ++index, routerPatterns, defaultRoute) | ||
} | ||
return middleware(req, res, step) | ||
if (middleware.id) { | ||
// nested routes support | ||
const pattern = routerPatterns[middleware.id] | ||
if (pattern) { | ||
req.preRouterUrl = req.url | ||
req.url = req.url.replace(pattern, '') | ||
} | ||
middleware.lookup(req, res, step) | ||
} else { | ||
return middleware(req, res, step) | ||
} | ||
} | ||
module.exports = next |
const Trouter = require('trouter') | ||
const next = require('./../next') | ||
const LRU = require('lru-cache') | ||
@@ -9,14 +10,47 @@ module.exports = (config = {}) => { | ||
}) | ||
config.cacheSize = config.cacheSize || 1000 | ||
config.id = config.id || (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toUpperCase() | ||
const r = new Trouter() | ||
let routersPattern = null | ||
const cache = new LRU(config.cacheSize) | ||
const router = new Trouter() | ||
router.id = config.id | ||
r.lookup = (req, res) => { | ||
req.originalUrl = req.url | ||
router.lookup = (req, res, step) => { | ||
if (req.originalUrl === undefined) { | ||
req.originalUrl = req.originalUrl || req.url | ||
} | ||
const match = r.find(req.method, req.url) | ||
req.params = match.params | ||
const reqCacheKey = `${req.method + req.url}` | ||
let match = cache.get(reqCacheKey) | ||
if (!match) { | ||
match = router.find(req.method, req.url) | ||
cache.set(reqCacheKey, match) | ||
} | ||
const middlewares = match.handlers.length | ||
if (middlewares > 0) { | ||
next(match.handlers, req, res) | ||
if (match.handlers.length) { | ||
if (routersPattern === null) { | ||
routersPattern = {} | ||
// caching router -> pattern relation for urls pattern replacement | ||
router.routes.forEach(route => route.handlers.forEach(h => { | ||
if (h.id) { | ||
routersPattern[h.id] = route.pattern | ||
} | ||
})) | ||
} | ||
const middlewares = [...match.handlers] | ||
if (step) { | ||
// router is being used as a nested router | ||
middlewares.push((req, res, next) => { | ||
req.url = req.preRouterUrl | ||
delete req.preRouterUrl | ||
step() | ||
}) | ||
} | ||
// middlewares invocation | ||
req.params = Object.assign(req.params || {}, match.params) | ||
next(middlewares, req, res, 0, routersPattern, config.defaultRoute) | ||
} else { | ||
@@ -27,5 +61,5 @@ config.defaultRoute(req, res) | ||
r.on = (method, pattern, ...handlers) => r.add(method, pattern, handlers) | ||
router.on = (method, pattern, ...handlers) => router.add(method, pattern, handlers) | ||
return r | ||
return router | ||
} |
@@ -74,11 +74,7 @@ const uWS = require('uWebSockets.js') | ||
this.req = uRequest | ||
this.url = uRequest.getUrl() | ||
this.originalUrl = this.url | ||
this.method = uRequest.getMethod().toUpperCase() | ||
this.body = null | ||
this.headers = {} | ||
this.headers = {} | ||
uRequest.forEach((k, v) => { | ||
@@ -85,0 +81,0 @@ this.headers[k] = v |
{ | ||
"name": "0http", | ||
"version": "1.2.4", | ||
"version": "2.0.0", | ||
"description": "Cero friction HTTP request router. The need for speed!", | ||
@@ -9,3 +9,3 @@ "main": "index.js", | ||
"format": "npx standard --fix", | ||
"test": "PORT=3000 NODE_ENV=testing npx nyc --check-coverage --lines 85 node ./node_modules/mocha/bin/mocha tests.js" | ||
"test": "PORT=3000 NODE_ENV=testing npx nyc --check-coverage --lines 85 node ./node_modules/mocha/bin/mocha tests/*.test.js" | ||
}, | ||
@@ -30,4 +30,5 @@ "repository": { | ||
"chai": "^4.2.0", | ||
"mocha": "^6.2.0", | ||
"nyc": "^14.1.1", | ||
"find-my-way": "^2.2.1", | ||
"mocha": "^6.2.2", | ||
"nyc": "^15.0.0", | ||
"standard": "^14.3.1", | ||
@@ -38,5 +39,5 @@ "supertest": "^4.0.2", | ||
"dependencies": { | ||
"find-my-way": "^2.1.0", | ||
"lru-cache": "^5.1.1", | ||
"trouter": "^3.1.0" | ||
} | ||
} |
118
README.md
# 0http | ||
Cero friction HTTP framework: | ||
- Tweaked Node.js Server for high throughput. | ||
- The request router you like. | ||
- Use the request router you like. | ||
> If no router is provided, it uses the `find-my-way` router as default implementation. | ||
## Usage | ||
@@ -13,7 +11,7 @@ ```js | ||
router.on('GET', '/hello', (req, res) => { | ||
router.get('/hello', (req, res) => { | ||
res.end('Hello World!') | ||
}) | ||
router.on('POST', '/do', (req, res) => { | ||
router.post('/do', (req, res) => { | ||
// ... | ||
@@ -34,12 +32,8 @@ res.statusCode = 201 | ||
``` | ||
### find-my-way router | ||
> https://github.com/delvedor/find-my-way | ||
This is the default router in `0http` if no router is provided via configuration. Internally uses a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) | ||
router that will bring better performance over iterative regular expressions matching. | ||
### 0http - sequential (default router) | ||
This a `0http` extended implementation of the [trouter](https://www.npmjs.com/package/trouter) router. Includes support for middlewares, nested routers and shortcuts for routes registration. | ||
As this is an iterative regular expression matching router, it tends to be slower than `find-my-way` when the number of registered routes increases; to mitigate this issue, we use | ||
an internal LRU cache to store the matching results of the previous requests, resulting on a super-fast matching process. | ||
### 0http - sequential | ||
This a `0http` extended implementation of the [trouter](https://www.npmjs.com/package/trouter) router. Includes support for middlewares and shortcuts for routes registration. | ||
As this is an iterative regular expression matching router, it tends to be slower than `find-my-way` when the number of registered routes increases. However, tiny micro-services should not see major performance degradation. | ||
Supported HTTP verbs: `GET, HEAD, PATCH, OPTIONS, CONNECT, DELETE, TRACE, POST, PUT` | ||
@@ -49,6 +43,5 @@ | ||
const cero = require('0http') | ||
const { router, server } = cero({ | ||
router: require('0http/lib/router/sequential')() | ||
}) | ||
const { router, server } = cero({}) | ||
// global middleware example | ||
router.use('/', (req, res, next) => { | ||
@@ -59,2 +52,3 @@ res.write('Hello ') | ||
// route middleware example | ||
const routeMiddleware = (req, res, next) => { | ||
@@ -64,2 +58,4 @@ res.write('World') | ||
} | ||
// GET /sayhi route with middleware and handler | ||
router.get('/sayhi', routeMiddleware, (req, res) => { | ||
@@ -71,4 +67,14 @@ res.end('!') | ||
``` | ||
#### Configuration Options | ||
- **defaultRoute**: Route handler when there is no router matching. Default value: | ||
```js | ||
(req, res) => { | ||
res.statusCode = 404 | ||
res.end() | ||
} | ||
``` | ||
- **cacheSize**: Router matching LRU cache size. Default value: `1000` | ||
#### Async middlewares | ||
You can user async middlewares to await the remaining chain execution: | ||
You can user async middlewares to await the remaining chain execution. Let's describe with a custom error handler middleware: | ||
```js | ||
@@ -84,6 +90,41 @@ router.use('/', async (req, res, next) => { | ||
router.get('/sayhi', () => { throw new Error('Uuuups!') }, (req, res) => { | ||
res.end('!') | ||
router.get('/sayhi', (req, res) => { | ||
throw new Error('Uuuups!') | ||
}) | ||
``` | ||
#### Nested Routers | ||
You can simply use `sequential` router intances as nested routers: | ||
```js | ||
const cero = require('../index') | ||
const { router, server } = cero({}) | ||
const nested = require('0http/lib/router/sequential')() | ||
nested.get('/url', (req, res, next) => { | ||
res.end(req.url) | ||
}) | ||
router.use('/v1', nested) | ||
server.listen(3000) | ||
``` | ||
### find-my-way router | ||
> https://github.com/delvedor/find-my-way | ||
Super-fast raw HTTP router with no goodies. Internally uses a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) | ||
router that will bring better performance over iterative regular expressions matching. | ||
```js | ||
const cero = require('../index') | ||
const { router, server } = cero({ | ||
router: require('find-my-way')() | ||
}) | ||
router.on('GET', '/hi', (req, res) => { | ||
res.end('Hello World!') | ||
}) | ||
server.listen(3000) | ||
``` | ||
## Servers | ||
@@ -121,3 +162,3 @@ `0http` is just a wrapper for the servers and routers implementations you provide. | ||
router.on('GET', '/hi', (req, res) => { | ||
router.get('/hi', (req, res) => { | ||
res.end('Hello World!') | ||
@@ -137,31 +178,30 @@ }) | ||
## Benchmarks (22/07/2019) | ||
**Node version**: v10.16.0 | ||
**Laptop**: MacBook Pro 2016, 2,7 GHz Intel Core i7, 16 GB 2133 MHz LPDDR3 | ||
## Benchmarks (30/12/2019) | ||
**Node version**: v12.14.0 | ||
**Laptop**: MacBook Pro 2019, 2,4 GHz Intel Core i9, 32 GB 2400 MHz DDR4 | ||
**Server**: Single instance | ||
```bash | ||
wrk -t8 -c8 -d5s http://127.0.0.1:3000/hi | ||
wrk -t8 -c40 -d5s http://127.0.0.1:3000/hi | ||
``` | ||
### 1 route registered | ||
- **0http (find-my-way + low)** | ||
`Requests/sec: 121006.70` | ||
- 0http (find-my-way) | ||
`Requests/sec: 68101.15` | ||
- 0http (sequential) | ||
`Requests/sec: 67124.65` | ||
- restana v3.3.1 | ||
`Requests/sec: 59519.98` | ||
- **0http (find-my-way + low)** | ||
`Requests/sec: 135436.99` | ||
- 0http (sequential + low) | ||
`Requests/sec: 134281.32` | ||
- 0http (sequential) | ||
`Requests/sec: 88438.69` | ||
- 0http (find-my-way) | ||
`Requests/sec: 87597.44` | ||
- restana v3.4.2 | ||
`Requests/sec: 73455.97` | ||
### 5 routes registered | ||
- 0http (find-my-way) | ||
`Requests/sec: 68067.34` | ||
- 0http (sequential) | ||
`Requests/sec: 64141.28` | ||
- restana v3.3.1 | ||
`Requests/sec: 59501.34` | ||
- **0http (sequential)** | ||
`Requests/sec: 85839.17` | ||
- 0http (find-my-way) | ||
`Requests/sec: 82682.86` | ||
> For more accurate benchmarks please see: | ||
> - https://github.com/the-benchmarker/web-frameworks |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
22463
24
551
198
7
4
+ Addedlru-cache@^5.1.1
+ Addedlru-cache@5.1.1(transitive)
+ Addedyallist@3.1.1(transitive)
- Removedfind-my-way@^2.1.0
- Removedfast-decode-uri-component@1.0.1(transitive)
- Removedfind-my-way@2.2.5(transitive)
- Removedret@0.2.2(transitive)
- Removedsafe-regex2@2.0.0(transitive)
- Removedsemver-store@0.3.0(transitive)