使用`didip/tollbooth`限制每小时的最大请求数量。

huangapple go评论75阅读模式
英文:

Limit Max Number of Requests Per Hour with `didip/tollbooth`

问题

我是你的中文翻译助手,以下是你要翻译的内容:

我对速率限制还不熟悉,想要使用tollbooth来限制HTTP请求。

我也阅读了维基百科上的令牌桶算法页面。

对于一个简单的测试应用程序,我想要无论请求的IP是什么,都将最大并发请求数限制为10,并且基于请求的IP设置最大突发大小为3

注意:103只是为了更容易观察速率限制。

以下是我基于tollboothGitHub页面上的示例代码:

package main

import (
    "net/http"
    "time"

    "github.com/didip/tollbooth/v7"
    "github.com/didip/tollbooth/v7/limiter"
)

func main() {
    lmt := tollbooth.NewLimiter(3, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})

    http.Handle("/", tollbooth.LimitFuncHandler(lmt, HelloHandler))
    http.ListenAndServe(":8080", nil)
}

func HelloHandler(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("Hello, World!"))
}

我通过连续快速运行curl -i localhost:8080来测试代码,当超过我设置的速率限制时,确实会收到HTTP/1.1 429 Too Many Requests的错误。

以下是我的问题:

  1. 如何使用tollbooth来限制最大并发请求数,例如限制为10?这样做有意义吗?我认为有意义,因为仅基于IP进行速率限制听起来服务器仍然可能在太多IP同时访问时耗尽内存。

  2. 我的速率限制方法是否正确,或者我是否遗漏了什么?也许这是由与云中应用程序一起工作的负载均衡器更好地处理的事情?

更新: 这是基于Woody1193回答的工作代码:

package main

import (
	"net/http"
	"sync"
	"time"

	"github.com/didip/tollbooth/v7"
	"github.com/didip/tollbooth/v7/limiter"
)

func main() {
	ipLimiter := tollbooth.NewLimiter(3, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
	globalLimiter := NewConcurrentLimiter(10)

	http.Handle("/", globalLimiter.LimitConcurrentRequests(ipLimiter, HelloHandler))
	http.ListenAndServe(":8080", nil)
}

func HelloHandler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("Hello, World!"))
}

type ConcurrentLimiter struct {
	max     int
	current int
	mut     sync.Mutex
}

func NewConcurrentLimiter(limit int) *ConcurrentLimiter {
	return &ConcurrentLimiter{
		max: limit,
	}
}

func (limiter *ConcurrentLimiter) LimitConcurrentRequests(lmt *limiter.Limiter,
	handler func(http.ResponseWriter, *http.Request)) http.Handler {

	middle := func(w http.ResponseWriter, r *http.Request) {

		limiter.mut.Lock()
		maxHit := limiter.current == limiter.max

		if maxHit {
			limiter.mut.Unlock()
			http.Error(w, http.StatusText(429), http.StatusTooManyRequests)
			return
		}

		limiter.current += 1
		limiter.mut.Unlock()

		defer func() {
			limiter.mut.Lock()
			limiter.current -= 1
			limiter.mut.Unlock()
		}()

		// 没有速率限制错误,继续处理下一个处理程序。
		handler(w, r)
	}

	return tollbooth.LimitHandler(lmt, http.HandlerFunc(middle))
}
英文:

I'm new to rate limiting and want to use tollbooth to limit HTTP requests.

I also read the Token Bucket Algorithm page on Wikipedia.

For a simple test app, I want to limit the max number of concurrent requests to 10 regardless of request IP, and have a max burst size of 3 based on request IP.

NOTE: The 10 and 3 are just to make rate limiting easier to observe.

Below is my code based on the examples on tollbooth's GitHub page:

