Skip to content

Commit e4d138d

Browse files
authored
Performance optimizations (#42)
* performance optimizations * increasing test coverage
1 parent 5a36840 commit e4d138d

File tree

4 files changed

+545
-74
lines changed

4 files changed

+545
-74
lines changed

lib/next.js

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,58 @@
1+
/**
2+
* Optimized middleware executor
3+
*
4+
* @param {Array} middlewares - Array of middleware functions
5+
* @param {Object} req - Request object
6+
* @param {Object} res - Response object
7+
* @param {Number} index - Current middleware index
8+
* @param {Object} routers - Router patterns map
9+
* @param {Function} defaultRoute - Default route handler
10+
* @param {Function} errorHandler - Error handler
11+
* @returns {*} Result of middleware execution
12+
*/
113
function next (middlewares, req, res, index, routers, defaultRoute, errorHandler) {
2-
routers = routers || {}
3-
14+
// Fast path for end of middleware chain
415
if (index >= middlewares.length) {
5-
if (!res.finished) {
6-
return defaultRoute(req, res)
7-
}
8-
9-
return
16+
// Only call defaultRoute if response is not finished
17+
return !res.finished && defaultRoute(req, res)
1018
}
1119

20+
// Get current middleware and increment index
1221
const middleware = middlewares[index++]
1322

14-
function step (err) {
15-
if (err) {
16-
return errorHandler(err, req, res)
17-
} else {
18-
return next(middlewares, req, res, index, routers, defaultRoute, errorHandler)
19-
}
23+
// Create step function - this is called by middleware to continue the chain
24+
const step = function (err) {
25+
return err
26+
? errorHandler(err, req, res)
27+
: next(middlewares, req, res, index, routers, defaultRoute, errorHandler)
2028
}
2129

2230
try {
31+
// Check if middleware is a router (has id)
2332
if (middleware.id) {
24-
// nested routes support
25-
const pattern = routers[middleware.id]
33+
// Get pattern for nested router
34+
const pattern = routers?.[middleware.id]
35+
2636
if (pattern) {
37+
// Save original URL and path
2738
req.preRouterUrl = req.url
2839
req.preRouterPath = req.path
2940

41+
// Replace pattern in URL - this is a hot path, optimize it
3042
req.url = req.url.replace(pattern, '')
31-
if (req.url.charCodeAt(0) !== 47) {
32-
req.url = '\u002f'.concat(req.url)
43+
44+
// Ensure URL starts with a slash
45+
if (req.url.length === 0 || req.url.charCodeAt(0) !== 47) { // 47 is '/'
46+
req.url = '/' + req.url
3347
}
3448
}
3549

50+
// Call router's lookup method
3651
return middleware.lookup(req, res, step)
37-
} else {
38-
return middleware(req, res, step)
3952
}
53+
54+
// Regular middleware function
55+
return middleware(req, res, step)
4056
} catch (err) {
4157
return errorHandler(err, req, res)
4258
}

lib/router/sequential.js

Lines changed: 75 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,43 @@ const { parse } = require('regexparam')
44
const { LRUCache: Cache } = require('lru-cache')
55
const queryparams = require('../utils/queryparams')
66

7+
// Default handlers as constants to avoid creating functions on each router instance
8+
const DEFAULT_ROUTE = (req, res) => {
9+
res.statusCode = 404
10+
res.end()
11+
}
12+
13+
const DEFAULT_ERROR_HANDLER = (err, req, res) => {
14+
res.statusCode = 500
15+
res.end(err.message)
16+
}
17+
18+
// Simple ID generator
19+
const generateId = () => Math.random().toString(36).substring(2, 10).toUpperCase()
20+
721
module.exports = (config = {}) => {
8-
if (config.defaultRoute === undefined) {
9-
config.defaultRoute = (req, res) => {
10-
res.statusCode = 404
11-
res.end()
12-
}
13-
}
14-
if (config.errorHandler === undefined) {
15-
config.errorHandler = (err, req, res) => {
16-
res.statusCode = 500
17-
res.end(err.message)
18-
}
19-
}
20-
if (config.cacheSize === undefined) {
21-
config.cacheSize = -1
22-
}
23-
if (config.id === undefined) {
24-
config.id = (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toUpperCase()
25-
}
22+
// Use object destructuring with defaults for cleaner config initialization
23+
const {
24+
defaultRoute = DEFAULT_ROUTE,
25+
errorHandler = DEFAULT_ERROR_HANDLER,
26+
cacheSize = -1,
27+
id = generateId()
28+
} = config
2629

2730
const routers = {}
31+
32+
// Initialize cache only once
2833
let cache = null
29-
if (config.cacheSize > 0) {
30-
cache = new Cache({ max: config.cacheSize })
31-
} else if (config.cacheSize < 0) {
32-
cache = new Map()
34+
if (cacheSize > 0) {
35+
cache = new Cache({ max: cacheSize })
36+
} else if (cacheSize < 0) {
37+
// For unlimited cache, still use LRUCache but with a very high max
38+
// This provides better memory management than an unbounded Map
39+
cache = new Cache({ max: 100000 })
3340
}
41+
3442
const router = new Trouter()
35-
router.id = config.id
43+
router.id = id
3644

3745
const _use = router.use
3846

@@ -43,60 +51,73 @@ module.exports = (config = {}) => {
4351
}
4452
_use.call(router, prefix, middlewares)
4553

46-
if (middlewares[0].id) {
54+
if (middlewares[0]?.id) {
4755
// caching router -> pattern relation for urls pattern replacement
4856
const { pattern } = parse(prefix, true)
4957
routers[middlewares[0].id] = pattern
5058
}
5159

52-
return this
60+
return router // Fix: return router instead of this
61+
}
62+
63+
// Create the cleanup middleware once
64+
const createCleanupMiddleware = (step) => (req, res, next) => {
65+
req.url = req.preRouterUrl
66+
req.path = req.preRouterPath
67+
68+
req.preRouterUrl = undefined
69+
req.preRouterPath = undefined
70+
71+
return step()
5372
}
5473

5574
router.lookup = (req, res, step) => {
56-
if (!req.url) {
57-
req.url = '/'
58-
}
59-
if (!req.originalUrl) {
60-
req.originalUrl = req.url
61-
}
75+
// Initialize URL and originalUrl if needed
76+
req.url = req.url || '/'
77+
req.originalUrl = req.originalUrl || req.url
6278

79+
// Parse query parameters
6380
queryparams(req, req.url)
6481

65-
let match
66-
if (cache) {
67-
const reqCacheKey = req.method + req.path
68-
match = cache.get(reqCacheKey)
69-
if (!match) {
70-
match = router.find(req.method, req.path)
82+
// Fast path for cache lookup
83+
const reqCacheKey = cache && (req.method + req.path)
84+
let match = cache && cache.get(reqCacheKey)
85+
86+
if (!match) {
87+
match = router.find(req.method, req.path)
88+
if (cache && reqCacheKey) {
7189
cache.set(reqCacheKey, match)
7290
}
73-
} else {
74-
match = router.find(req.method, req.path)
7591
}
7692

77-
if (match.handlers.length > 0) {
78-
const middlewares = [...match.handlers]
79-
if (step !== undefined) {
80-
// router is being used as a nested router
81-
middlewares.push((req, res, next) => {
82-
req.url = req.preRouterUrl
83-
req.path = req.preRouterPath
93+
const { handlers, params } = match
8494

85-
req.preRouterUrl = undefined
86-
req.preRouterPath = undefined
95+
if (handlers.length > 0) {
96+
// Avoid creating a new array with spread operator
97+
// Use the handlers array directly
98+
let middlewares
8799

88-
return step()
89-
})
100+
if (step !== undefined) {
101+
// Only create a new array if we need to add the cleanup middleware
102+
middlewares = handlers.slice()
103+
middlewares.push(createCleanupMiddleware(step))
104+
} else {
105+
middlewares = handlers
90106
}
91107

108+
// Initialize params object if needed
92109
if (!req.params) {
93-
req.params = {}
110+
req.params = params
111+
} else if (params) {
112+
// Faster than Object.assign for small objects
113+
for (const key in params) {
114+
req.params[key] = params[key]
115+
}
94116
}
95-
Object.assign(req.params, match.params)
96117

97-
return next(middlewares, req, res, 0, routers, config.defaultRoute, config.errorHandler)
118+
return next(middlewares, req, res, 0, routers, defaultRoute, errorHandler)
98119
} else {
99-
config.defaultRoute(req, res)
120+
defaultRoute(req, res)
100121
}
101122
}
102123

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
},
2929
"homepage": "https://github.com/BackendStack21/0http#readme",
3030
"devDependencies": {
31-
"0http": "^4.0.0",
31+
"0http": "^4.1.0",
3232
"@types/node": "^22.10.5",
3333
"body-parser": "^1.20.1",
3434
"chai": "^4.3.7",

0 commit comments

Comments
 (0)