首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >速率限制HTTP请求(通过http.HandlerFunc中间件)

速率限制HTTP请求(通过http.HandlerFunc中间件)
EN

Stack Overflow用户
提问于 2013-11-30 08:34:08
回答 4查看 7K关注 0票数 12

我希望编写一小部分限制速率的中间件:

  1. 允许我为每个远程IP设置一个合理的速率(例如,10 req/s)。
  2. 可能(但它不需要)允许爆发
  3. 水滴(关闭?)超过速率并返回HTTP 429的连接

然后,我可以围绕身份验证路由或其他可能容易受到野蛮攻击的路由(即使用过期令牌的密码重置URL等)进行包装。有人强行使用16或24字节标记的可能性确实很低,但这一额外步骤并不会有什么影响。

我看过https://code.google.com/p/go-wiki/wiki/RateLimiting,但不知道如何与http.Request协调。此外,我不确定我们如何在任何一段时间内“跟踪”来自给定IP的请求。

理想情况下,我会得到这样的结果,注意到我在反向代理(nginx)后面,所以我们正在检查REMOTE_ADDR header,而不是使用r.RemoteAddr

代码语言:javascript
复制
// 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
}

我希望能在这里得到一些指导。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-11-30 09:29:26

您所链接到的速率限制示例是一个通用示例。它使用range,因为它通过一个通道接收请求。

对于HTTP请求,情况就不同了,但是这里没有什么真正复杂的事情。请注意,您不会迭代一个请求通道或任何东西--您的HandlerFunc是针对每个传入请求单独调用的。

代码语言:javascript
复制
func rateLimit(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        remoteIP := r.Header.Get("REMOTE_ADDR")
        if exceededTheLimit(remoteIP) {
            w.WriteHeader(429)
            // it then returns, not passing the request down the chain
        } else {
            h.ServeHTTP(w, r);
        }
    }       
}

现在,选择存储利率限制计数器的地点取决于您。一种解决方案是使用全局映射(不要忘记安全并发访问)将IP映射到其请求计数器。但是,您必须知道这些请求是在多长时间前提出的。

塞尔吉奥建议用雷迪斯。它的关键价值性质是一个完美的适合于这样简单的结构,你可以免费获得过期。

票数 11
EN

Stack Overflow用户

发布于 2013-11-30 08:53:49

你可以用redis存储数据。这里有一个非常有用的命令,它甚至在其文档中提到了限制速率的应用程序:英格尔。Redis还将处理旧数据的清理(通过旧键过期)。

此外,由于redis是速率限制器存储,您可以使用共享此中央存储的多个前端进程。

有些人会争辩说,每次去外部处理都是很昂贵的。但是密码重置页面并不是一种绝对要求性能最好的页面。另外,如果将redis放在同一台计算机上,则延迟应该相当低。

票数 4
EN

Stack Overflow用户

发布于 2016-06-18 15:29:01

今天早上我做了一些简单的类似的事情,我认为这会对你的案子有所帮助。

代码语言:javascript
复制
package main

import (
    "log"
    "net/http"
    "strings"
    "time"
)

func main() {
    fs := http.FileServer(http.Dir("./html/"))
    http.Handle("/", fs)
    log.Println("Listening..")
    go clearLastRequestsIPs()
    go clearBlockedIPs()
    err := http.ListenAndServe(":8080", middleware(nil))
    if err != nil {
        log.Fatalln(err)
    }
}

// Stores last requests IPs
var lastRequestsIPs []string

// Block IP for 6 hours
var blockedIPs []string

func middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ipAddr := strings.Split(r.RemoteAddr, ":")[0]
        if existsBlockedIP(ipAddr) {
            http.Error(w, "", http.StatusTooManyRequests)
            return
        }
        // how many requests the current IP made in last 5 mins
        requestCounter := 0
        for _, ip := range lastRequestsIPs {
            if ip == ipAddr {
                requestCounter++
            }
        }
        if requestCounter >= 1000 {
            blockedIPs = append(blockedIPs, ipAddr)
            http.Error(w, "", http.StatusTooManyRequests)
            return
        }
        lastRequestsIPs = append(lastRequestsIPs, ipAddr)

        // Don't cut the chain of middlewares
        if next == nil {
            http.DefaultServeMux.ServeHTTP(w, r)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func existsBlockedIP(ipAddr string) bool {
    for _, ip := range blockedIPs {
        if ip == ipAddr {
            return true
        }
    }
    return false
}

func existsLastRequest(ipAddr string) bool {
    for _, ip := range lastRequestsIPs {
        if ip == ipAddr {
            return true
        }
    }
    return false
}

// Clears lastRequestsIPs array every 5 mins
func clearLastRequestsIPs() {
    for {
        lastRequestsIPs = []string{}
        time.Sleep(time.Minute * 5)
    }
}

// Clears blockedIPs array every 6 hours
func clearBlockedIPs() {
    for {
        blockedIPs = []string{}
        time.Sleep(time.Hour * 6)
    }
}

它仍然不精确,然而,它将有助于作为一个简单的例子,速率限制。您可以通过添加请求的路径、http方法甚至身份验证来改进它,以此作为决定流是否是攻击的因素。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20298220

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档