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。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。