为何Golang中x/net/html的Token().Attr空切片len返回非零值?
Golang中html.Token().Attr出现len非零但切片为空的异常原因
问题现象
使用golang.org/x/net/html库解析HTML时,出现矛盾行为:
- 直接调用
len(h.Token().Attr)返回非零值,但紧接着打印h.Token().Attr却显示为空切片 - 将
h.Token().Attr的结果保存到变量后,len()和打印内容均正常,符合预期
问题复现代码
package main import ( "fmt" "log" "net/http" "golang.org/x/net/html" ) const url = "https://google.com" func main() { resp, err := http.Get(url) if err != nil { log.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { log.Fatalf("Status code error: %d %s", resp.StatusCode, resp.Status) } h := html.NewTokenizer(resp.Body) for { if h.Next() == html.ErrorToken { break } l := len(h.Token().Attr) if l != 0 { fmt.Println("=======") fmt.Println("Length", l) // 结果大于0 fmt.Println("Attr", h.Token().Attr) // 始终为空 } } }
异常输出
======= Length 2 Attr [] typeof Attr []html.Attribute ======= Length 8 Attr [] typeof Attr []html.Attribute ======= Length 1 Attr [] typeof Attr []html.Attribute ======= Length 1 Attr [] typeof Attr []html.Attribute
Go版本
go version go1.17.7 linux/amd64
修复后的代码
package main import ( "fmt" "log" "net/http" "golang.org/x/net/html" ) const url = "https://google.com" func main() { resp, err := http.Get(url) if err != nil { log.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { log.Fatalf("Status code error: %d %s", resp.StatusCode, resp.Status) } h := html.NewTokenizer(resp.Body) for { if h.Next() == html.ErrorToken { break } attrs := h.Token().Attr // 保存结果后复用 l := len(attrs) if l != 0 { fmt.Println("=======") fmt.Println("Length", l) fmt.Println("Attr", attrs) } } }
正常输出
Length 3 Attr [{ value AJiK0e8AAAAAYtZT7PXDBRBC2BJawIxezEfmIL6Aw5Uy} { name iflsig} { type hidden}] ======= Length 4 Attr [{ class fl sblc} { align left} { nowrap } { width 25%}] ======= Length 1 Attr [{ href /advanced_search?hl=en-IN&authuser=0}] ======= Length 4 Attr [{ id gbv} { name gbv} { type hidden} { value 1}]
原因分析
该异常是由于旧版本golang.org/x/net/html库的Tokenizer.Token()方法实现导致的:
- 旧版本中,
Token()返回的Attr切片直接引用了Tokenizer内部的attrs字段(未创建副本) - Tokenizer内部的
attrs切片会被复用:每次调用Next()时,该切片会被重置长度为0,再填充当前token的属性 - 当你两次调用
h.Token().Attr时,第一次获取的是当前有效属性的引用,但第二次调用时,内部缓冲区已被复用逻辑修改,返回的引用指向了已被重置为空的切片 - 将
h.Token().Attr保存到变量后,相当于保留了当前token属性的独立引用/副本,后续内部缓冲区的变化不会影响该变量的值
后续该库的新版本已修复此问题,Token()方法会创建Attr切片的副本并返回,避免了引用内部缓冲区带来的状态不一致问题。
内容的提问来源于stack exchange,提问作者vadasambar
相关产品推荐
相关产品推荐

