Go语言 内存泄漏与html渲染函数

gr8qqesn  于 2023-08-01  发布在  Go
关注(0)|答案(1)|浏览(121)

**我面临的问题是,即使只尝试200个请求也会导致程序吃掉容器的6GB内存,最终被OOM杀死。**想法是我提取HTML中存在的所有文本节点,然后处理它们以提取它们的名称,该标签的HTML和文本。因此,为了生成特定标签的html,我使用了www.example.com的Render函数golang.org/x/net/html。在该函数中,我提供了strings.Builder作为io.Writer来编写生成的html。但是由于某种原因,builder占用了太多的内存。

package main

import (
    "encoding/csv"
    "io"
    "log"
    "net/http"
    "strings"
    "golang.org/x/net/html"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/data", GetData)
    if err := http.ListenAndServe(":8001", mux); err != nil {
        log.Println(err)
    }
}

type TagInfo struct {
    Tag  string
    Name string
    Text string
}

// http.handler
func GetData(w http.ResponseWriter, r *http.Request) {
    u := r.URL.Query().Get("url")
    doc, err := GetDoc(u)
    if err != nil {
        log.Println(err)
        w.WriteHeader(500)
        return
    }
    var buf strings.Builder
    data := Extract(doc, &buf)
    csvw := csv.NewWriter(io.Discard)
    for _, d := range data {
        csvw.Write([]string{d.Name, d.Tag, d.Text})
    }
}

// fires request and get text/html
func GetDoc(u string) (*html.Node, error) {
    res, err := http.Get(u)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    return html.Parse(res.Body)
}

func Extract(doc *html.Node, buf *strings.Builder) []TagInfo {
    var (
        tags = make([]TagInfo, 0, 100)
        f    func(*html.Node)
    )

    f = func(n *html.Node) {
        if n.Type == html.TextNode {
            text := strings.TrimSpace(n.Data)
            if text != "" {
                parent := n.Parent
                tag := Render(parent, buf)
                tagInfo := TagInfo{
                    Tag:  tag,
                    Name: parent.Data,
                    Text: n.Data,
                }
                tags = append(tags, tagInfo)
            }
        }
        for child := n.FirstChild; child != nil; child = child.NextSibling {
            f(child)
        }
    }
    f(doc)
    return tags
}

// Render the html around the tag
// if node is text then pass the
// parent node paramter in function
func Render(n *html.Node, buf *strings.Builder) string {
    defer buf.Reset()
    if err := html.Render(buf, n); err != nil {
        log.Println(err)
        return ""
    }
    return buf.String()
}

字符串
如果你想要特定URL列表,这里是。我一次发出大约60个请求。
我尝试了bytes.Buffersync.Pool使用bytes.Buffer,但两者都有相同的问题。使用pprof我注意到strings.Builder's WriteString方法导致了巨大的内存使用。

ddrv8njm

ddrv8njm1#

因此,这里的基本问题是接受任何content-type,这是不可接受的,因为大多数网站都需要发送text/html
问题是,即使url发送任何不代表html数据golang.org/x/net/html的内容,仍然接受它而不会抛出错误。
让我们举一个例子,其中application/pdf被返回,然后主体将包含pdf的二进制数据,html.Parse解析并不返回任何错误,这是一个奇怪的行为,思考库为抓取/抓取接受二进制数据而做的。

**解决方案是:**检查响应头,然后继续,如果只有数据是html,否则会有歧义或更高的内存使用(可能更低),但我们无法预测会发生什么。

相关问题