go ip过滤_Go 中基于 ip 地址的速率限制 http 请求

如果您正在运行HTTP服务器并希望对端点进行速率限制请求,则可以使用维护良好的工具,例如 github.com/didip/tollbooth。但是如果你正在构建一些非常简单的东西,那么自己实现它并不困难。

已经有一个很好用的的Go包x/time/rate,我们可以使用它。

在本教程中,我们将根据用户的IP地址创建一个简单的速率限制中间件。

纯HTTP服务器

让我们从构建一个简单的HTTP服务器开始,该服务器内容很简单。但是它可能面临着大量的请求,这就是我们想在那里添加速率限制的原因。

packagemain

import(

"log"

"net/http"

)

func main(){

mux:=http.NewServeMux()

mux.HandleFunc("/",okHandler)

iferr:=http.ListenAndServe(":8888",mux);err!=nil{

log.Fatalf("unable to start server: %s",err.Error())

}

}

func okHandler(w http.ResponseWriter,r*http.Request){

// Some very expensive database call

w.Write([]byte("alles gut"))

}

在main.go我们启动服务器并且监听:8888端口。

golang.org/x/time/rate

我们将使用x/time/rateGo包,它提供令牌桶速率限制器算法。rate#Limiter控制允许事件发生的频率。它实现了一个有具体大小的“令牌桶” b,最初是满的,并以每秒的速率r令牌重新填充。非正式地,在任何足够大的时间间隔内,限制器将速率限制为每秒r个令牌,具有b个事件的最大突发大小。

由于我们希望为每个IP地址实施速率限制器,因此我们还需要维护一个限制器映射。

packagemain

import(

"sync"

"golang.org/x/time/rate"

)

// IPRateLimiter .

typeIPRateLimiterstruct{

ips map[string]*rate.Limiter

mu*sync.RWMutex

r rate.Limit

bint

}

// NewIPRateLimiter .

funcNewIPRateLimiter(r rate.Limit,bint)*IPRateLimiter{

i:=&IPRateLimiter{

ips:make(map[string]*rate.Limiter),

mu:&sync.RWMutex{},

r:r,

b:b,

}

returni

}

// AddIP creates a new rate limiter and adds it to the ips map,

// using the IP address as the key

func(i*IPRateLimiter)AddIP(ipstring)*rate.Limiter{

i.mu.Lock()

defer i.mu.Unlock()

limiter:=rate.NewLimiter(i.r,i.b)

i.ips[ip]=limiter

returnlimiter

}

// GetLimiter returns the rate limiter for the provided IP address if it exists.

// Otherwise calls AddIP to add IP address to the map

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达到限制,它将响应429太多请求,否则,它将继续请求。

在limitMiddleware函数中,Allow()每次中间件收到HTTP请求时,我们都会调用全局限制器的方法。如果存储桶中没有剩余的令牌Allow()将返回false,我们会向用户发送429 Too Many Requests响应。否则,调用Allow()将从桶中只消耗一个令牌,并将控制传递给链中的下一个处理程序。

packagemain

import(

"log"

"net/http"

)

varlimiter=NewIPRateLimiter(1,5)

func main(){

mux:=http.NewServeMux()

mux.HandleFunc("/",okHandler)

iferr:=http.ListenAndServe(":8888",limitMiddleware(mux));err!=nil{

log.Fatalf("unable to start server: %s",err.Error())

}

}

func limitMiddleware(nexthttp.Handler)http.Handler{

returnhttp.HandlerFunc(func(w http.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)

})

}

func okHandler(w http.ResponseWriter,r*http.Request){

// Some very expensive database call

w.Write([]byte("alles gut"))

}

建立和运行gogetgolang.org/x/time/rate

go build-o server.

./server

测试

有一个非常好的工具我喜欢用于HTTP负载测试,称为vegeta(也用Go编写)。

brew install vegeta

我们需要创建一个简单的配置文件,说明我们想要生成哪些请求。

GET http://localhost:8888/

然后以每个时间单位100个请求运行攻击10秒。

vegeta attack-duration=10s-rate=100-targets=vegeta.conf|vegeta report

结果你会看到一些请求返回200,但大多数返回429。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值