首页 > 解决方案 > 如何解决 Golang WAF 服务的竞争条件?

问题描述

首先,对不起所有糟糕的英语。

我尝试在 Golang 中开发 WAF(Web 应用程序防火墙)服务。一切都map[string]*Struct{}在记忆中。当请求到来时,我将请求标头的主机设置为映射到处理函数中。

host,err := GetHost(r.Host)

func GetHost(host string) (*Host,error){
    split, _, err := net.SplitHostPort(host)
    if err == nil {
        host = split
    }
    if data, val := hosts[host]; val { 
        return data, nil
    }
    return nil,errors.New("host not found!)
}
//hosts is a map for all host, key is host and value is host struct.

问题是,当服务收到大量请求时,地图会变得混乱。例如; host isexample.comhosts["example.com"]给出了另一个无关紧要的值。

type Server struct {
    mu   sync.RWMutex
    Host *models.Host
}
func (c *Server) handler(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    mutex.Lock()
    defer mutex.Unlock()
    var err error
    c.Host, err = GetHost(r.Host)
    if err != nil {
        w.WriteHeader(http.StatusBadGateway)
        w.Write([]byte(r.Host + " not found!!"))
        return
    }
    //it's going on..Edited part

所以,我尝试使用mutexandwg来解决这个问题,但它没有用。打开任何建议。

标签: gorace-condition

解决方案


您认为您需要从 HTTP 处理程序序列化对共享状态的访问是正确的(因为服务器在专用 goroutine 中处理每个 HTTP 请求)。否则,您的程序确实会遇到同步错误,这可能会在执行过程中表现为数据竞争,正如您所经历的那样;Go 工具链提供的竞赛检测器很可能会检测到它。

可以说,序列化对该共享状态的访问的最简单方法是使用一些互斥锁。但是,您需要小心。您的延迟调用mutex.Unlock是有问题的,至少有一个,可能有两个原因:

  1. 一般来说,您应该努力使关键部分Lock(代码中被调用and包围的部分Unlock)尽可能地“小”和“便宜”。简而言之,临界区应该只做内存处理,而不是 I/O 的东西。在这里,在每个请求的整个处理过程中都需要持有锁,这可能会导致您的服务器出现大量争用。
  2. 尽管您在处理程序中省略了代码的结尾,但我猜(?)您稍后也会获取锁以更新映射(如果之前没有遇到当前请求的主机)。但是,由于包导出的互斥锁类型都不sync是可重入的,因此您可能会遇到死锁:由于调用Unlock被延迟,互斥锁只会在您的处理程序终止时被释放。

一种解决方案是避开defer和限制对GetHost函数的调用的关键部分。

另一个改进是消除全局状态,以获得更好的可测试性等。您可以通过简单地将地图走私存储在结构的字段中并声明为 on 的方法来使hosts地图成为非全局的。ServerGetHost*Server

type Server struct {
    mu   sync.RWMutex
    Host *models.Host
    hosts map[string]*Host
}

func (srv *Server) GetHost(host string) (*Host, error){
    split, _, err := net.SplitHostPort(host)
    if err == nil {
        host = split
    }
    if data, exists := srv.hosts[host]; exists { 
        return data, nil
    }
    return nil, errors.New("host not found!")
}

func (srv *Server) handler(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        mutex.Lock()
        srv.Host, err := srv.GetHost(r.Host)
        mutex.Unlock()
        if err != nil {
            w.WriteHeader(http.StatusBadGateway)
            w.Write([]byte(r.Host + " not found!!"))
            return
        }
        // possibly acquire and release the lock again
        // for further treatment of the hosts map
    })
}

我可能遗漏了一些东西,但必须承认我没有看到为每个请求更新结构Host字段的意义......Server


推荐阅读