我想编写一个小型的速率限制中间件,它能够:
- 允许我为每个远程 IP 设置合理的速率(例如,每秒10个请求)
- 可能(但不一定)允许并发请求
- 关闭超过速率限制的连接,并返回HTTP 429
然后我可以将其包装在身份验证路由或其他可能受到暴力攻击的路由周围(例如使用过期令牌的密码重置URL等)。虽然暴力破解16或24字节的令牌的机会真的很低,但采取额外的防范措施也无妨。
我已经查看了https://code.google.com/p/go-wiki/wiki/RateLimiting,但不确定如何与http.Request(s)协调。此外,我不确定如何“跟踪”来自给定IP的请求。
理想情况下,我希望得到像这样的东西,注意我在反向代理(nginx)后面,因此我们检查的是REMOTE_ADDR
HTTP标头而不是使用r.RemoteAddr
:
// Rate-limiting middleware
func rateLimit(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
remoteIP := r.Header.Get("REMOTE_ADDR")
for req := range (what here?) {
// what here?
// w.WriteHeader(429) and close the request if it exceeds the limit
// else pass to the next handler in the chain
h.ServeHTTP(w, r)
}
}
// Example routes
r.HandleFunc("/login", use(loginForm, rateLimit, csrf)
r.HandleFunc("/form", use(editHandler, rateLimit, csrf)
// Middleware wrapper, for context
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}
我希望你能在这里给我一些指导。
"SETEX",path + remoteIP,reqLimit,counter"
,其中计数器是在每个请求中检索、检查/增加并发送回Redis的整数? 但需要注意的是,在到期之前我们必须确保不会“重置”计数器。 - elithrarSETEX path+remoteIP expirationPeriod counter
,然后在每个请求上进行INCR。问题是,INCR不会重置到期时间。 - justinaspath+IP+time.Now().Second()
作为键来覆盖了这个(简化的)实现。有什么缺点吗?这似乎是一种明智的方法。另外,对于任何竞争条件还是要谨慎,虽然这可能会被Redis自身解决(?)。 - elithrarpath+IP
作为键,计数器作为值,并设置过期时间,在每个请求上进行INCR
操作。INCR
是一个原子操作,不应该存在竞争条件。 - justinasINCR path+IP
(如果键不存在,则将我们的键设置为1),检查返回值是否大于限制(返回 429 并跳过我们剩余的处理程序),否则,如果当前值等于1,则执行SETEX path+IP, 1 second, 1
(在“新鲜”键上设置到期时间),否则将其传递给链中的下一个处理程序。这样做对吗?(在此之后我就让你走了!);) - elithrar