implement ratelimiting

This commit is contained in:
sophie 2022-09-17 23:49:38 +01:00
parent da9157fa20
commit bb195d160e
3 changed files with 63 additions and 34 deletions

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
# view count
.count.json
# rate limited ips
.ratelimit.json
# ---> Node
# Logs

View File

@ -2,6 +2,7 @@
"port": 8001,
"path": "/viewcounter",
"site": "zvava.org",
"db": ".count.json",
"countDB": ".count.json",
"ratelimitDB": ".ratelimit.json",
"saveTimeout": 5000
}

View File

@ -7,53 +7,52 @@ let config = readJson("conf.json", {
port: 8001,
path: "/viewcounter",
site: "zvava.org",
db: ".count.json",
countDB: ".count.json",
ratelimitDB: ".ratelimit.json",
saveTimeout: 5000,
})
// read existing view count
let count = readJson(config.db)
// read databases
let count = readJson(config.countDB)
let ratelimit = readJson(config.ratelimitDB)
// store the save timeout
let saveTimeout
let server = new http.Server((request, response) => {
try {
if (request.url != config.path)
return close(response, 404)
if (request.url != config.path)
return close(response, 404)
switch (request.method) {
case "POST":
const chunks = []
request.on("data", chunk => chunks.push(chunk))
request.on("end", async () => {
let page = Buffer.concat(chunks).toString()
switch (request.method) {
case "POST":
const chunks = []
request.on("data", chunk => chunks.push(chunk))
request.on("end", async () => {
let page = Buffer.concat(chunks).toString()
// check if page is a valid url
if (/\/[a-zA-Z0-9_-]+.html/.test(page) && await validateUrl(page)) {
count[page] = count[page] ? count[page] + 1 : 1
saveCount()
close(response, 200, "counted page view", { views: count[page] })
} else {
close(response, 300, "invalid url provided")
}
})
break
// check if page is a valid url
if (/\/[a-zA-Z0-9_-]+.html/.test(page) && await validateUrl(page)) {
let message = incrementCount(request.socket.remoteAddress, page)
? "counted page view" : "you have seen this page today"
close(response, 200, { message, views: count[page] })
} else {
close(response, 300, "invalid url provided")
}
})
break
case "GET": close(response, 200, count); break
case "GET": close(response, 200, count); break
default: close(response, "invalid method " + request.method)
}
} catch (e) {
console.log(e)
default: close(response, "invalid method " + request.method)
}
})
server.listen(config.port)
function saveCount() {
// if a timeout
saveTimeout && clearTimeout(saveTimeout)
clearTimeout(saveTimeout)
saveTimeout = setTimeout(() => {
fs.writeFile(config.db, JSON.stringify(count), (err) =>
fs.writeFile(config.countDB, JSON.stringify(count), (err) =>
err && console.error(err))
fs.writeFile(config.ratelimitDB, JSON.stringify(ratelimit), (err) =>
err && console.error(err))
}, config.saveTimeout)
}
@ -66,10 +65,10 @@ function readJson(file, defaultJson = {}) {
}
// helper function to close an incoming http request
function close(res, code = 300, message = "", extraData = {}) {
function close(res, code = 300, message = "") {
let data = typeof message == "string"
? { code, ...extraData, message: message || code.toString() }
: { code, ...extraData, ...message }
? { code, message: message || code.toString() }
: { code, ...message }
res.setHeader("Content-type", "application/json")
res.writeHead(code)
res.end(JSON.stringify(data))
@ -98,3 +97,30 @@ function validateUrl(url) {
}
})
}
function incrementCount(ip, page) {
if (!ip) return false // in case client disconnected
// calculate date string
let d = new Date(); d = d.getMonth() + "-" + d.getDate()
// will increment count at end
let i = false
// create ratelimit profile for the ip if it doesn't exist already
if (!ratelimit[ip]) ratelimit[ip] = {}
i = !ratelimit[ip][page]
// if last visited date doesn't exist
? true
// else, check if last visited date was today
: ratelimit[ip][page] != d
if (i) {
// update last visited day
ratelimit[ip][page] = d
// increment page count if it exists, otherwise set page count to 1
count[page] = count[page] ? count[page] + 1 : 1
saveCount()
return true
}
}