elliotwutingfeng/go-fasttld
GitHub: elliotwutingfeng/go-fasttld
一个基于 Mozilla Public Suffix List 的高性能 Go 语言 eTLD 提取库,用于从 URL 中精确解析各层级域名组件。
Stars: 39 | Forks: 8
# go-fasttld
[](https://pkg.go.dev/github.com/elliotwutingfeng/go-fasttld)
[](https://goreportcard.com/report/github.com/elliotwutingfeng/go-fasttld)
[](https://coveralls.io/github/elliotwutingfeng/go-fasttld?branch=main)
[](https://github.com/avelino/awesome-go)
[](LICENSE)
## 简介
**go-fasttld** 是一个高性能的[有效顶级域名 (eTLD)](https://wiki.mozilla.org/Public_Suffix_List)提取模块,可从 [URL](https://en.wikipedia.org/wiki/URL)中提取子组件。
URL 可以包含主机名、IPv4 地址或 IPv6 地址。eTLD 的提取基于 [Mozilla Public Suffix List](http://www.publicsuffix.org)。[Mozilla Public Suffix List](http://www.publicsuffix.org) 中列出的私有域名(如 'blogspot.co.uk' 和 'sinaapp.com')也受支持。

发现了任何 Bug?请[在此报告](https://github.com/elliotwutingfeng/go-fasttld/issues)。
## 安装
```
go get github.com/elliotwutingfeng/go-fasttld
```
## 尝试 CLI
首先,构建 CLI 应用程序。
```
# 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹
make build_cli
```
然后,尝试从 URL 中提取子组件。
```
# 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹
./dist/fasttld extract https://user@a.subdomain.example.a%63.uk:5000/a/b\?id\=42
```
## 尝试示例代码
以下所有示例均可在 `examples/demo.go` 中找到。要运行演示,请执行以下命令:
```
# 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹
make demo
```
### 主机名
```
// Initialise fasttld extractor
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
// Extract URL subcomponents
url := "https://user@a.subdomain.example.a%63.uk:5000/a/b?id=42"
res, _ := extractor.Extract(fasttld.URLParams{URL: url})
// Display results
fasttld.PrintRes(url, res) // Pretty-prints res.Scheme, res.UserInfo, res.SubDomain etc.
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|-------------|---------|---------|------------------|------|------------|----------|
| https:// | user | a.subdomain | example | a%63.uk | example.a%63.uk | 5000 | /a/b?id=42 | hostname |
### IPv4 地址
```
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
url := "https://127.0.0.1:5000"
res, _ := extractor.Extract(fasttld.URLParams{URL: url})
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|-----------|-----------|--------|------------------|------|------|--------------|
| https:// | | | 127.0.0.1 | | 127.0.0.1 | 5000 | | ipv4 address |
### IPv6 地址
```
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
url := "https://[aBcD:ef01:2345:6789:aBcD:ef01:2345:6789]:5000"
res, _ := extractor.Extract(fasttld.URLParams{URL: url})
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|-----------|-----------------------------------------|--------|-----------------------------------------|------|------|--------------|
| https:// | | | aBcD:ef01:2345:6789:aBcD:ef01:2345:6789 | | aBcD:ef01:2345:6789:aBcD:ef01:2345:6789 | 5000 | | ipv6 address |
### 国际化标签分隔符
**go-fasttld** 支持以下国际化标签分隔符 (IETF RFC 3490)
| Full Stop | Ideographic Full Stop | Fullwidth Full Stop | Halfwidth Ideographic Full Stop |
|------------|-----------------------|---------------------|---------------------------------|
| U+002E `.` | U+3002 `。` | U+FF0E `.` | U+FF61 `。` |
```
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
url := "https://brb\u002ei\u3002am\uff0egoing\uff61to\uff0ebe\u3002a\uff61fk"
res, _ := extractor.Extract(fasttld.URLParams{URL: url})
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|---------------------------------------|--------|-----------|-------------------|------|------|----------|
| https:// | | brb\u002ei\u3002am\uff0egoing\uff61to | be | a\uff61fk | be\u3002a\uff61fk | | | hostname |
## Public Suffix List 选项
### 指定自定义 Public Suffix List 文件
您可以通过将 `fasttld.SuffixListParams{}` 中的 `CacheFilePath` 设置为其绝对路径,来使用自定义的 Public Suffix List 文件。
```
cacheFilePath := "/absolute/path/to/file.dat"
extractor, err := fasttld.New(fasttld.SuffixListParams{CacheFilePath: cacheFilePath})
```
### 更新默认的 Public Suffix List 缓存
每当调用 `fasttld.New` 且未在 `fasttld.SuffixListParams{}` 中指定 `CacheFilePath` 时,如果默认的 Public Suffix List 本地缓存超过 3 天,就会自动更新。您也可以使用 `Update()` 手动更新缓存。
```
// Automatic update performed if `CacheFilePath` is not specified
// and local cache is more than 3 days old
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
// Manually update local cache
if err := extractor.Update(); err != nil {
log.Println(err)
}
```
### 私有域名
根据 [Mozilla.org wiki](https://wiki.mozilla.org/Public_Suffix_List/Uses),Mozilla Public Suffix List 包含私有域名,例如 `blogspot.com` 和 `sinaapp.com`。
默认情况下,这些私有域名会被排除(即 `IncludePrivateSuffix = false`)
```
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
url := "https://google.blogspot.com"
res, _ := extractor.Extract(fasttld.URLParams{URL: url})
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|-----------|----------|--------|------------------|------|------|----------|
| https:// | | google | blogspot | com | blogspot.com | | | hostname |
您可以通过设置 `IncludePrivateSuffix = true` 来_包含_私有域名
```
extractor, _ := fasttld.New(fasttld.SuffixListParams{IncludePrivateSuffix: true})
url := "https://google.blogspot.com"
res, _ := extractor.Extract(fasttld.URLParams{URL: url})
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|-----------|--------|--------------|---------------------|------|------|----------|
| https:// | | | google | blogspot.com | google.blogspot.com | | | hostname |
## 提取选项
### 忽略子域名
您可以通过设置 `IgnoreSubDomains = true` 来忽略子域名。默认情况下,会提取子域名。
```
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
url := "https://maps.google.com"
res, _ := extractor.Extract(fasttld.URLParams{URL: url, IgnoreSubDomains: true})
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|-----------|--------|--------|------------------|------|------|----------|
| https:// | | | google | com | google.com | | | hostname |
### Punycode
默认情况下,国际化 URL 在提取之前不会转换为 Punycode。
```
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
url := "https://hello.世界.com"
res, _ := extractor.Extract(fasttld.URLParams{URL: url})
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|-----------|--------|--------|------------------|------|------|----------|
| https:// | | hello | 世界 | com | 世界.com | | | hostname |
您可以通过设置 `ConvertURLToPunyCode = true` 在提取之前将国际化 URL 转换为 [Punycode](https://en.wikipedia.org/wiki/Punycode)。
```
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
url := "https://hello.世界.com"
res, _ := extractor.Extract(fasttld.URLParams{URL: url, ConvertURLToPunyCode: true})
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|-----------|-------------|--------|------------------|------|------|----------|
| https:// | | hello | xn--rhqv96g | com | xn--rhqv96g.com | | | hostname |
## 解析错误
如果 URL 无效,`Extract()` 返回的第二个值 **error** 将不为 nil。仍然可以从返回的第一个值 **ExtractResult** 中获取部分提取的子组件。
```
extractor, _ := fasttld.New(fasttld.SuffixListParams{})
url := "https://example!.com" // invalid characters in hostname
color.New().Println("The following line should be an error message")
if res, err := extractor.Extract(fasttld.URLParams{URL: url}); err != nil {
color.New(color.FgHiRed, color.Bold).Print("Error: ")
color.New(color.FgHiWhite).Println(err)
}
fasttld.PrintRes(url, res) // Partially extracted subcomponents can still be retrieved
```
| Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType |
|----------|----------|-----------|--------|--------|------------------|------|------|----------|
| https:// | | | | | | | | |
## 测试
```
# 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹
make tests
# 或者,在没有 race detection 的情况下运行测试
# 适用于不支持 -race flag 的系统,例如 windows/386
# 参见 https://tip.golang.org/src/cmd/dist/test.go
make tests_without_race
```
## 基准测试
```
# 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹
make bench
```
### 使用的模块
| Benchmark Name | Source |
|----------------------|----------------------------------|
| GoFastTld | go-fasttld(本模块) |
| JPilloraGoTld | github.com/jpillora/go-tld |
| JoeGuoTldExtract | github.com/joeguo/tldextract |
| Mjd2021USATldExtract | github.com/mjd2021usa/tldextract |
### 结果
基准测试在 AMD Ryzen 7 5800X、Manjaro Linux 上进行。
**go-fasttld** 在处理较长的 URL 时表现尤为出色。
#### #1
https://iupac.org/iupac-announces-the-2021-top-ten-emerging-technologies-in-chemistry/
| Benchmark Name | Iterations | ns/op | B/op | allocs/op | Fastest |
|----------------------|------------|-------------|----------|-------------|--------------------|
| GoFastTld | 8037906 | 150.8 ns/op | 0 B/op | 0 allocs/op | :heavy_check_mark: |
| JPilloraGoTld | 1675113 | 716.1 ns/op | 224 B/op | 2 allocs/op | |
| JoeGuoTldExtract | 2204854 | 515.1 ns/op | 272 B/op | 5 allocs/op | |
| Mjd2021USATldExtract | 1676722 | 712.0 ns/op | 288 B/op | 6 allocs/op | |
#### #2
https://www.google.com/maps/dir/Parliament+Place,+Parliament+House+Of+Singapore,+Singapore/Parliament+St,+London,+UK/@25.2440033,33.6721455,4z/data=!3m1!4b1!4m14!4m13!1m5!1m1!1s0x31da19a0abd4d71d:0xeda26636dc4ea1dc!2m2!1d103.8504863!2d1.2891543!1m5!1m1!1s0x487604c5aaa7da5b:0xf13a2197d7e7dd26!2m2!1d-0.1260826!2d51.5017061!3e4
| Benchmark Name | Iterations | ns/op | B/op | allocs/op | Fastest |
|----------------------|------------|-------------|-----------|-------------|--------------------|
| GoFastTld | 6381516 | 181.9 ns/op | 0 B/op | 0 allocs/op | :heavy_check_mark: |
| JPilloraGoTld | 431671 | 2603 ns/op | 928 B/op | 4 allocs/op | |
| JoeGuoTldExtract | 893347 | 1176 ns/op | 1120 B/op | 6 allocs/op | |
| Mjd2021USATldExtract | 1030250 | 1165 ns/op | 1120 B/op | 6 allocs/op | |
#### #3
https://a.b.c.d.e.f.g.h.i.j.k.l.m.n.oo.pp.qqq.rrrr.ssssss.tttttttt.uuuuuuuuuuu.vvvvvvvvvvvvvvv.wwwwwwwwwwwwwwwwwwwwww.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.cc
| Benchmark Name | Iterations | ns/op | B/op | allocs/op | Fastest |
|----------------------|------------|------------|-----------|-------------|--------------------|
| GoFastTld | 833682 | 1424 ns/op | 0 B/op | 0 allocs/op | :heavy_check_mark: |
| JPilloraGoTld | 734790 | 1640 ns/op | 304 B/op | 3 allocs/op | |
| JoeGuoTldExtract | 695475 | 1452 ns/op | 1040 B/op | 5 allocs/op | |
| Mjd2021USATldExtract | 330717 | 3628 ns/op | 1904 B/op | 8 allocs/op | |
## 实现细节
### 为什么不按“.”分割并直接取最后一个元素呢?
按“.”分割并取最后一个元素的方法仅适用于像 `com` 这样简单的 eTLD,但不适用于像 `oseto.nagasaki.jp` 这样更复杂的 eTLD。
### eTLD 字典树

**go-fasttld** 将 eTLD 存储在[压缩字典树](https://en.wikipedia.org/wiki/Trie)中。
来自 [Mozilla Publicuffix List](http://www.publicsuffix.org) 的有效 eTLD 会以倒序添加到压缩字典树中。
```
Given the following eTLDs
au
nsw.edu.au
com.ac
edu.ac
gov.ac
and the example URL host `example.nsw.edu.au`
The compressed trie will be structured as follows:
START
╠═ au 🚩 ✅
║ ╚═ edu ✅
║ ╚═ nsw 🚩 ✅
╚═ ac
╠═ com 🚩
╠═ edu 🚩
╚═ gov 🚩
=== Symbol meanings ===
🚩 : path to this node is a valid eTLD
✅ : path to this node found in example URL host `example.nsw.edu.au`
```
URL 主机子组件会从右向左解析,直到找不到匹配的节点为止。在此示例中,匹配节点的路径为 `au -> edu -> nsw`。将节点倒序即可得到提取出的 eTLD `nsw.edu.au`。
## 致谢
本模块是 Python [fasttld](https://github.com/jophy/fasttld) 模块的移植版本,并进行了额外的修改,以支持从完整 URL、IPv4 地址和 IPv6 地址中提取子组件。
- [fasttld (Python)](https://github.com/jophy/fasttld)
- [tldextract (Python)](https://github.com/john-kurkowski/tldextract)
- [ICANN IDN 字符验证指南](https://www.icann.org/resources/pages/idna-protocol-2012-02-25-en)
- [IETF RFC 2396](https://www.ietf.org/rfc/rfc2396.txt)
- [IETF RFC 3490](https://www.ietf.org/rfc/rfc3490.txt)
- [IETF RFC 3986](https://www.ietf.org/rfc/rfc3986.txt)
- [IETF RFC 6874](https://www.ietf.org/rfc/rfc6874.txt)标签:eTLD提取, EVTX分析, Go, Ruby工具, SYN扫描, URL解析, 域名解析, 文档结构分析, 日志审计, 网络处理