Go 如何基于IP限制HTTP访问频率的方法实现
如果你运行HTTP服务,并且希望限制HTTP的访问频率,那么你可以借助一些比较稳定的工具,例如:github.com/didip/tollbooth。不过如果你构建的应用比较简单,也可以自己来实现。
我们可以使用一个现有的Go包x/time/rate。
本课程,我们将创建一个简单的中间件实现基于IP限制HTTP访问频率。
简单的HTTP服务
让我们从创建一个简单的HTTP服务开始,它有非常简单的终端。但是,因为它的访问频率可能非常高,因此我们要为它添加频率限制。
packagemain
import(
"log"
"net/http"
)
funcmain(){
mux:=http.NewServeMux()
mux.HandleFunc("/",okHandler)
iferr:=http.ListenAndServe(":8888",mux);err!=nil{
log.Fatalf("unabletostartserver:%s",err.Error())
}
}
funcokHandler(whttp.ResponseWriter,r*http.Request){
//某些消耗很高的数据库请求
w.Write([]byte("allesgut"))
}
通过main.go我们启动服务,监听:8888端口,这样我们就有了一个简单的终端/。
golang.org/x/time/rate
我们将使用名为x/time/rate的Go包,它提供了一个令牌桶速率限制器算法。rate#Limiter控制允许事件发生的频率。它实现了一个大小为b的「令牌桶」,最初是满的,并以每秒r的速度重新填充令牌。通俗地讲,就是在任何足够大的时间间隔内,限制器将速率限制为每秒r个令牌,最大突发大小为b个事件。
由于我们希望实现每个IP地址的速率限制器,我们还需要维护一个限制器映射。
packagemain
import(
"sync"
"golang.org/x/time/rate"
)
//IPRateLimiter.
typeIPRateLimiterstruct{
ipsmap[string]*rate.Limiter
mu*sync.RWMutex
rrate.Limit
bint
}
//NewIPRateLimiter.
funcNewIPRateLimiter(rrate.Limit,bint)*IPRateLimiter{
i:=&IPRateLimiter{
ips:make(map[string]*rate.Limiter),
mu:&sync.RWMutex{},
r:r,
b:b,
}
returni
}
//AddIP创建了一个新的速率限制器,并将其添加到ips映射中,
//使用IP地址作为密钥
func(i*IPRateLimiter)AddIP(ipstring)*rate.Limiter{
i.mu.Lock()
deferi.mu.Unlock()
limiter:=rate.NewLimiter(i.r,i.b)
i.ips[ip]=limiter
returnlimiter
}
//GetLimiter返回所提供的IP地址的速率限制器(如果存在的话).
//否则调用AddIP将IP地址添加到映射中
func(i*IPRateLimiter)GetLimiter(ipstring)*rate.Limiter{
i.mu.Lock()
limiter,exists:=i.ips[ip]
if!exists{
i.mu.Unlock()
returni.AddIP(ip)
}
i.mu.Unlock()
returnlimiter
}
NewIPRateLimiter创建一个IP限制器实例,HTTP服务器必须调用这个实例的GetLimiter来获得指定IP的限制器(从映射或生成一个新的)。
中间件
让我们升级的HTTP服务并将中间件添加到所有端点,如果IP达到限制,它将响应429TooManyRequests,否则,它将继续该请求。
每一个经过中间件的请求,我们都会调用limitMiddleware函数中的全局方法Allow()。如果存储桶中没有令牌了,该方法会返回false,该请求会收到429TooManyRequests的响应。否则Allow()方法将消耗一个令牌,并将请求传递给下一个程序。
packagemain
import(
"log"
"net/http"
)
varlimiter=NewIPRateLimiter(1,5)
funcmain(){
mux:=http.NewServeMux()
mux.HandleFunc("/",okHandler)
iferr:=http.ListenAndServe(":8888",limitMiddleware(mux));err!=nil{
log.Fatalf("unabletostartserver:%s",err.Error())
}
}
funclimitMiddleware(nexthttp.Handler)http.Handler{
returnhttp.HandlerFunc(func(whttp.ResponseWriter,r*http.Request){
limiter:=limiter.GetLimiter(r.RemoteAddr)
if!limiter.Allow(){
http.Error(w,http.StatusText(http.StatusTooManyRequests),http.StatusTooManyRequests)
return
}
next.ServeHTTP(w,r)
})
}
funcokHandler(whttp.ResponseWriter,r*http.Request){
//非常重要的数据请求(译者注:这句话没理解到位)
w.Write([]byte("allesgut"))
}
编译&执行
gogetgolang.org/x/time/rate gobuild-oserver. ./server
测试
这是我喜欢使用的一个非常好的来进行HTTP负载测试的工具,它叫做vegeta(它也是用Go编写的)。
brewinstallvegeta
我们需要创建一个简单的配置文件,来展示我们希望生成的请求。
GEThttp://localhost:8888/
然后运行攻击10秒,每个时间单位100个请求。
vegetaattack-duration=10s-rate=100-targets=vegeta.conf|vegetareport
结果,您将看到一些请求返回了200,但是大多数都返回了429。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。