Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 35 additions & 19 deletions lib/next.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,58 @@
/**
* Optimized middleware executor
*
* @param {Array} middlewares - Array of middleware functions
* @param {Object} req - Request object
* @param {Object} res - Response object
* @param {Number} index - Current middleware index
* @param {Object} routers - Router patterns map
* @param {Function} defaultRoute - Default route handler
* @param {Function} errorHandler - Error handler
* @returns {*} Result of middleware execution
*/
function next (middlewares, req, res, index, routers, defaultRoute, errorHandler) {
routers = routers || {}

// Fast path for end of middleware chain
if (index >= middlewares.length) {
if (!res.finished) {
return defaultRoute(req, res)
}

return
// Only call defaultRoute if response is not finished
return !res.finished && defaultRoute(req, res)
}

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

function step (err) {
if (err) {
return errorHandler(err, req, res)
} else {
return next(middlewares, req, res, index, routers, defaultRoute, errorHandler)
}
// Create step function - this is called by middleware to continue the chain
const step = function (err) {
return err
? errorHandler(err, req, res)
: next(middlewares, req, res, index, routers, defaultRoute, errorHandler)
}

try {
// Check if middleware is a router (has id)
if (middleware.id) {
// nested routes support
const pattern = routers[middleware.id]
// Get pattern for nested router
const pattern = routers?.[middleware.id]

if (pattern) {
// Save original URL and path
req.preRouterUrl = req.url
req.preRouterPath = req.path

// Replace pattern in URL - this is a hot path, optimize it
req.url = req.url.replace(pattern, '')
if (req.url.charCodeAt(0) !== 47) {
req.url = '\u002f'.concat(req.url)

// Ensure URL starts with a slash
if (req.url.length === 0 || req.url.charCodeAt(0) !== 47) { // 47 is '/'
req.url = '/' + req.url
}
}

// Call router's lookup method
return middleware.lookup(req, res, step)
} else {
return middleware(req, res, step)
}

// Regular middleware function
return middleware(req, res, step)
} catch (err) {
return errorHandler(err, req, res)
}
Expand Down
129 changes: 75 additions & 54 deletions lib/router/sequential.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,43 @@ const { parse } = require('regexparam')
const { LRUCache: Cache } = require('lru-cache')
const queryparams = require('../utils/queryparams')

// Default handlers as constants to avoid creating functions on each router instance
const DEFAULT_ROUTE = (req, res) => {
res.statusCode = 404
res.end()
}

const DEFAULT_ERROR_HANDLER = (err, req, res) => {
res.statusCode = 500
res.end(err.message)
}

// Simple ID generator
const generateId = () => Math.random().toString(36).substring(2, 10).toUpperCase()

module.exports = (config = {}) => {
if (config.defaultRoute === undefined) {
config.defaultRoute = (req, res) => {
res.statusCode = 404
res.end()
}
}
if (config.errorHandler === undefined) {
config.errorHandler = (err, req, res) => {
res.statusCode = 500
res.end(err.message)
}
}
if (config.cacheSize === undefined) {
config.cacheSize = -1
}
if (config.id === undefined) {
config.id = (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toUpperCase()
}
// Use object destructuring with defaults for cleaner config initialization
const {
defaultRoute = DEFAULT_ROUTE,
errorHandler = DEFAULT_ERROR_HANDLER,
cacheSize = -1,
id = generateId()
} = config

const routers = {}

// Initialize cache only once
let cache = null
if (config.cacheSize > 0) {
cache = new Cache({ max: config.cacheSize })
} else if (config.cacheSize < 0) {
cache = new Map()
if (cacheSize > 0) {
cache = new Cache({ max: cacheSize })
} else if (cacheSize < 0) {
// For unlimited cache, still use LRUCache but with a very high max
// This provides better memory management than an unbounded Map
cache = new Cache({ max: 100000 })
}

const router = new Trouter()
router.id = config.id
router.id = id

const _use = router.use

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

if (middlewares[0].id) {
if (middlewares[0]?.id) {
// caching router -> pattern relation for urls pattern replacement
const { pattern } = parse(prefix, true)
routers[middlewares[0].id] = pattern
}

return this
return router // Fix: return router instead of this
}

// Create the cleanup middleware once
const createCleanupMiddleware = (step) => (req, res, next) => {
req.url = req.preRouterUrl
req.path = req.preRouterPath

req.preRouterUrl = undefined
req.preRouterPath = undefined

return step()
}

router.lookup = (req, res, step) => {
if (!req.url) {
req.url = '/'
}
if (!req.originalUrl) {
req.originalUrl = req.url
}
// Initialize URL and originalUrl if needed
req.url = req.url || '/'
req.originalUrl = req.originalUrl || req.url

// Parse query parameters
queryparams(req, req.url)

let match
if (cache) {
const reqCacheKey = req.method + req.path
match = cache.get(reqCacheKey)
if (!match) {
match = router.find(req.method, req.path)
// Fast path for cache lookup
const reqCacheKey = cache && (req.method + req.path)
let match = cache && cache.get(reqCacheKey)

if (!match) {
match = router.find(req.method, req.path)
if (cache && reqCacheKey) {
cache.set(reqCacheKey, match)
}
} else {
match = router.find(req.method, req.path)
}

if (match.handlers.length > 0) {
const middlewares = [...match.handlers]
if (step !== undefined) {
// router is being used as a nested router
middlewares.push((req, res, next) => {
req.url = req.preRouterUrl
req.path = req.preRouterPath
const { handlers, params } = match

req.preRouterUrl = undefined
req.preRouterPath = undefined
if (handlers.length > 0) {
// Avoid creating a new array with spread operator
// Use the handlers array directly
let middlewares

return step()
})
if (step !== undefined) {
// Only create a new array if we need to add the cleanup middleware
middlewares = handlers.slice()
middlewares.push(createCleanupMiddleware(step))
} else {
middlewares = handlers
}

// Initialize params object if needed
if (!req.params) {
req.params = {}
req.params = params
} else if (params) {
// Faster than Object.assign for small objects
for (const key in params) {
req.params[key] = params[key]
}
}
Object.assign(req.params, match.params)

return next(middlewares, req, res, 0, routers, config.defaultRoute, config.errorHandler)
return next(middlewares, req, res, 0, routers, defaultRoute, errorHandler)
} else {
config.defaultRoute(req, res)
defaultRoute(req, res)
}
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"homepage": "https://github.com/BackendStack21/0http#readme",
"devDependencies": {
"0http": "^4.0.0",
"0http": "^4.1.0",
"@types/node": "^22.10.5",
"body-parser": "^1.20.1",
"chai": "^4.3.7",
Expand Down
Loading