mirror of
https://github.com/pockethost/pockethost.git
synced 2025-11-23 22:15:49 +00:00
feat(pockethost): concurrency rate limits
This commit is contained in:
parent
262da3e455
commit
64d4d9c111
@ -34,6 +34,17 @@ export const createRateLimiterMiddleware = (logger: Logger) => {
|
|||||||
duration: 60 * 60,
|
duration: 60 * 60,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Concurrent request limiters
|
||||||
|
const ipConcurrentLimiter = new RateLimiterMemory({
|
||||||
|
points: 5,
|
||||||
|
duration: 0, // Duration 0 means we manually manage release
|
||||||
|
})
|
||||||
|
|
||||||
|
const hostnameConcurrentLimiter = new RateLimiterMemory({
|
||||||
|
points: 50,
|
||||||
|
duration: 0,
|
||||||
|
})
|
||||||
|
|
||||||
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
const ip = getClientIp(req)
|
const ip = getClientIp(req)
|
||||||
if (!ip) {
|
if (!ip) {
|
||||||
@ -44,6 +55,7 @@ export const createRateLimiterMiddleware = (logger: Logger) => {
|
|||||||
const hostname = req.hostname
|
const hostname = req.hostname
|
||||||
// dbg(`Request from ${ip} for host ${hostname}`)
|
// dbg(`Request from ${ip} for host ${hostname}`)
|
||||||
|
|
||||||
|
// Check rate limits first (requests per hour)
|
||||||
try {
|
try {
|
||||||
const ipResult = await ipRateLimiter.consume(ip)
|
const ipResult = await ipRateLimiter.consume(ip)
|
||||||
dbg(`IP points remaining for ${ip}: ${ipResult.remainingPoints}`)
|
dbg(`IP points remaining for ${ip}: ${ipResult.remainingPoints}`)
|
||||||
@ -51,19 +63,66 @@ export const createRateLimiterMiddleware = (logger: Logger) => {
|
|||||||
const retryAfter = Math.ceil(rateLimiterRes.msBeforeNext / 1000)
|
const retryAfter = Math.ceil(rateLimiterRes.msBeforeNext / 1000)
|
||||||
warn(`IP rate limit exceeded for ${ip} on host ${hostname}. Retry after ${retryAfter} seconds`)
|
warn(`IP rate limit exceeded for ${ip} on host ${hostname}. Retry after ${retryAfter} seconds`)
|
||||||
res.set('Retry-After', String(retryAfter))
|
res.set('Retry-After', String(retryAfter))
|
||||||
res.status(429).send(`Too Many Requests: retry after ${retryAfter} seconds`)
|
res.status(429).send(`Too Many Requests: retry after ${retryAfter} seconds [1]`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const hostnameResult = await hostnameRateLimiter.consume(hostname)
|
const hostnameResult = await hostnameRateLimiter.consume(hostname)
|
||||||
dbg(`Hostname points remaining for ${hostname}: ${hostnameResult.remainingPoints}`)
|
dbg(`Hostname points remaining for ${hostname}: ${hostnameResult.remainingPoints}`)
|
||||||
next()
|
|
||||||
} catch (rateLimiterRes: any) {
|
} catch (rateLimiterRes: any) {
|
||||||
const retryAfter = Math.ceil(rateLimiterRes.msBeforeNext / 1000)
|
const retryAfter = Math.ceil(rateLimiterRes.msBeforeNext / 1000)
|
||||||
warn(`Hostname rate limit exceeded for ${hostname} by IP ${ip}. Retry after ${retryAfter} seconds`)
|
warn(`Hostname rate limit exceeded for ${hostname} by IP ${ip}. Retry after ${retryAfter} seconds`)
|
||||||
res.set('Retry-After', String(retryAfter))
|
res.set('Retry-After', String(retryAfter))
|
||||||
res.status(429).send(`Too Many Requests: retry after ${retryAfter} seconds`)
|
res.status(429).send(`Too Many Requests: retry after ${retryAfter} seconds [2]`)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ipConcurrentConsumed = false
|
||||||
|
let hostnameConcurrentConsumed = false
|
||||||
|
|
||||||
|
// Helper to release concurrent points
|
||||||
|
const releaseConcurrentPoints = async () => {
|
||||||
|
if (ipConcurrentConsumed) {
|
||||||
|
const ipConcurrentResult = await ipConcurrentLimiter.reward(ip, 1)
|
||||||
|
dbg(`Released concurrent point for IP ${ip}. Points remaining: ${ipConcurrentResult.remainingPoints}`)
|
||||||
|
}
|
||||||
|
if (hostnameConcurrentConsumed) {
|
||||||
|
const hostnameConcurrentResult = await hostnameConcurrentLimiter.reward(hostname, 1)
|
||||||
|
dbg(
|
||||||
|
`Released concurrent point for hostname ${hostname}. Points remaining: ${hostnameConcurrentResult.remainingPoints}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check concurrent limits
|
||||||
|
try {
|
||||||
|
const ipConcurrentResult = await ipConcurrentLimiter.consume(ip)
|
||||||
|
ipConcurrentConsumed = true
|
||||||
|
dbg(`IP concurrent request accepted for ${ip}. Points remaining: ${ipConcurrentResult.remainingPoints}`)
|
||||||
|
} catch (rateLimiterRes: any) {
|
||||||
|
warn(`IP concurrent limit exceeded for ${ip} on host ${hostname}`)
|
||||||
|
res.status(429).send(`Too Many Requests: concurrent request limit exceeded [3]`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const hostnameConcurrentResult = await hostnameConcurrentLimiter.consume(hostname)
|
||||||
|
hostnameConcurrentConsumed = true
|
||||||
|
dbg(
|
||||||
|
`Hostname concurrent request accepted for ${hostname} on IP ${ip}. Points remaining: ${hostnameConcurrentResult.remainingPoints}`
|
||||||
|
)
|
||||||
|
} catch (rateLimiterRes: any) {
|
||||||
|
await releaseConcurrentPoints()
|
||||||
|
warn(`Hostname concurrent limit exceeded for ${hostname} by IP ${ip}`)
|
||||||
|
res.status(429).send(`Too Many Requests: concurrent request limit exceeded [4]`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release concurrent points when response finishes
|
||||||
|
res.on('finish', releaseConcurrentPoints)
|
||||||
|
res.on('close', releaseConcurrentPoints)
|
||||||
|
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user