package main
import (
"net/http"
"time"
"github.com/didip/tollbooth/v7"
"github.com/didip/tollbooth/v7/limiter"
)
func main() {
lmt := tollbooth.NewLimiter(3, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
http.Handle("/", tollbooth.LimitFuncHandler(lmt, HelloHandler))
http.ListenAndServe(":8080", nil)
}
func HelloHandler(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello, World!"))
}

I test the code by running curl -i localhost:8080 several times in rapid succession, and I do get HTTP/1.1 429 Too Many Requests errors whenever I exceed the rate limit I set.

Below are my questions:

  1. How do I use tollbooth to limit max number of concurrent requests to something like 10? And does it even make sense to do so? I assume it does because rate limiting based only on IPs sounds like the server could still go out of memory when too many IPs access it at once.

  2. Am I approaching rate limiting correctly, or am I missing something? Perhaps this is something that's better handled by whatever load balancer is working with the app in the cloud?

UPDATE: Here's my working code based on Woody1193's answer:

package main
import (
"net/http"
"sync"
"time"
"github.com/didip/tollbooth/v7"
"github.com/didip/tollbooth/v7/limiter"
)
func main() {
ipLimiter := tollbooth.NewLimiter(3, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
globalLimiter := NewConcurrentLimiter(10)
http.Handle("/", globalLimiter.LimitConcurrentRequests(ipLimiter, HelloHandler))
http.ListenAndServe(":8080", nil)
}
func HelloHandler(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello, World!"))
}
type ConcurrentLimiter struct {
max     int
current int
mut     sync.Mutex
}
func NewConcurrentLimiter(limit int) *ConcurrentLimiter {
return &ConcurrentLimiter{
max: limit,
}
}
func (limiter *ConcurrentLimiter) LimitConcurrentRequests(lmt *limiter.Limiter,
handler func(http.ResponseWriter, *http.Request)) http.Handler {
middle := func(w http.ResponseWriter, r *http.Request) {
limiter.mut.Lock()
maxHit := limiter.current == limiter.max
if maxHit {
limiter.mut.Unlock()
http.Error(w, http.StatusText(429), http.StatusTooManyRequests)
return
}
limiter.current += 1
limiter.mut.Unlock()
defer func() {
limiter.mut.Lock()
limiter.current -= 1
limiter.mut.Unlock()
}()
// There's no rate-limit error, serve the next handler.
handler(w, r)
}
return tollbooth.LimitHandler(lmt, http.HandlerFunc(middle))
}

答案1

得分: 3

看起来tollbooth并不提供你所需要的功能。但是,你可以自己实现:

type ConcurrentLimiter struct {
    max int
    current int
    mut sync.Mutex
}

func NewConcurrentLimiter(limit int) *ConcurrentLimiter {
    return &ConcurrentLimiter {
        max: limit,
        mut: new(sync.Mutex),
    }
}

func (limiter *ConcurrentLimiter) LimitConcurrentRequests(lmt *limiter.Limiter, 
    next http.Handler) http.Handler {

    middle := func(w http.ResponseWriter, r *http.Request) {

        limiter.mut.Lock()
        maxHit := limiter.current == limiter.max
        if maxHit {
            limiter.mut.Unlock()
            httpError := // 在这里插入你的HTTP错误
            return
        }

        limiter.current += 1
        limiter.mut.Unlock()

        defer func() {
            limiter.mut.Lock()
            limiter.current -= 1
            limiter.mut.Unlock()
        }()

        // 没有速率限制错误,继续处理下一个处理程序
        next.ServeHTTP(w, r)
    }

    return tollbooth.LimitHandler(lmt, http.HandlerFunc(middle))
}

然后,在你的设置中可以这样做:

http.Handle("/", NewConcurrentLimiter(10).LimitConcurrentRequests(HelloHandler))

这段代码通过维护一个描述API当前处理的请求数的值,并在达到最大值时返回错误。Mutex用于确保无论并发请求如何,该值都会被更新。

由于tollbooth处理此类函数的方式(即它不作为中间件运行),我不得不将tollbooth.Limiter注入到我编写的限制器中。

英文:

It appears that tollbooth doesn't offer the functionality you're looking for. However, you can roll your own:

type ConcurrentLimiter struct {
max int
current int
mut sync.Mutex
}
func NewConcurrentLimiter(limit int) *ConcurrentLimiter {
return &ConcurrentLimiter {
max: limit,
mut: new(sync.Mutex),
}
}
func (limiter *ConcurrentLimiter) LimitConcurrentRequests(lmt *limiter.Limiter, 
next http.Handler) http.Handler {
middle := func(w http.ResponseWriter, r *http.Request) {
limiter.mut.Lock()
maxHit := limiter.current == limiter.max
if maxHit {
limiter.mut.Unlock()
httpError := // Insert your HTTP error here
return
}
limiter.current += 1
limiter.mut.Unlock()
defer func() {
limiter.mut.Lock()
limiter.current -= 1
limiter.mut.Unlock()
}()
// There's no rate-limit error, serve the next handler.
next.ServeHTTP(w, r)
}
return tollbooth.LimitHandler(lmt, http.HandlerFunc(middle))
}

Then, in your setup you can do:

http.Handle("/", NewConcurrentLimiter(10).LimitConcurrentRequests(HelloHandler))

This code works by maintaining a value describing how many requests the API is currently handling and returning an error if the maximum value has been met. The Mutex is used to ensure that the value is updated regardless of concurrent requests.

I had to inject the tollbooth.Limiter into the limiter I wrote because of the way tollbooth handles such functions (i.e. it doesn't operate as a middleware).

huangapple
  • 本文由 发表于 2022年8月22日 08:04:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/73439068.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定