Compare commits

..

51 Commits

Author SHA1 Message Date
刘祥超
2390a3ef61 版本号更改为1.2.6 2023-07-28 09:28:05 +08:00
刘祥超
ffda81715f 可以修改访问未绑定域名时的状态码 2023-07-27 11:21:35 +08:00
刘祥超
f991700031 未绑定域名页面提示、访问节点IP显示自定义内容支持变量 2023-07-27 10:50:18 +08:00
刘祥超
e1ba6a90ff 增加重载一组网站事件 2023-07-27 10:37:16 +08:00
刘祥超
354161037b 优化升级程序 2023-07-27 10:03:29 +08:00
刘祥超
00d28df3ee 优化本地数据库关闭时提示 2023-07-27 10:03:18 +08:00
刘祥超
23abed0949 改进缓存相关错误提示 2023-07-26 19:12:02 +08:00
刘祥超
2acf01dcb7 刷新/预热缓存任务可以并行处理 2023-07-26 18:45:17 +08:00
刘祥超
470c6a8b0e 版本号更改为1.2.5 2023-07-26 15:29:49 +08:00
刘祥超
efc2810d1d 修复分区内容长度判断错误的问题 2023-07-26 14:48:07 +08:00
刘祥超
de2374577f 增加硬盘速度检测命令:edge-node disk speed 2023-07-26 11:22:15 +08:00
刘祥超
2a1f949c13 版本号修改为1.2.3 2023-07-25 13:18:06 +08:00
刘祥超
959e274063 调整关闭连接后的Linger值 2023-07-25 09:36:45 +08:00
刘祥超
b6a2bd37b1 去除连接中的Linger设置,防止有些反向代理在数据未发送前关闭连接 2023-07-24 19:22:22 +08:00
刘祥超
3e60c9913a 优化TOA 2023-07-24 10:01:38 +08:00
刘祥超
fd7f3f4029 取消HTTP源站传递TOA(因为此时源站连接是可以重用的) 2023-07-23 18:57:16 +08:00
刘祥超
2705a5d444 修复HTTP传输时可能无法传递TOA的问题 2023-07-23 18:49:50 +08:00
刘祥超
556055cfcb 优化代码 2023-07-22 14:51:17 +08:00
刘祥超
67a0d06944 缓存条件一些无法匹配的情况在X-Cache中也增加详情 2023-07-20 16:42:54 +08:00
刘祥超
a16d8f1afa 版本号更改为1.2.2 2023-07-18 14:31:47 +08:00
刘祥超
1bab7bfcba 优化对未知长度内容的缓存长度限制 2023-07-18 12:45:25 +08:00
刘祥超
5928875623 优化代码 2023-07-18 11:21:39 +08:00
刘祥超
734cf81ff0 增加测试用例 2023-07-17 20:31:50 +08:00
刘祥超
de8c2e13f1 修复清理内存缓存内容后无法写入新缓存的问题(一直提示the file is writing) 2023-07-17 09:29:59 +08:00
刘祥超
0742dc963d 修复OSS stub 2023-07-15 17:19:08 +08:00
刘祥超
1fdce3ef7e 优化计数器 2023-07-15 11:08:25 +08:00
刘祥超
2079b0ebee 区域封禁增加延时返回 2023-07-14 16:03:58 +08:00
刘祥超
c706f2fdf1 WAF-区域封禁增加提示内容设置 2023-07-14 11:02:20 +08:00
刘祥超
bd3247668d 增加cc使用的若干通用扩展名 2023-07-13 16:20:46 +08:00
刘祥超
73024fe38c 实现新的计数器算法(将时间分片, 统计更加精准) 2023-07-13 15:37:08 +08:00
刘祥超
db520858b3 集群设置--网站设置增加“服务器旗标”设置 2023-07-12 17:39:19 +08:00
刘祥超
84c8a351a9 支持页面优化 2023-07-11 19:52:57 +08:00
刘祥超
c6c0823d30 剩余空间使用free blocks代替available blocks 2023-07-09 21:27:04 +08:00
刘祥超
1be64adb6a 版本号改为1.2.1 2023-07-09 17:38:23 +08:00
刘祥超
d0610a5001 优化缓存数据库相关代码 2023-07-08 20:00:27 +08:00
刘祥超
a2f7511a46 优化代码 2023-07-08 18:59:08 +08:00
刘祥超
6e8c886cd6 缓存策略移除“容纳Key数量”选项;缓存占用空间统计改成统计缓存目录所在文件系统 2023-07-08 18:52:57 +08:00
刘祥超
03f2130827 自定义页面中只允许使用pages/目录下文件(兼容以往版本) 2023-07-07 11:50:10 +08:00
刘祥超
9fea0749a0 优化国家/地区、省份封禁 2023-07-07 10:11:49 +08:00
刘祥超
71e0d9ce07 更新相关库 2023-07-06 10:28:28 +08:00
刘祥超
24e69028f8 “集群设置 -- 网站设置”增加“允许记录访问日志”选项 2023-07-05 15:29:26 +08:00
刘祥超
34521dbc5c 优化SSE处理/优化超时设置 2023-07-03 16:23:54 +08:00
刘祥超
2c59ae4a5b 使用自定义 executils.LookPath() 代替 exec.LookPath() 避免因$PATH环境变量被破坏而无法工作 2023-07-03 10:37:36 +08:00
刘祥超
d0bd7bb88d 提交一些公共函数 2023-07-02 17:47:59 +08:00
刘祥超
47c24b4aa8 优化OSS相关代码 2023-07-02 11:10:04 +08:00
刘祥超
4eb58a3082 优化ETag报头 2023-07-02 10:31:08 +08:00
刘祥超
c10c7cc157 优化本地数据库关闭相关代码 2023-06-23 21:50:34 +08:00
刘祥超
7fd8d7756b 优化本地数据库关闭相关代码 2023-06-23 21:32:38 +08:00
刘祥超
032c118f49 修复停止节点时无法正确保存带宽数据到本地文件的Bug 2023-06-23 18:11:27 +08:00
刘祥超
b6cab3919a 改进退出程序时关闭数据库写入 2023-06-23 17:45:39 +08:00
刘祥超
8edd30bdd8 源站支持HTTP/2 2023-06-23 11:43:02 +08:00
93 changed files with 2110 additions and 710 deletions

View File

@@ -54,10 +54,11 @@ function build() {
cp -R "$ROOT"/www "$DIST"/
cp -R "$ROOT"/pages "$DIST"/
# we support TOA on linux/amd64 only
if [ "$OS" == "linux" ] && [ "$ARCH" == "amd64" ]
# we support TOA on linux only
if [ "$OS" == "linux" ] && [ -f "${ROOT}/edge-toa/edge-toa-${ARCH}" ]
then
cp -R "$ROOT"/edge-toa "$DIST"
mkdir "$DIST/edge-toa"
cp "${ROOT}/edge-toa/edge-toa-${ARCH}" "$DIST/edge-toa/edge-toa"
fi
echo "building ..."

Binary file not shown.

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/apps"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/nodes"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
@@ -365,6 +366,29 @@ func main() {
}
fmt.Println(string(statsJSON))
})
app.On("disk", func() {
var args = os.Args[2:]
if len(args) > 0 {
switch args[0] {
case "speed":
speedMB, isFast, err := fsutils.CheckDiskIsFast()
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
fmt.Printf("Speed: %.2fMB/s\n", speedMB)
if isFast {
fmt.Println("IsFast: true")
} else {
fmt.Println("IsFast: false")
}
}
default:
fmt.Println("Usage: edge-node disk [speed]")
}
} else {
fmt.Println("Usage: edge-node disk [speed]")
}
})
app.Run(func() {
var node = nodes.NewNode()
node.Start()

53
go.mod
View File

@@ -10,50 +10,73 @@ replace (
require (
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
github.com/andybalholm/brotli v1.0.4
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
github.com/andybalholm/brotli v1.0.5
github.com/aws/aws-sdk-go v1.44.279
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
github.com/cespare/xxhash v1.1.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/fsnotify/fsnotify v1.5.1
github.com/fsnotify/fsnotify v1.6.0
github.com/go-redis/redis/v8 v8.11.5
github.com/golang/protobuf v1.5.2
github.com/golang/protobuf v1.5.3
github.com/google/nftables v0.1.0
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508
github.com/klauspost/compress v1.16.5
github.com/mattn/go-sqlite3 v1.14.9
github.com/mdlayher/netlink v1.7.1
github.com/miekg/dns v1.1.43
github.com/mssola/useragent v1.0.0
github.com/pires/go-proxyproto v0.6.1
github.com/qiniu/go-sdk/v7 v7.16.0
github.com/quic-go/quic-go v0.36.1
github.com/shirou/gopsutil/v3 v3.22.2
github.com/tencentyun/cos-go-sdk-v5 v0.7.41
golang.org/x/image v0.7.0
golang.org/x/net v0.8.0
golang.org/x/sys v0.6.0
golang.org/x/text v0.9.0
google.golang.org/grpc v1.45.0
golang.org/x/net v0.12.0
golang.org/x/sys v0.10.0
google.golang.org/grpc v1.55.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/webp v1.1.1 // indirect
github.com/clbanning/mxj v1.8.4 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mdlayher/socket v0.4.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mozillazg/go-httpheader v0.2.1 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/tdewolff/minify/v2 v2.12.7 // indirect
github.com/tdewolff/parse/v2 v2.6.6 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/sync v0.1.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.11.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/protobuf v1.30.0 // indirect
)

247
go.sum
View File

@@ -1,100 +1,98 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/aws/aws-sdk-go v1.44.279 h1:g23dxnYjIiPlQo0gIKNR0zVPsSvo1bj5frWln+5sfhk=
github.com/aws/aws-sdk-go v1.44.279/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470 h1:TuRxvKRv9PxKVijWOkUnZm5TeanQqWGUJyPx9u6cra4=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8=
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d h1:XnTIj781NdSipts60fVqbgZorVAKVSRaA6nqVNfBQ1g=
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 h1:PKtXlgNHJhdwl5ozio7KRV3n0SckMw+8ZC2NCpRSv8U=
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4/go.mod h1:DmAukmDY25inGlriLn0B2jidmvaDR1REOcPXwvzvDPI=
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5 h1:SgKhrqRhWVpu0ZKxQM8MGjdhy7hlH3Xax8snwsZWSic=
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508 h1:fjKiHAyPQmdwuw1DQ2BI1JTbhUWAtI3Kr9wIZQBdRgQ=
github.com/iwind/gowebp v0.0.0-20230615040911-5013dbb9d508/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4 h1:RPAH9Sj9l/20zH5zU5/iJGszfwPq6eLjoiC/n/asulA=
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4/go.mod h1:7OLL+86wZKfBnAJxNxmdcZ0ebbgdp/A28fcagx9oJqA=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
@@ -103,148 +101,165 @@ github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qiniu/go-sdk/v7 v7.16.0 h1:Jt4YOMLuaDfgb/KdVg0O1fYLpv5MDkYe/zV+Ri7gWRs=
github.com/qiniu/go-sdk/v7 v7.16.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.36.1 h1:WsG73nVtnDy1TiACxFxhQ3TqaW+DipmqzLEtNlAwZyY=
github.com/quic-go/quic-go v0.36.1/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4=
github.com/tencentyun/cos-go-sdk-v5 v0.7.41 h1:iU0Li/Np78H4SBna0ECQoF3mpgi6ImLXU+doGzPFXGc=
github.com/tencentyun/cos-go-sdk-v5 v0.7.41/go.mod h1:4dCEtLHGh8QPxHEkgq+nFaky7yZxQuYwgSJM87icDaw=
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg=
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -3,6 +3,7 @@ package apps
import (
"fmt"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
@@ -215,7 +216,7 @@ func (this *AppCmd) runStop() {
// 从systemd中停止
if runtime.GOOS == "linux" {
systemctl, _ := exec.LookPath("systemctl")
systemctl, _ := executils.LookPath("systemctl")
if len(systemctl) > 0 {
go func() {
// 有可能会长时间执行,这里不阻塞进程

View File

@@ -7,7 +7,7 @@ import "errors"
// 常用的几个错误
var (
ErrNotFound = errors.New("cache not found")
ErrFileIsWriting = errors.New("the file is writing")
ErrFileIsWriting = errors.New("the cache file is updating")
ErrInvalidRange = errors.New("invalid range")
ErrEntityTooLarge = errors.New("entity too large")
ErrWritingUnavailable = errors.New("writing unavailable")

View File

@@ -11,7 +11,6 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
"github.com/iwind/TeaGo/types"
"os"
"sync/atomic"
"time"
)
@@ -21,7 +20,6 @@ const CountFileDB = 20
type FileList struct {
dir string
dbList [CountFileDB]*FileListDB
total int64
onAdd func(item *Item)
onRemove func(item *Item)
@@ -76,12 +74,6 @@ func (this *FileList) Init() error {
this.dbList[i] = db
}
// 读取总数量
this.total = 0
for _, db := range this.dbList {
this.total += db.total
}
// 升级老版本数据库
goman.New(func() {
this.upgradeOldDB()
@@ -102,13 +94,11 @@ func (this *FileList) Add(hash string, item *Item) error {
return nil
}
err := db.AddAsync(hash, item)
err := db.AddSync(hash, item)
if err != nil {
return err
}
atomic.AddInt64(&this.total, 1)
// 这里不增加点击量,以减少对数据库的操作次数
this.memoryCache.Write(hash, 1, item.ExpiredAt)
@@ -296,8 +286,6 @@ func (this *FileList) CleanAll() error {
}
}
atomic.StoreInt64(&this.total, 0)
return nil
}
@@ -332,7 +320,15 @@ func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
// Count 总数量
// 常用的方法,所以避免直接查询数据库
func (this *FileList) Count() (int64, error) {
return atomic.LoadInt64(&this.total), nil
var total int64
for _, db := range this.dbList {
count, err := db.Total()
if err != nil {
return 0, err
}
total += count
}
return total, nil
}
// IncreaseHit 增加点击量
@@ -392,37 +388,19 @@ func (this *FileList) remove(hash string) (notFound bool, err error) {
// 从缓存中删除
this.memoryCache.Delete(hash)
var row = db.selectByHashStmt.QueryRow(hash)
if row.Err() != nil {
if row.Err() == sql.ErrNoRows {
return true, nil
}
return false, row.Err()
}
var item = &Item{Type: ItemTypeFile}
err = row.Scan(&item.Key, &item.HeaderSize, &item.BodySize, &item.MetaSize, &item.ExpiredAt)
if err != nil {
if err == sql.ErrNoRows {
return true, nil
}
return false, err
}
err = db.DeleteAsync(hash)
err = db.DeleteSync(hash)
if err != nil {
return false, db.WrapError(err)
}
atomic.AddInt64(&this.total, -1)
err = db.DeleteHitAsync(hash)
if err != nil {
return false, db.WrapError(err)
}
if this.onRemove != nil {
this.onRemove(item)
// when remove file item, no any extra information needed
this.onRemove(nil)
}
return false, nil

View File

@@ -35,8 +35,6 @@ type FileListDB struct {
itemsTableName string
hitsTableName string
total int64
isClosed bool
isReady bool
@@ -110,7 +108,7 @@ func (this *FileListDB) Open(dbPath string) error {
}
}
this.writeBatch = dbs.NewBatch(writeDB.RawDB(), 4)
this.writeBatch = dbs.NewBatch(writeDB, 4)
this.writeBatch.OnFail(func(err error) {
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()+" ("+filepath.Base(this.dbPath)+")")
})
@@ -151,18 +149,6 @@ func (this *FileListDB) Init() error {
return errors.New("init tables failed: " + err.Error())
}
// 读取总数量
row := this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
if row.Err() != nil {
return row.Err()
}
var total int64
err = row.Scan(&total)
if err != nil {
return err
}
this.total = total
// 常用语句
this.existsByHashStmt, err = this.readDB.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" INDEXED BY "hash" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
if err != nil {
@@ -250,20 +236,15 @@ func (this *FileListDB) IsReady() bool {
return this.isReady
}
func (this *FileListDB) Total() int64 {
return this.total
}
func (this *FileListDB) AddAsync(hash string, item *Item) error {
this.hashMap.Add(hash)
if item.StaleAt == 0 {
item.StaleAt = item.ExpiredAt
func (this *FileListDB) Total() (int64, error) {
// 读取总数量
var row = this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
if row.Err() != nil {
return 0, row.Err()
}
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
return nil
var total int64
err := row.Scan(&total)
return total, err
}
func (this *FileListDB) AddSync(hash string, item *Item) error {
@@ -281,13 +262,6 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
return nil
}
func (this *FileListDB) DeleteAsync(hash string) error {
this.hashMap.Delete(hash)
this.writeBatch.Add(this.deleteByHashSQL, hash)
return nil
}
func (this *FileListDB) DeleteSync(hash string) error {
this.hashMap.Delete(hash)
@@ -504,6 +478,10 @@ func (this *FileListDB) CleanAll() error {
}
func (this *FileListDB) Close() error {
if this.isClosed {
return nil
}
this.isClosed = true
this.isReady = false
@@ -535,10 +513,6 @@ func (this *FileListDB) Close() error {
_ = this.listOlderItemsStmt.Close()
}
if this.writeBatch != nil {
this.writeBatch.Close()
}
var errStrings []string
if this.readDB != nil {

View File

@@ -19,7 +19,7 @@ func init() {
return
}
events.On(events.EventQuit, func() {
events.OnClose(func() {
remotelogs.Println("CACHE", "quiting cache manager")
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
})

View File

@@ -52,9 +52,8 @@ func TestManager_UpdatePolicies(t *testing.T) {
},
},
{
Id: 2,
Type: serverconfigs.CachePolicyStorageFile,
MaxKeys: 1,
Id: 2,
Type: serverconfigs.CachePolicyStorageFile,
Options: map[string]interface{}{
"dir": Tea.Root + "/caches",
},
@@ -95,9 +94,9 @@ func TestManager_ChangePolicy_Memory(t *testing.T) {
func TestManager_ChangePolicy_File(t *testing.T) {
var policies = []*serverconfigs.HTTPCachePolicy{
{
Id: 1,
Type: serverconfigs.CachePolicyStorageFile,
Options: map[string]interface{}{
Id: 1,
Type: serverconfigs.CachePolicyStorageFile,
Options: map[string]interface{}{
"dir": Tea.Root + "/data/cache-index/p1",
},
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
@@ -106,9 +105,9 @@ func TestManager_ChangePolicy_File(t *testing.T) {
SharedManager.UpdatePolicies(policies)
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
{
Id: 1,
Type: serverconfigs.CachePolicyStorageFile,
Options: map[string]interface{}{
Id: 1,
Type: serverconfigs.CachePolicyStorageFile,
Options: map[string]interface{}{
"dir": Tea.Root + "/data/cache-index/p1",
},
Capacity: &shared.SizeCapacity{Count: 2, Unit: shared.SizeCapacityUnitGB},

View File

@@ -14,6 +14,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
"github.com/TeaOSLab/EdgeNode/internal/zero"
@@ -21,9 +22,6 @@ import (
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
"golang.org/x/sys/unix"
"golang.org/x/text/language"
"golang.org/x/text/message"
"math"
"os"
"path/filepath"
@@ -32,7 +30,6 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
)
@@ -53,12 +50,12 @@ const (
)
const (
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
HotItemSize = 1024 // 热点数据数量
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
FileTmpSuffix = ".tmp"
MinDiskSpace = 5 << 30 // 当前磁盘最小剩余空间
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
HotItemSize = 1024 // 热点数据数量
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
FileTmpSuffix = ".tmp"
MinDiskSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
)
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
@@ -77,7 +74,6 @@ type FileStorage struct {
policy *serverconfigs.HTTPCachePolicy
options *serverconfigs.HTTPFileCacheStorage // 二级缓存
memoryStorage *MemoryStorage // 一级缓存
totalSize int64
list ListInterface
locker sync.RWMutex
@@ -92,7 +88,6 @@ type FileStorage struct {
openFileCache *OpenFileCache
mainDir string
mainDiskIsFull bool
subDirs []*FileDir
@@ -203,6 +198,9 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
if newPolicy.PersistenceAutoPurgeInterval != this.policy.PersistenceAutoPurgeInterval {
this.initPurgeTicker()
}
// reset ignored keys
this.ignoreKeys.Reset()
}
// Init 初始化
@@ -255,19 +253,6 @@ func (this *FileStorage) Init() error {
}
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
this.list = list
stat, err := list.Stat(func(hash string) bool {
return true
})
if err != nil {
return err
}
this.totalSize = stat.Size
this.list.OnAdd(func(item *Item) {
atomic.AddInt64(&this.totalSize, item.TotalSize())
})
this.list.OnRemove(func(item *Item) {
atomic.AddInt64(&this.totalSize, -item.TotalSize())
})
// 检查目录是否存在
_, err = os.Stat(dir)
@@ -284,19 +269,17 @@ func (this *FileStorage) Init() error {
defer func() {
// 统计
var count = stat.Count
var size = stat.Size
var totalSize = this.TotalDiskSize()
var cost = time.Since(before).Seconds() * 1000
sizeMB := strconv.FormatInt(size, 10) + " Bytes"
if size > 1*sizes.G {
sizeMB = fmt.Sprintf("%.3f G", float64(size)/float64(sizes.G))
} else if size > 1*sizes.M {
sizeMB = fmt.Sprintf("%.3f M", float64(size)/float64(sizes.M))
} else if size > 1*sizes.K {
sizeMB = fmt.Sprintf("%.3f K", float64(size)/float64(sizes.K))
var sizeMB = types.String(totalSize) + " Bytes"
if totalSize > 1*sizes.G {
sizeMB = fmt.Sprintf("%.3f G", float64(totalSize)/float64(sizes.G))
} else if totalSize > 1*sizes.M {
sizeMB = fmt.Sprintf("%.3f M", float64(totalSize)/float64(sizes.M))
} else if totalSize > 1*sizes.K {
sizeMB = fmt.Sprintf("%.3f K", float64(totalSize)/float64(sizes.K))
}
remotelogs.Println("CACHE", "init policy "+strconv.FormatInt(this.policy.Id, 10)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+message.NewPrinter(language.English).Sprintf("%d", count)+", size: "+sizeMB)
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB)
}()
// 初始化list
@@ -430,14 +413,14 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
}
// 是否已忽略
if this.ignoreKeys.Has(key) {
if maxSize > 0 && this.ignoreKeys.Has(types.String(maxSize)+"$"+key) {
return nil, ErrEntityTooLarge
}
// 先尝试内存缓存
// 我们限定仅小文件优先存在内存中
var maxMemorySize = FileToMemoryMaxSize
if maxSize > maxMemorySize {
if maxSize > 0 && maxSize < maxMemorySize {
maxMemorySize = maxSize
}
var memoryStorage = this.memoryStorage
@@ -480,17 +463,10 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
}
}()
// 检查是否超出最大值
count, err := this.list.Count()
if err != nil {
return nil, err
}
if this.policy.MaxKeys > 0 && count > this.policy.MaxKeys {
return nil, NewCapacityError("write file cache failed: too many keys in cache storage")
}
// 检查是否超出容量
var capacityBytes = this.diskCapacityBytes()
if capacityBytes > 0 && capacityBytes <= this.totalSize {
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + strconv.FormatInt(this.totalSize, 10) + " bytes, capacity: " + strconv.FormatInt(capacityBytes, 10))
if capacityBytes > 0 && capacityBytes <= this.TotalDiskSize() {
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + types.String(this.TotalDiskSize()) + " bytes, capacity: " + types.String(capacityBytes))
}
var hash = stringutil.Md5(key)
@@ -669,7 +645,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
sharedWritingFileKeyLocker.Unlock()
}), nil
} else {
return NewFileWriter(this, writer, key, expiredAt, metaHeaderSize, metaBodySize, -1, func() {
return NewFileWriter(this, writer, key, expiredAt, metaHeaderSize, metaBodySize, maxSize, func() {
sharedWritingFileKeyLocker.Lock()
delete(sharedWritingFileKeyMap, key)
if len(sharedWritingFileKeyMap) == 0 {
@@ -924,7 +900,11 @@ func (this *FileStorage) Stop() {
// TotalDiskSize 消耗的磁盘尺寸
func (this *FileStorage) TotalDiskSize() int64 {
return atomic.LoadInt64(&this.totalSize)
stat, err := fsutils.StatCache(this.options.Dir)
if err == nil {
return int64(stat.UsedSize())
}
return 0
}
// TotalMemorySize 内存尺寸
@@ -937,8 +917,8 @@ func (this *FileStorage) TotalMemorySize() int64 {
}
// IgnoreKey 忽略某个Key即不缓存某个Key
func (this *FileStorage) IgnoreKey(key string) {
this.ignoreKeys.Push(key)
func (this *FileStorage) IgnoreKey(key string, maxSize int64) {
this.ignoreKeys.Push(types.String(maxSize) + "$" + key)
}
// CanSendfile 是否支持Sendfile
@@ -1364,7 +1344,6 @@ func (this *FileStorage) createMemoryStorage() error {
Name: this.policy.Name,
Description: this.policy.Description,
Capacity: this.options.MemoryPolicy.Capacity,
MaxKeys: this.policy.MaxKeys,
MaxSize: &shared.SizeCapacity{Count: 128, Unit: shared.SizeCapacityUnitMB}, // TODO 将来可以修改
Type: serverconfigs.CachePolicyStorageMemory,
Options: this.policy.Options,
@@ -1440,20 +1419,16 @@ func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStor
// 检查磁盘剩余空间
func (this *FileStorage) checkDiskSpace() {
if this.options != nil && len(this.options.Dir) > 0 {
var stat unix.Statfs_t
err := unix.Statfs(this.options.Dir, &stat)
stat, err := fsutils.Stat(this.options.Dir)
if err == nil {
var availableBytes = stat.Bavail * uint64(stat.Bsize)
this.mainDiskIsFull = availableBytes < MinDiskSpace
this.mainDiskIsFull = stat.FreeSize() < MinDiskSpace
}
}
var subDirs = this.subDirs // copy slice
for _, subDir := range subDirs {
var stat unix.Statfs_t
err := unix.Statfs(subDir.Path, &stat)
stat, err := fsutils.Stat(subDir.Path)
if err == nil {
var availableBytes = stat.Bavail * uint64(stat.Bsize)
subDir.IsFull = availableBytes < MinDiskSpace
subDir.IsFull = stat.FreeSize() < MinDiskSpace
}
}
}

View File

@@ -42,7 +42,9 @@ func TestFileStorage_Init(t *testing.T) {
time.Sleep(2 * time.Second)
storage.purgeLoop()
t.Log(storage.list.(*FileList).total, "entries left")
t.Log(storage.list.(*FileList).Stat(func(hash string) bool {
return true
}))
}
func TestFileStorage_OpenWriter(t *testing.T) {

View File

@@ -54,7 +54,7 @@ type StorageInterface interface {
AddToList(item *Item)
// IgnoreKey 忽略某个Key即不缓存某个Key
IgnoreKey(key string)
IgnoreKey(key string, maxSize int64)
// CanSendfile 是否支持Sendfile
CanSendfile() bool

View File

@@ -111,8 +111,15 @@ func (this *MemoryStorage) Init() error {
// OpenReader 读取缓存
func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool) (Reader, error) {
hash := this.hash(key)
var hash = this.hash(key)
// check if exists in list
exists, _ := this.list.Exist(types.String(hash))
if !exists {
return nil, ErrNotFound
}
// read from valuesMap
this.locker.RLock()
item := this.valuesMap[hash]
if item == nil || !item.IsDone {
@@ -121,7 +128,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
}
if useStale || (item.ExpiresAt > fasttime.Now().Unix()) {
reader := NewMemoryReader(item)
var reader = NewMemoryReader(item)
err := reader.Init()
if err != nil {
this.locker.RUnlock()
@@ -151,7 +158,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
// OpenWriter 打开缓存写入器等待写入
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
if this.ignoreKeys.Has(key) {
if maxSize > 0 && this.ignoreKeys.Has(types.String(maxSize)+"$"+key) {
return nil, ErrEntityTooLarge
}
@@ -193,20 +200,22 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
}()
// 检查是否过期
hash := this.hash(key)
var hash = this.hash(key)
item, ok := this.valuesMap[hash]
if ok && !item.IsExpired() {
return nil, ErrFileIsWriting
var hashString = types.String(hash)
exists, _ := this.list.Exist(hashString)
if !exists {
// remove from values map
delete(this.valuesMap, hash)
_ = this.list.Remove(hashString)
item = nil
} else {
return nil, ErrFileIsWriting
}
}
// 检查是否超出最大值
totalKeys, err := this.list.Count()
if err != nil {
return nil, err
}
if this.policy.MaxKeys > 0 && totalKeys > this.policy.MaxKeys {
return nil, NewCapacityError("write memory cache failed: too many keys in cache storage")
}
capacityBytes := this.memoryCapacityBytes()
if bodySize < 0 {
bodySize = 0
@@ -216,7 +225,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
}
// 先删除
err = this.deleteWithoutLocker(key)
err := this.deleteWithoutLocker(key)
if err != nil {
return nil, err
}
@@ -353,6 +362,9 @@ func (this *MemoryStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy
if newPolicy.CapacityBytes() == 0 {
_ = this.CleanAll()
}
// reset ignored keys
this.ignoreKeys.Reset()
}
// CanUpdatePolicy 检查策略是否可以更新
@@ -383,8 +395,8 @@ func (this *MemoryStorage) TotalMemorySize() int64 {
}
// IgnoreKey 忽略某个Key即不缓存某个Key
func (this *MemoryStorage) IgnoreKey(key string) {
this.ignoreKeys.Push(key)
func (this *MemoryStorage) IgnoreKey(key string, maxSize int64) {
this.ignoreKeys.Push(types.String(maxSize) + "$" + key)
}
// CanSendfile 是否支持Sendfile

View File

@@ -79,7 +79,7 @@ func (this *FileWriter) Write(data []byte) (n int, err error) {
err = ErrEntityTooLarge
if this.storage != nil {
this.storage.IgnoreKey(this.key)
this.storage.IgnoreKey(this.key, this.maxSize)
}
}

View File

@@ -59,7 +59,7 @@ func (this *MemoryWriter) Write(data []byte) (n int, err error) {
// 检查尺寸
if this.maxSize > 0 && this.bodySize > this.maxSize {
err = ErrEntityTooLarge
this.storage.IgnoreKey(this.key)
this.storage.IgnoreKey(this.key, this.maxSize)
return len(data), err
}

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "1.2.0"
Version = "1.2.6"
ProductName = "Edge Node"
ProcessName = "edge-node"

View File

@@ -3,10 +3,11 @@ package events
type Event = string
const (
EventStart Event = "start" // start loading
EventLoaded Event = "loaded" // first load
EventQuit Event = "quit" // quit node gracefully
EventReload Event = "reload" // reload config
EventTerminated Event = "terminated" // process terminated
EventNFTablesReady Event = "nftablesReady" // nftables ready
EventStart Event = "start" // start loading
EventLoaded Event = "loaded" // first load
EventQuit Event = "quit" // quit node gracefully
EventReload Event = "reload" // reload config
EventTerminated Event = "terminated" // process terminated
EventNFTablesReady Event = "nftablesReady" // nftables ready
EventReloadSomeServers Event = "reloadSomeServers" // reload some servers
)

View File

@@ -24,6 +24,17 @@ func On(event Event, callback func()) {
OnKey(event, nil, callback)
}
func OnEvents(events []Event, callback func()) {
for _, event := range events {
On(event, callback)
}
}
func OnClose(callback func()) {
On(EventQuit, callback)
On(EventTerminated, callback)
}
// OnKey 使用Key增加事件回调
func OnKey(event Event, key interface{}, callback func()) {
if key == nil {

View File

@@ -9,7 +9,6 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/types"
"os/exec"
"strings"
"time"
)
@@ -32,7 +31,7 @@ func NewFirewalld() *Firewalld {
cmdQueue: make(chan *firewalldCmd, 4096),
}
path, err := exec.LookPath("firewall-cmd")
path, err := executils.LookPath("firewall-cmd")
if err == nil && len(path) > 0 {
var cmd = executils.NewTimeoutCmd(3*time.Second, path, "--state")
err := cmd.Run()

View File

@@ -6,12 +6,13 @@ package nftables_test
import (
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"os/exec"
"testing"
)
func TestConn_Test(t *testing.T) {
_, err := exec.LookPath("nft")
_, err := executils.LookPath("nft")
if err == nil {
t.Log("ok")
return

View File

@@ -13,7 +13,6 @@ import (
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/logs"
"os"
"os/exec"
"runtime"
"time"
)
@@ -55,7 +54,7 @@ func init() {
// NftExePath 查找nftables可执行文件路径
func NftExePath() string {
path, _ := exec.LookPath("nft")
path, _ := executils.LookPath("nft")
if len(path) > 0 {
return path
}
@@ -93,14 +92,14 @@ func (this *Installer) Install() error {
var cmd *executils.Cmd
// check dnf
dnfExe, err := exec.LookPath("dnf")
dnfExe, err := executils.LookPath("dnf")
if err == nil {
cmd = executils.NewCmd(dnfExe, "-y", "install", "nftables")
}
// check apt
if cmd == nil {
aptExe, err := exec.LookPath("apt")
aptExe, err := executils.LookPath("apt")
if err == nil {
cmd = executils.NewCmd(aptExe, "install", "nftables")
}
@@ -108,7 +107,7 @@ func (this *Installer) Install() error {
// check yum
if cmd == nil {
yumExe, err := exec.LookPath("yum")
yumExe, err := executils.LookPath("yum")
if err == nil {
cmd = executils.NewCmd(yumExe, "-y", "install", "nftables")
}

View File

@@ -1,27 +1,28 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package goman
package goman_test
import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"testing"
"time"
)
func TestNew(t *testing.T) {
New(func() {
goman.New(func() {
t.Log("Hello")
t.Log(List())
t.Log(goman.List())
})
time.Sleep(1 * time.Second)
t.Log(List())
t.Log(goman.List())
time.Sleep(1 * time.Second)
}
func TestNewWithArgs(t *testing.T) {
NewWithArgs(func(args ...interface{}) {
goman.NewWithArgs(func(args ...interface{}) {
t.Log(args[0], args[1])
}, 1, 2)
time.Sleep(1 * time.Second)

View File

@@ -0,0 +1,52 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package goman
import (
"github.com/TeaOSLab/EdgeNode/internal/zero"
"runtime"
"sync"
)
type TaskGroup struct {
semi chan zero.Zero
wg *sync.WaitGroup
locker *sync.RWMutex
}
func NewTaskGroup() *TaskGroup {
var concurrent = runtime.NumCPU()
if concurrent <= 1 {
concurrent = 2
}
return &TaskGroup{
semi: make(chan zero.Zero, concurrent),
wg: &sync.WaitGroup{},
locker: &sync.RWMutex{},
}
}
func (this *TaskGroup) Run(f func()) {
this.wg.Add(1)
go func() {
defer this.wg.Done()
this.semi <- zero.Zero{}
f()
<-this.semi
}()
}
func (this *TaskGroup) Wait() {
this.wg.Wait()
}
func (this *TaskGroup) Lock() {
this.locker.Lock()
}
func (this *TaskGroup) Unlock() {
this.locker.Unlock()
}

View File

@@ -0,0 +1,30 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package goman_test
import (
"github.com/TeaOSLab/EdgeNode/internal/goman"
"runtime"
"testing"
)
func TestNewTaskGroup(t *testing.T) {
var group = goman.NewTaskGroup()
var m = map[int]bool{}
for i := 0; i < runtime.NumCPU()*2; i++ {
var index = i
group.Run(func() {
t.Log("task", index)
group.Lock()
_, ok := m[index]
if ok {
t.Error("duplicated:", index)
}
m[index] = true
group.Unlock()
})
}
group.Wait()
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"os/exec"
"runtime"
"time"
)
@@ -82,7 +81,7 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
path := this.config.Path
var err error
if len(path) == 0 {
path, err = exec.LookPath("firewall-cmd")
path, err = executils.LookPath("firewall-cmd")
if err != nil {
if this.firewalldNotFound {
return nil

View File

@@ -6,7 +6,6 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/types"
"os/exec"
"runtime"
"strconv"
"strings"
@@ -55,7 +54,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
// 创建ipset
{
path, err := exec.LookPath("ipset")
path, err := executils.LookPath("ipset")
if err != nil {
return err
}
@@ -99,7 +98,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
// firewalld
if this.config.AutoAddToFirewalld {
path, err := exec.LookPath("firewall-cmd")
path, err := executils.LookPath("firewall-cmd")
if err != nil {
return err
}
@@ -179,7 +178,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
// iptables
if this.config.AutoAddToIPTables {
path, err := exec.LookPath("iptables")
path, err := executils.LookPath("iptables")
if err != nil {
return err
}
@@ -311,7 +310,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
var path = this.config.Path
var err error
if len(path) == 0 {
path, err = exec.LookPath("ipset")
path, err = executils.LookPath("ipset")
if err != nil {
// 找不到ipset命令错误只提示一次
if this.ipsetNotfound {

View File

@@ -6,7 +6,6 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"os/exec"
"runtime"
"strings"
"time"
@@ -87,7 +86,7 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
var path = this.config.Path
var err error
if len(path) == 0 {
path, err = exec.LookPath("iptables")
path, err = executils.LookPath("iptables")
if err != nil {
if this.iptablesNotFound {
return nil

View File

@@ -161,7 +161,7 @@ ON "` + this.itemTableName + `" (
this.db = db
goman.New(func() {
events.On(events.EventQuit, func() {
events.OnClose(func() {
_ = this.Close()
this.cleanTicker.Stop()
})

View File

@@ -29,7 +29,7 @@ func init() {
SharedIPListManager.Start()
})
})
events.On(events.EventQuit, func() {
events.OnClose(func() {
SharedIPListManager.Stop()
})

View File

@@ -18,7 +18,7 @@ func init() {
return
}
events.On(events.EventQuit, func() {
events.OnClose(func() {
SharedManager.Quit()
})
}

View File

@@ -20,7 +20,6 @@ import (
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/maps"
"net/url"
"os/exec"
"regexp"
"runtime"
"strconv"
@@ -336,7 +335,7 @@ func (this *APIStream) handleNewNodeTask(message *pb.NodeStreamMessage) error {
// 检查Systemd服务
func (this *APIStream) handleCheckSystemdService(message *pb.NodeStreamMessage) error {
systemctl, err := exec.LookPath("systemctl")
systemctl, err := executils.LookPath("systemctl")
if err != nil {
this.replyFail(message.RequestId, "'systemctl' not found")
return nil
@@ -378,7 +377,7 @@ func (this *APIStream) handleCheckLocalFirewall(message *pb.NodeStreamMessage) e
return nil
}
nft, err := exec.LookPath("nft")
nft, err := executils.LookPath("nft")
if err != nil {
this.replyFail(message.RequestId, "'nft' not found: "+err.Error())
return nil

View File

@@ -10,9 +10,9 @@ import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils"
connutils "github.com/TeaOSLab/EdgeNode/internal/utils/conns"
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/Tea"
@@ -24,6 +24,8 @@ import (
"time"
)
var synFloodCounter = counters.NewCounter().WithGC()
// ClientConn 客户端连接
type ClientConn struct {
BaseClientConn
@@ -127,9 +129,8 @@ func (this *ClientConn) Read(b []byte) (n int, err error) {
// 检测是否为超时错误
var isTimeout = err != nil && os.IsTimeout(err)
var isHandshakeError = isTimeout && !this.hasRead
if isTimeout {
_ = this.SetLinger(0)
} else {
if err != nil {
_ = this.SetLinger(nodeconfigs.DefaultTCPLinger)
}
@@ -289,14 +290,13 @@ func (this *ClientConn) LastErr() error {
}
func (this *ClientConn) resetSYNFlood() {
ttlcache.SharedCache.Delete("SYN_FLOOD:" + this.RawIP())
synFloodCounter.ResetKey("SYN_FLOOD:" + this.RawIP())
}
func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloodConfig) {
var ip = this.RawIP()
if len(ip) > 0 && !iplibrary.IsInWhiteList(ip) && (!synFloodConfig.IgnoreLocal || !utils.IsLocalIP(ip)) {
var timestamp = fasttime.Now().UnixNextMinute()
var result = ttlcache.SharedCache.IncreaseInt64("SYN_FLOOD:"+ip, 1, timestamp, true)
var result = synFloodCounter.IncreaseKey("SYN_FLOOD:"+ip, 60)
var minAttempts = synFloodConfig.MinAttempts
if minAttempts < 5 {
minAttempts = 5
@@ -305,7 +305,7 @@ func (this *ClientConn) increaseSYNFlood(synFloodConfig *firewallconfigs.SYNFloo
// 非TLS设置为两倍防止误封
minAttempts = 2 * minAttempts
}
if result >= int64(minAttempts) {
if result >= types.Uint64(minAttempts) {
var timeout = synFloodConfig.TimeoutSeconds
if timeout <= 0 {
timeout = 600

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/firewalls"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"net"
"time"
)
type BaseClientConn struct {
@@ -146,6 +147,8 @@ func (this *BaseClientConn) SetLinger(seconds int) error {
func (this *BaseClientConn) SetIsPersistent(isPersistent bool) {
this.isPersistent = isPersistent
_ = this.rawConn.SetDeadline(time.Time{})
}
// SetFingerprint 设置指纹信息

View File

@@ -19,6 +19,7 @@ import (
"io"
"net"
"net/http"
"os"
"regexp"
"strings"
"time"
@@ -131,21 +132,29 @@ func (this *HTTPCacheTaskManager) Loop() error {
var pbResults = []*pb.UpdateHTTPCacheTaskKeysStatusRequest_KeyResult{}
var taskGroup = goman.NewTaskGroup()
for _, key := range keys {
err = this.processKey(key)
var taskKey = key
taskGroup.Run(func() {
processErr := this.processKey(taskKey)
var pbResult = &pb.UpdateHTTPCacheTaskKeysStatusRequest_KeyResult{
Id: taskKey.Id,
NodeClusterId: taskKey.NodeClusterId,
Error: "",
}
var pbResult = &pb.UpdateHTTPCacheTaskKeysStatusRequest_KeyResult{
Id: key.Id,
NodeClusterId: key.NodeClusterId,
Error: "",
}
if processErr != nil {
pbResult.Error = processErr.Error()
}
if err != nil {
pbResult.Error = err.Error()
}
pbResults = append(pbResults, pbResult)
taskGroup.Lock()
pbResults = append(pbResults, pbResult)
taskGroup.Unlock()
})
}
taskGroup.Wait()
_, err = rpcClient.HTTPCacheTaskKeyRPC.UpdateHTTPCacheTaskKeysStatus(rpcClient.Context(), &pb.UpdateHTTPCacheTaskKeysStatusRequest{KeyResults: pbResults})
if err != nil {
return err
@@ -242,6 +251,7 @@ func (this *HTTPCacheTaskManager) fetchKey(key *pb.HTTPCacheTaskKey) error {
req.Header.Set("Accept-Encoding", "gzip, deflate, br")
resp, err := this.httpClient.Do(req)
if err != nil {
err = this.simplifyErr(err)
return errors.New("request failed: " + fullKey + ": " + err.Error())
}
@@ -249,13 +259,32 @@ func (this *HTTPCacheTaskManager) fetchKey(key *pb.HTTPCacheTaskKey) error {
_ = resp.Body.Close()
}()
// 读取内容,以便于生成缓存
_, _ = io.Copy(io.Discard, resp.Body)
// 处理502
if resp.StatusCode == http.StatusBadGateway {
return errors.New("read origin site timeout")
}
// 读取内容,以便于生成缓存
_, err = io.Copy(io.Discard, resp.Body)
if err != nil {
if err != io.EOF {
err = this.simplifyErr(err)
return errors.New("request failed: " + fullKey + ": " + err.Error())
} else {
err = nil
}
}
return nil
}
func (this *HTTPCacheTaskManager) simplifyErr(err error) error {
if err == nil {
return nil
}
if os.IsTimeout(err) {
return errors.New("timeout to read origin site")
}
return err
}

View File

@@ -6,8 +6,8 @@ import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/pires/go-proxyproto"
"golang.org/x/net/http2"
"net"
"net/http"
"runtime"
@@ -131,14 +131,8 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
var transport = &HTTPClientTransport{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// 支持TOA的连接
conn, err := this.handleTOA(req, ctx, network, originAddr, connectionTimeout)
if conn != nil || err != nil {
return conn, err
}
// 普通的连接
conn, err = (&net.Dialer{
conn, err := (&net.Dialer{
Timeout: connectionTimeout,
KeepAlive: 1 * time.Minute,
}).DialContext(ctx, network, originAddr)
@@ -166,6 +160,11 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
},
}
// support http/2
if origin.HTTP2Enabled && origin.Addr != nil && origin.Addr.Protocol == serverconfigs.ProtocolHTTPS {
_ = http2.ConfigureTransport(transport.Transport)
}
rawClient = &http.Client{
Timeout: readTimeout,
Transport: transport,
@@ -209,38 +208,6 @@ func (this *HTTPClientPool) cleanClients() {
}
}
// 支持TOA
func (this *HTTPClientPool) handleTOA(req *HTTPRequest, ctx context.Context, network string, originAddr string, connectionTimeout time.Duration) (net.Conn, error) {
// TODO 每个服务读取自身所属集群的TOA设置
var toaConfig = sharedTOAManager.Config()
if toaConfig != nil && toaConfig.IsOn {
var retries = 3
for i := 1; i <= retries; i++ {
var port = int(toaConfig.RandLocalPort())
// TODO 思考是否支持X-Real-IP/X-Forwarded-IP
err := sharedTOAManager.SendMsg("add:" + strconv.Itoa(port) + ":" + req.requestRemoteAddr(true))
if err != nil {
remotelogs.Error("TOA", "add failed: "+err.Error())
} else {
dialer := net.Dialer{
Timeout: connectionTimeout,
KeepAlive: 1 * time.Minute,
LocalAddr: &net.TCPAddr{
Port: port,
},
}
conn, err := dialer.DialContext(ctx, network, originAddr)
// TODO 需要在合适的时机删除TOA记录
if err == nil || i == retries {
return conn, err
}
}
}
}
return nil, nil
}
// 支持PROXY Protocol
func (this *HTTPClientPool) handlePROXYProtocol(conn net.Conn, req *HTTPRequest, proxyProtocol *serverconfigs.ProxyProtocolConfig) error {
if proxyProtocol != nil && proxyProtocol.IsOn && (proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {

View File

@@ -509,6 +509,11 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo
this.web.Compression = web.Compression
}
// optimizer
if web.Optimization != nil && (web.Optimization.IsPrior || (isTop && web.Optimization.IsOn())) {
this.web.Optimization = web.Optimization
}
// webp
if web.WebP != nil && (web.WebP.IsPrior || isTop) {
this.web.WebP = web.WebP
@@ -1712,6 +1717,11 @@ func (this *HTTPRequest) fixRequestHeader(header http.Header) {
// ProcessResponseHeaders 处理自定义Response Header
func (this *HTTPRequest) ProcessResponseHeaders(responseHeader http.Header, statusCode int) {
// Server Name
if this.nodeConfig != nil && this.nodeConfig.GlobalServerConfig != nil && len(this.nodeConfig.GlobalServerConfig.HTTPAll.ServerName) > 0 {
responseHeader.Set("Server", this.nodeConfig.GlobalServerConfig.HTTPAll.ServerName)
}
// 删除/添加/替换Header
// TODO 实现AddTrailers
if this.web.ResponseHeaderPolicy != nil && this.web.ResponseHeaderPolicy.IsOn {

View File

@@ -38,8 +38,13 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
// 添加 X-Cache Header
var addStatusHeader = this.web.Cache.AddStatusHeader
var cacheBypassDescription = ""
if addStatusHeader {
defer func() {
if len(cacheBypassDescription) > 0 {
this.writer.Header().Set("X-Cache", cacheBypassDescription)
return
}
var cacheStatus = this.varMapping["cache.status"]
if cacheStatus != "HIT" {
this.writer.Header().Set("X-Cache", cacheStatus)
@@ -94,6 +99,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
// 校验请求
if !this.cacheRef.MatchRequest(this.RawReq) {
this.cacheRef = nil
cacheBypassDescription = "BYPASS, not match"
return
}
@@ -106,6 +112,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
if this.cacheRef.EnableRequestCachePragma {
if this.RawReq.Header.Get("Cache-Control") == "no-cache" || this.RawReq.Header.Get("Pragma") == "no-cache" {
this.cacheRef = nil
cacheBypassDescription = "BYPASS, Cache-Control or Pragma"
return
}
}
@@ -119,6 +126,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
var key = this.Format(this.cacheRef.Key)
if len(key) == 0 {
this.cacheRef = nil
cacheBypassDescription = "BYPASS, empty key"
return
}
var method = this.Method()
@@ -134,6 +142,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
var storage = caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
if storage == nil {
this.cacheRef = nil
cacheBypassDescription = "BYPASS, no policy found"
return
}
this.writer.cacheStorage = storage

View File

@@ -14,6 +14,11 @@ const (
// 日志
func (this *HTTPRequest) log() {
// 检查全局配置
if this.nodeConfig != nil && this.nodeConfig.GlobalServerConfig != nil && !this.nodeConfig.GlobalServerConfig.HTTPAccessLog.IsOn {
return
}
var ref *serverconfigs.HTTPAccessLogRef
if !this.forceLog {
if this.disableLog {

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/types"
"net"
"net/http"
"time"
@@ -35,9 +36,24 @@ func (this *HTTPRequest) doMismatch() {
// 根据配置进行相应的处理
var globalServerConfig = sharedNodeConfig.GlobalServerConfig
if globalServerConfig != nil && globalServerConfig.HTTPAll.MatchDomainStrictly {
var statusCode = 404
var httpAllConfig = globalServerConfig.HTTPAll
var mismatchAction = httpAllConfig.DomainMismatchAction
if mismatchAction != nil && mismatchAction.Options != nil {
var mismatchStatusCode = mismatchAction.Options.GetInt("statusCode")
if mismatchStatusCode > 0 && mismatchStatusCode >= 100 && mismatchStatusCode < 1000 {
statusCode = mismatchStatusCode
}
}
// 是否正在访问IP
if globalServerConfig.HTTPAll.NodeIPShowPage && net.ParseIP(this.ReqHost) != nil {
_, _ = this.writer.WriteString(globalServerConfig.HTTPAll.NodeIPPageHTML)
var contentHTML = this.Format(globalServerConfig.HTTPAll.NodeIPPageHTML)
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
this.writer.Header().Set("Content-Length", types.String(len(contentHTML)))
this.writer.WriteHeader(statusCode)
_, _ = this.writer.WriteString(contentHTML)
return
}
@@ -55,13 +71,13 @@ func (this *HTTPRequest) doMismatch() {
}
// 处理当前连接
var httpAllConfig = globalServerConfig.HTTPAll
var mismatchAction = httpAllConfig.DomainMismatchAction
if mismatchAction != nil && mismatchAction.Code == "page" {
if mismatchAction.Options != nil {
var contentHTML = this.Format(mismatchAction.Options.GetString("contentHTML"))
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
this.writer.WriteHeader(mismatchAction.Options.GetInt("statusCode"))
_, _ = this.writer.Write([]byte(mismatchAction.Options.GetString("contentHTML")))
this.writer.Header().Set("Content-Length", types.String(len(contentHTML)))
this.writer.WriteHeader(statusCode)
_, _ = this.writer.Write([]byte(contentHTML))
} else {
http.Error(this.writer, "404 page not found: '"+this.URL()+"'", http.StatusNotFound)
}

View File

@@ -9,7 +9,7 @@ import (
"net/http"
)
func (this *HTTPRequest) doOSSOrigin(origin *serverconfigs.OriginConfig) (resp *http.Response, goNext bool, errorCode string, err error) {
func (this *HTTPRequest) doOSSOrigin(origin *serverconfigs.OriginConfig) (resp *http.Response, goNext bool, errorCode string, ossBucketName string, err error) {
// stub
return nil, false, "", errors.New("not implemented")
return nil, false, "", "", errors.New("not implemented")
}

View File

@@ -6,9 +6,10 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"net/http"
"os"
"path"
"strings"
)
// 请求特殊页面
@@ -54,30 +55,30 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
this.doURL(http.MethodGet, page.URL, "", newStatus, true)
return true
} else {
file := Tea.Root + Tea.DS + page.URL
fp, err := os.Open(file)
if err != nil {
logs.Error(err)
msg := "404 page not found: '" + page.URL + "'"
var realpath = path.Clean(page.URL)
if !strings.HasPrefix(realpath, "/pages/") && !strings.HasPrefix(realpath, "pages/") { // only files under "/pages/" can be used
var msg = "404 page not found: '" + page.URL + "'"
this.writer.WriteHeader(http.StatusNotFound)
_, err := this.writer.Write([]byte(msg))
if err != nil {
logs.Error(err)
}
_, _ = this.writer.Write([]byte(msg))
return true
}
var file = Tea.Root + Tea.DS + realpath
fp, err := os.Open(file)
if err != nil {
var msg = "404 page not found: '" + page.URL + "'"
this.writer.WriteHeader(http.StatusNotFound)
_, _ = this.writer.Write([]byte(msg))
return true
}
defer func() {
_ = fp.Close()
}()
stat, err := fp.Stat()
if err != nil {
logs.Error(err)
msg := "404 could not read page content: '" + page.URL + "'"
var msg = "404 could not read page content: '" + page.URL + "'"
this.writer.WriteHeader(http.StatusNotFound)
_, err := this.writer.Write([]byte(msg))
if err != nil {
logs.Error(err)
}
_, _ = this.writer.Write([]byte(msg))
return true
}
@@ -92,7 +93,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
this.writer.Prepare(nil, stat.Size(), status, true)
this.writer.WriteHeader(status)
}
buf := utils.BytePool1k.Get()
var buf = utils.BytePool1k.Get()
_, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte {
return []byte(this.Format(string(p)))
})
@@ -104,10 +105,6 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
} else {
this.writer.SetOk()
}
err = fp.Close()
if err != nil {
logs.Error(err)
}
}
return true

View File

@@ -286,7 +286,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
resp, requestErr = client.Do(this.RawReq)
} else if origin.OSS != nil { // OSS源站
var goNext bool
resp, goNext, requestErrCode, requestErr = this.doOSSOrigin(origin)
resp, goNext, requestErrCode, _, requestErr = this.doOSSOrigin(origin)
if requestErr == nil {
if resp == nil || !goNext {
return
@@ -412,6 +412,15 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
return
}
// Page optimization
if this.web.Optimization != nil && resp.Body != nil && this.cacheRef != nil /** must under cache **/ {
err := this.web.Optimization.FilterResponse(resp)
if err != nil {
this.write50x(err, http.StatusBadGateway, "Page Optimization: Fail to read content from origin", "内容优化:从源站读取内容失败", false)
return
}
}
// 设置Charset
// TODO 这里应该可以设置文本类型的列表,以及是否强制覆盖所有文本类型的字符集
if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 {
@@ -461,7 +470,19 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
this.ProcessResponseHeaders(this.writer.Header(), resp.StatusCode)
// 是否需要刷新
var shouldAutoFlush = this.reverseProxy.AutoFlush || this.RawReq.Header.Get("Accept") == "text/event-stream"
var shouldAutoFlush = this.reverseProxy.AutoFlush || (resp.Header != nil && strings.Contains(resp.Header.Get("Content-Type"), "stream"))
// 设置当前连接为Persistence
if shouldAutoFlush && this.nodeConfig != nil && this.nodeConfig.HasConnTimeoutSettings() {
var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
if requestConn == nil {
return
}
requestClientConn, ok := requestConn.(ClientConnInterface)
if ok {
requestClientConn.SetIsPersistent(true)
}
}
// 准备
var delayHeaders = this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true)

View File

@@ -5,14 +5,15 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"net/http"
"os"
"path"
"strings"
)
// 调用临时关闭页面
func (this *HTTPRequest) doShutdown() {
shutdown := this.web.Shutdown
var shutdown = this.web.Shutdown
if shutdown == nil {
return
}
@@ -34,28 +35,30 @@ func (this *HTTPRequest) doShutdown() {
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
this.writer.WriteHeader(http.StatusOK)
}
_, err := this.writer.WriteString("The site have been shutdown.")
if err != nil {
logs.Error(err)
}
_, _ = this.writer.WriteString("The site have been shutdown.")
return
}
// 从本地文件中读取
file := Tea.Root + Tea.DS + shutdown.URL
fp, err := os.Open(file)
if err != nil {
logs.Error(err)
msg := "404 page not found: '" + shutdown.URL + "'"
var realpath = path.Clean(shutdown.URL)
if !strings.HasPrefix(realpath, "/pages/") && !strings.HasPrefix(realpath, "pages/") { // only files under "/pages/" can be used
var msg = "404 page not found: '" + shutdown.URL + "'"
this.writer.WriteHeader(http.StatusNotFound)
_, err = this.writer.Write([]byte(msg))
if err != nil {
logs.Error(err)
}
_, _ = this.writer.Write([]byte(msg))
return
}
var file = Tea.Root + Tea.DS + shutdown.URL
fp, err := os.Open(file)
if err != nil {
var msg = "404 page not found: '" + shutdown.URL + "'"
this.writer.WriteHeader(http.StatusNotFound)
_, _ = this.writer.Write([]byte(msg))
return
}
defer func() {
_ = fp.Close()
}()
// 自定义响应Headers
if shutdown.Status > 0 {
@@ -65,7 +68,7 @@ func (this *HTTPRequest) doShutdown() {
this.ProcessResponseHeaders(this.writer.Header(), http.StatusOK)
this.writer.WriteHeader(http.StatusOK)
}
buf := utils.BytePool1k.Get()
var buf = utils.BytePool1k.Get()
_, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte {
return []byte(this.Format(string(p)))
})
@@ -77,11 +80,6 @@ func (this *HTTPRequest) doShutdown() {
} else {
this.writer.SetOk()
}
err = fp.Close()
if err != nil {
remotelogs.Warn("HTTP_REQUEST_SHUTDOWN", "close file failed: "+err.Error())
}
} else if shutdown.BodyType == shared.BodyTypeHTML {
// 自定义响应Headers
if shutdown.Status > 0 {

View File

@@ -67,8 +67,8 @@ func (this *HTTPRequest) doURL(method string, url string, host string, statusCod
}
// 输出内容
pool := this.bytePool(resp.ContentLength)
buf := pool.Get()
var pool = this.bytePool(resp.ContentLength)
var buf = pool.Get()
if supportVariables {
_, err = utils.CopyWithFilter(this.writer, resp.Body, buf, func(p []byte) []byte {
return []byte(this.Format(string(p)))

View File

@@ -7,7 +7,7 @@ import (
)
func (this *HTTPRequest) doCheckUserAgent() (shouldStop bool) {
if this.web.UserAgent == nil {
if this.web.UserAgent == nil || !this.web.UserAgent.IsOn {
return
}

View File

@@ -9,10 +9,10 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"time"
)
// 调用WAF
@@ -171,13 +171,20 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
var currentURL = this.URL()
if regionConfig.MatchCountryURL(currentURL) {
// 检查国家/地区级别封禁
var countryId = result.CountryId()
if countryId > 0 && lists.ContainsInt64(regionConfig.DenyCountryIds, countryId) {
if !regionConfig.IsAllowedCountry(result.CountryId(), result.ProvinceId()) {
this.firewallPolicyId = firewallPolicy.Id
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
this.writer.Flush()
this.writer.Close()
if len(regionConfig.CountryHTML) > 0 {
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
this.writer.Header().Set("Content-Length", types.String(len(regionConfig.CountryHTML)))
this.writer.WriteHeader(http.StatusForbidden)
_, _ = this.writer.Write([]byte(regionConfig.CountryHTML))
} else {
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
}
// 延时返回,避免攻击
time.Sleep(1 * time.Second)
// 停止日志
if !logDenying {
@@ -192,13 +199,20 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
if regionConfig.MatchProvinceURL(currentURL) {
// 检查省份封禁
var provinceId = result.ProvinceId()
if provinceId > 0 && lists.ContainsInt64(regionConfig.DenyProvinceIds, provinceId) {
if !regionConfig.IsAllowedProvince(result.CountryId(), result.ProvinceId()) {
this.firewallPolicyId = firewallPolicy.Id
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
this.writer.Flush()
this.writer.Close()
if len(regionConfig.ProvinceHTML) > 0 {
this.writer.Header().Set("Content-Type", "text/html; charset=utf-8")
this.writer.Header().Set("Content-Length", types.String(len(regionConfig.ProvinceHTML)))
this.writer.WriteHeader(http.StatusForbidden)
_, _ = this.writer.Write([]byte(regionConfig.ProvinceHTML))
} else {
this.writeCode(http.StatusForbidden, "The region has been denied.", "当前区域禁止访问")
}
// 延时返回,避免攻击
time.Sleep(1 * time.Second)
// 停止日志
if !logDenying {

View File

@@ -324,11 +324,11 @@ func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) {
// 待写入尺寸
var totalSize = size
if totalSize < 0 && this.isPartial {
if this.isPartial {
var contentRange = resp.Header.Get("Content-Range")
if len(contentRange) > 0 {
_, partialTotalSize := httpRequestParseContentRangeHeader(contentRange)
if partialTotalSize > 0 {
if partialTotalSize > 0 && partialTotalSize > totalSize {
totalSize = partialTotalSize
}
}
@@ -786,12 +786,18 @@ func (this *HTTPWriter) AddHeaders(header http.Header) {
if this.rawWriter == nil {
return
}
var newHeaders = this.rawWriter.Header()
for key, value := range header {
if key == "Connection" {
continue
}
for _, v := range value {
this.rawWriter.Header().Add(key, v)
switch key {
case "ETag":
newHeaders[key] = value
default:
for _, v := range value {
newHeaders.Add(key, v)
}
}
}
}

View File

@@ -15,7 +15,6 @@ import (
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net/url"
"os/exec"
"regexp"
"runtime"
"sort"
@@ -213,7 +212,7 @@ func (this *ListenerManager) findProcessNameWithPort(isUdp bool, port string) st
return ""
}
path, err := exec.LookPath("ss")
path, err := executils.LookPath("ss")
if err != nil {
return ""
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
iplib "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
@@ -26,6 +27,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/utils"
_ "github.com/TeaOSLab/EdgeNode/internal/utils/agents" // 引入Agent管理器
_ "github.com/TeaOSLab/EdgeNode/internal/utils/clock" // 触发时钟更新
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
"github.com/TeaOSLab/EdgeNode/internal/utils/jsonutils"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/andybalholm/brotli"
@@ -141,7 +143,7 @@ func (this *Node) Start() {
// 调整系统参数
this.checkSystem()
// 检查硬盘类型
// 检查硬盘
this.checkDisk()
// 启动事件
@@ -1054,6 +1056,9 @@ func (this *Node) reloadServer() {
if err != nil {
remotelogs.Error("NODE", "apply server config error: "+err.Error())
}
// notify event
events.Notify(events.EventReloadSomeServers)
}
}
@@ -1109,23 +1114,16 @@ func (this *Node) checkSystem() {
// 检查硬盘
func (this *Node) checkDisk() {
if runtime.GOOS != "linux" {
speedMB, isFast, err := fsutils.CheckDiskIsFast()
if err != nil {
remotelogs.Error("NODE", "check disk speed failed: "+err.Error())
return
}
for n := 'a'; n <= 'z'; n++ {
for _, path := range []string{
"/sys/block/vd" + string(n) + "/queue/rotational",
"/sys/block/sd" + string(n) + "/queue/rotational",
} {
data, err := os.ReadFile(path)
if err != nil {
continue
}
if string(data) == "0" {
teaconst.DiskIsFast = true
}
return
}
teaconst.DiskIsFast = isFast
if isFast {
remotelogs.Println("NODE", "disk is fast, writing test speed: "+fmt.Sprintf("%.2fMB/s", speedMB))
} else {
remotelogs.Println("NODE", "disk is slow, writing test speed: "+fmt.Sprintf("%.2fMB/s", speedMB))
}
}

View File

@@ -15,3 +15,7 @@ func (this *Node) reloadIPLibrary() {
func (this *Node) notifyPlusChange() error {
return nil
}
func (this *Node) execTOAChangedTask() error {
return nil
}

View File

@@ -13,12 +13,12 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/net"
"golang.org/x/sys/unix"
"math"
"os"
"runtime"
@@ -279,16 +279,15 @@ func (this *NodeStatusExecutor) updateCacheSpace(status *nodeconfigs.NodeStatus)
var result = []maps.Map{}
var cachePaths = caches.SharedManager.FindAllCachePaths()
for _, path := range cachePaths {
var stat unix.Statfs_t
err := unix.Statfs(path, &stat)
stat, err := fsutils.Stat(path)
if err != nil {
return
}
result = append(result, maps.Map{
"path": path,
"total": stat.Blocks * uint64(stat.Bsize),
"avail": stat.Bavail * uint64(stat.Bsize),
"used": (stat.Blocks - stat.Bavail) * uint64(stat.Bsize),
"total": stat.TotalSize(),
"avail": stat.FreeSize(),
"used": stat.UsedSize(),
})
}
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemCacheDir, maps.Map{

View File

@@ -90,6 +90,8 @@ func (this *Node) execTask(rpcClient *rpc.RPCClient, task *pb.NodeTask) error {
err = this.execUpdatingServersTask(rpcClient)
case "plusChanged":
err = this.notifyPlusChange()
case "toaChanged":
err = this.execTOAChangedTask()
default:
remotelogs.Error("NODE", "task '"+types.String(task.Id)+"', type '"+task.Type+"' has not been handled")
}
@@ -197,7 +199,7 @@ func (this *Node) execGlobalServerConfigChangedTask(rpcClient *rpc.RPCClient) er
return err
}
if len(resp.GlobalServerConfigJSON) > 0 {
var globalServerConfig = serverconfigs.DefaultGlobalServerConfig()
var globalServerConfig = serverconfigs.NewGlobalServerConfig()
err = json.Unmarshal(resp.GlobalServerConfigJSON, globalServerConfig)
if err != nil {
return errors.New("decode global server config failed: " + err.Error())

View File

@@ -13,7 +13,6 @@ import (
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/maps"
"os"
"os/exec"
"runtime"
"time"
)
@@ -84,7 +83,7 @@ func (this *SystemServiceManager) setupSystemd(params maps.Map) error {
}
// 检查当前的service
systemctl, err := exec.LookPath("systemctl")
systemctl, err := executils.LookPath("systemctl")
if err != nil {
return err
}

View File

@@ -27,10 +27,9 @@ func init() {
sharedOCSPTask.Start()
})
})
events.On(events.EventQuit, func() {
events.OnClose(func() {
sharedOCSPTask.Stop()
})
}
// OCSPUpdateTask 更新OCSP任务

View File

@@ -26,7 +26,7 @@ func init() {
sharedSyncAPINodesTask.Start()
})
})
events.On(events.EventQuit, func() {
events.OnClose(func() {
sharedSyncAPINodesTask.Stop()
})
}

View File

@@ -1,117 +1,31 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/Tea"
"net"
"os"
"strings"
"time"
)
import "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
var sharedTOAManager = NewTOAManager()
func init() {
if !teaconst.IsMain {
return
}
events.On(events.EventReload, func() {
err := sharedTOAManager.Run(sharedNodeConfig.TOA)
if err != nil {
remotelogs.Error("TOA", err.Error())
}
})
}
type TOAManager struct {
config *nodeconfigs.TOAConfig
pid int
conn net.Conn
}
func NewTOAManager() *TOAManager {
return &TOAManager{}
}
func (this *TOAManager) Run(config *nodeconfigs.TOAConfig) error {
this.config = config
if this.pid > 0 {
remotelogs.Println("TOA", "stopping ...")
err := this.Quit()
if err != nil {
remotelogs.Error("TOA", "quit error: "+err.Error())
}
if this.conn != nil {
_ = this.conn.Close()
}
this.conn = nil
this.pid = 0
}
if !config.IsOn {
return nil
}
binPath := Tea.Root + "/edge-toa/edge-toa" // TODO 可以做成配置
_, err := os.Stat(binPath)
if err != nil {
return err
}
remotelogs.Println("TOA", "starting ...")
remotelogs.Println("TOA", "args: "+strings.Join(config.AsArgs(), " "))
cmd := executils.NewCmd(binPath, config.AsArgs()...)
err = cmd.Start()
if err != nil {
return err
}
var process = cmd.Process()
if process == nil {
return errors.New("start failed")
}
this.pid = process.Pid
goman.New(func() {
_ = cmd.Wait()
})
func (this *TOAManager) Apply(config *nodeconfigs.TOAConfig) error {
return nil
}
func (this *TOAManager) Config() *nodeconfigs.TOAConfig {
return this.config
return nil
}
func (this *TOAManager) Quit() error {
return this.SendMsg("quit:0")
return nil
}
func (this *TOAManager) SendMsg(msg string) error {
if this.config == nil {
return nil
}
if this.conn != nil {
_, err := this.conn.Write([]byte(msg + "\n"))
if err != nil {
_ = this.conn.Close()
this.conn = nil
}
return err
}
conn, err := net.DialTimeout("unix", this.config.SockFile(), 1*time.Second)
if err != nil {
return err
}
this.conn = conn
_, err = this.conn.Write([]byte(msg + "\n"))
return err
return nil
}

View File

@@ -1,17 +0,0 @@
package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"testing"
)
func TestTOAManager_Run(t *testing.T) {
manager := NewTOAManager()
err := manager.Run(&nodeconfigs.TOAConfig{
IsOn: true,
})
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -29,6 +29,7 @@ var sharedUpgradeManager = NewUpgradeManager()
type UpgradeManager struct {
isInstalling bool
lastFile string
exe string
}
// NewUpgradeManager 获取新对象
@@ -38,6 +39,14 @@ func NewUpgradeManager() *UpgradeManager {
// Start 启动升级
func (this *UpgradeManager) Start() {
// 必须放在文件解压之前读取可执行文件路径,防止解析之后,当前的可执行文件路径发生改变
exe, err := os.Executable()
if err != nil {
remotelogs.Error("UPGRADE_MANAGER", "can not find current executable file name")
return
}
this.exe = exe
// 测试环境下不更新
if Tea.IsTesting() {
return
@@ -49,7 +58,7 @@ func (this *UpgradeManager) Start() {
this.isInstalling = true
remotelogs.Println("UPGRADE_MANAGER", "upgrading node ...")
err := this.install()
err = this.install()
if err != nil {
remotelogs.Error("UPGRADE_MANAGER", "download failed: "+err.Error())
@@ -104,7 +113,7 @@ func (this *UpgradeManager) install() error {
remotelogs.Println("UPGRADE_MANAGER", "downloading new node ...")
var path = dir + "/edge-node" + ".tmp"
var path = dir + "/edge-node.tmp"
fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777)
if err != nil {
return err
@@ -238,11 +247,6 @@ func (this *UpgradeManager) restart() error {
if DaemonIsOn && DaemonPid == os.Getppid() {
utils.Exit() // TODO 试着更优雅重启
} else {
exe, err := os.Executable()
if err != nil {
return err
}
// quit
events.Notify(events.EventQuit)
@@ -250,10 +254,9 @@ func (this *UpgradeManager) restart() error {
events.Notify(events.EventTerminated)
// 启动
exe = filepath.Dir(exe) + "/" + teaconst.ProcessName
var exe = filepath.Dir(this.exe) + "/" + teaconst.ProcessName
var cmd = executils.NewCmd(exe, "start")
err = cmd.Start()
err := cmd.Start()
if err != nil {
return err
}

View File

@@ -36,7 +36,7 @@ func init() {
})
})
events.On(events.EventQuit, func() {
events.OnClose(func() {
SharedBandwidthStatManager.Cancel()
err := SharedBandwidthStatManager.Save()
@@ -74,15 +74,12 @@ type BandwidthStatManager struct {
ticker *time.Ticker
locker sync.Mutex
cacheFile string // 上一次的缓存文件
}
func NewBandwidthStatManager() *BandwidthStatManager {
return &BandwidthStatManager{
m: map[string]*BandwidthStat{},
ticker: time.NewTicker(1 * time.Minute), // 时间小于1分钟是为了更快速地上传结果
cacheFile: Tea.Root + "/data/bandwidth.dat",
m: map[string]*BandwidthStat{},
ticker: time.NewTicker(1 * time.Minute), // 时间小于1分钟是为了更快速地上传结果
}
}
@@ -271,13 +268,17 @@ func (this *BandwidthStatManager) Save() error {
this.locker.Lock()
defer this.locker.Unlock()
if len(this.m) == 0 {
return nil
}
data, err := json.Marshal(this.m)
if err != nil {
return err
}
_ = os.Remove(this.cacheFile)
return os.WriteFile(this.cacheFile, data, 0666)
_ = os.Remove(this.cacheFile())
return os.WriteFile(this.cacheFile(), data, 0666)
}
// Cancel 取消上传
@@ -287,7 +288,7 @@ func (this *BandwidthStatManager) Cancel() {
// 从本地缓存文件中恢复数据
func (this *BandwidthStatManager) recover() {
cacheData, err := os.ReadFile(this.cacheFile)
cacheData, err := os.ReadFile(this.cacheFile())
if err == nil {
var m = map[string]*BandwidthStat{}
err = json.Unmarshal(cacheData, &m)
@@ -309,6 +310,12 @@ func (this *BandwidthStatManager) recover() {
}
}
}
_ = os.Remove(this.cacheFile)
_ = os.Remove(this.cacheFile())
}
}
// 获取缓存文件
// 不能在init()中初始化,避免无法获得正确的路径
func (this *BandwidthStatManager) cacheFile() string {
return Tea.Root + "/data/bandwidth.dat"
}

View File

@@ -213,7 +213,7 @@ func BenchmarkCache_Add_Parallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var j = atomic.AddInt64(&i, 1)
cache.Write(types.String(j), j, fasttime.Now().Unix()+i%1024)
cache.Write(types.String(j%1e6), j, fasttime.Now().Unix()+i%1024)
}
})
}

View File

@@ -30,7 +30,7 @@ type DB struct {
func NewDB(path string) *DB {
var db = &DB{path: path}
events.On(events.EventQuit, func() {
events.OnClose(func() {
_ = db.Close()
})

View File

@@ -13,7 +13,6 @@ import (
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
timeutil "github.com/iwind/TeaGo/utils/time"
"net"
"os/exec"
"runtime"
"time"
)
@@ -86,7 +85,7 @@ func (this *ClockManager) Sync() error {
// check chrony
if config.CheckChrony {
chronycExe, err := exec.LookPath("chronyc")
chronycExe, err := executils.LookPath("chronyc")
if err == nil && len(chronycExe) > 0 {
var chronyCmd = executils.NewTimeoutCmd(3*time.Second, chronycExe, "tracking")
err = chronyCmd.Run()
@@ -101,11 +100,11 @@ func (this *ClockManager) Sync() error {
server = "pool.ntp.org"
}
ntpdate, err := exec.LookPath("ntpdate")
ntpdate, err := executils.LookPath("ntpdate")
if err != nil {
// 使用 date 命令设置
// date --set TIME
dateExe, err := exec.LookPath("date")
dateExe, err := executils.LookPath("date")
if err == nil {
currentTime, err := this.ReadServer(server)
if err != nil {

View File

@@ -0,0 +1,179 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package counters
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
syncutils "github.com/TeaOSLab/EdgeNode/internal/utils/sync"
"github.com/cespare/xxhash"
"runtime"
"sync"
"time"
)
type Counter struct {
countMaps uint64
locker *syncutils.RWMutex
itemMaps []map[uint64]*Item
gcTicker *time.Ticker
gcIndex int
gcLocker sync.Mutex
}
// NewCounter create new counter
func NewCounter() *Counter {
var count = runtime.NumCPU() * 4
if count < 8 {
count = 8
} else if count > 128 {
count = 128
}
var itemMaps = []map[uint64]*Item{}
for i := 0; i < count; i++ {
itemMaps = append(itemMaps, map[uint64]*Item{})
}
var counter = &Counter{
countMaps: uint64(count),
locker: syncutils.NewRWMutex(count),
itemMaps: itemMaps,
}
return counter
}
// WithGC start the counter with gc automatically
func (this *Counter) WithGC() *Counter {
if this.gcTicker != nil {
return this
}
this.gcTicker = time.NewTicker(1 * time.Second)
go func() {
for range this.gcTicker.C {
this.GC()
}
}()
return this
}
// Increase key
func (this *Counter) Increase(key uint64, lifeSeconds int) uint64 {
var index = int(key % this.countMaps)
this.locker.RLock(index)
var item = this.itemMaps[index][key]
this.locker.RUnlock(index)
if item == nil { // no need to care about duplication
item = NewItem(lifeSeconds)
this.locker.Lock(index)
// check again
oldItem, ok := this.itemMaps[index][key]
if !ok {
this.itemMaps[index][key] = item
} else {
item = oldItem
}
this.locker.Unlock(index)
}
this.locker.Lock(index)
var result = item.Increase()
this.locker.Unlock(index)
return result
}
// IncreaseKey increase string key
func (this *Counter) IncreaseKey(key string, lifeSeconds int) uint64 {
return this.Increase(this.hash(key), lifeSeconds)
}
// Get value of key
func (this *Counter) Get(key uint64) uint64 {
var index = int(key % this.countMaps)
this.locker.RLock(index)
defer this.locker.RUnlock(index)
var item = this.itemMaps[index][key]
if item != nil {
return item.Sum()
}
return 0
}
// GetKey get value of string key
func (this *Counter) GetKey(key string) uint64 {
return this.Get(this.hash(key))
}
// Reset key
func (this *Counter) Reset(key uint64) {
var index = int(key % this.countMaps)
this.locker.RLock(index)
var item = this.itemMaps[index][key]
this.locker.RUnlock(index)
if item != nil {
this.locker.Lock(index)
delete(this.itemMaps[index], key)
this.locker.Unlock(index)
}
}
// ResetKey string key
func (this *Counter) ResetKey(key string) {
this.Reset(this.hash(key))
}
// TotalItems get items count
func (this *Counter) TotalItems() int {
var total = 0
for i := 0; i < int(this.countMaps); i++ {
this.locker.RLock(i)
total += len(this.itemMaps[i])
this.locker.RUnlock(i)
}
return total
}
// GC garbage expired items
func (this *Counter) GC() {
this.gcLocker.Lock()
var gcIndex = this.gcIndex
this.gcIndex++
if this.gcIndex >= int(this.countMaps) {
this.gcIndex = 0
}
this.gcLocker.Unlock()
var currentTime = fasttime.Now().Unix()
this.locker.RLock(gcIndex)
var itemMap = this.itemMaps[gcIndex]
var expiredKeys = []uint64{}
for key, item := range itemMap {
if item.IsExpired(currentTime) {
expiredKeys = append(expiredKeys, key)
}
}
this.locker.RUnlock(gcIndex)
if len(expiredKeys) > 0 {
for _, key := range expiredKeys {
this.locker.Lock(gcIndex)
delete(itemMap, key)
this.locker.Unlock(gcIndex)
}
}
}
// calculate hash of the key
func (this *Counter) hash(key string) uint64 {
return xxhash.Sum64String(key)
}

View File

@@ -0,0 +1,173 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package counters_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"runtime"
"sync/atomic"
"testing"
"time"
)
func TestCounter_Increase(t *testing.T) {
var a = assert.NewAssertion(t)
var counter = counters.NewCounter()
a.IsTrue(counter.Increase(1, 10) == 1)
a.IsTrue(counter.Increase(1, 10) == 2)
a.IsTrue(counter.Increase(2, 10) == 1)
counter.Reset(1)
a.IsTrue(counter.Get(1) == 0) // changed
a.IsTrue(counter.Get(2) == 1) // not changed
}
func TestCounter_IncreaseKey(t *testing.T) {
var a = assert.NewAssertion(t)
var counter = counters.NewCounter()
a.IsTrue(counter.IncreaseKey("1", 10) == 1)
a.IsTrue(counter.IncreaseKey("1", 10) == 2)
a.IsTrue(counter.IncreaseKey("2", 10) == 1)
counter.ResetKey("1")
a.IsTrue(counter.GetKey("1") == 0) // changed
a.IsTrue(counter.GetKey("2") == 1) // not changed
}
func TestCounter_GC(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var counter = counters.NewCounter()
counter.Increase(1, 20)
time.Sleep(1 * time.Second)
counter.Increase(1, 20)
time.Sleep(1 * time.Second)
counter.Increase(1, 20)
counter.GC()
}
func TestCounter_GC2(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var counter = counters.NewCounter().WithGC()
for i := 0; i < 1e5; i++ {
counter.Increase(uint64(i), rands.Int(10, 300))
}
var ticker = time.NewTicker(1 * time.Second)
for range ticker.C {
t.Log(timeutil.Format("H:i:s"), counter.TotalItems())
if counter.TotalItems() == 0 {
break
}
}
}
func TestCounterMemory(t *testing.T) {
var stat = &runtime.MemStats{}
runtime.ReadMemStats(stat)
var counter = counters.NewCounter()
for i := 0; i < 1e5; i++ {
counter.Increase(uint64(i), rands.Int(10, 300))
}
var stat1 = &runtime.MemStats{}
runtime.ReadMemStats(stat1)
t.Log((stat1.TotalAlloc-stat.TotalAlloc)/(1<<20), "MB")
}
func BenchmarkCounter_Increase(b *testing.B) {
runtime.GOMAXPROCS(4)
var counter = counters.NewCounter()
b.ResetTimer()
var i uint64
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
counter.Increase(atomic.AddUint64(&i, 1)%1e6, 20)
}
})
//b.Log(counter.TotalItems())
}
func BenchmarkCounter_IncreaseKey(b *testing.B) {
runtime.GOMAXPROCS(4)
var counter = counters.NewCounter()
go func() {
var ticker = time.NewTicker(100 * time.Millisecond)
for range ticker.C {
counter.GC()
}
}()
b.ResetTimer()
var i uint64
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
counter.IncreaseKey(types.String(atomic.AddUint64(&i, 1)%1e6), 20)
}
})
//b.Log(counter.TotalItems())
}
func BenchmarkCounter_IncreaseKey2(b *testing.B) {
runtime.GOMAXPROCS(4)
var counter = counters.NewCounter()
go func() {
var ticker = time.NewTicker(1 * time.Millisecond)
for range ticker.C {
counter.GC()
}
}()
b.ResetTimer()
var i uint64
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
counter.IncreaseKey(types.String(atomic.AddUint64(&i, 1)%1e5), 20)
}
})
//b.Log(counter.TotalItems())
}
func BenchmarkCounter_GC(b *testing.B) {
runtime.GOMAXPROCS(4)
var counter = counters.NewCounter()
for i := uint64(0); i < 1e5; i++ {
counter.IncreaseKey(types.String(i), 20)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
counter.GC()
}
})
//b.Log(counter.TotalItems())
}

View File

@@ -0,0 +1,77 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package counters
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
)
type Item struct {
lifeSeconds int64
spanSeconds int64
spans []*Span
lastUpdateTime int64
}
func NewItem(lifeSeconds int) *Item {
if lifeSeconds <= 0 {
lifeSeconds = 60
}
var spanSeconds = lifeSeconds / 10
if spanSeconds < 1 {
spanSeconds = 1
}
var countSpans = lifeSeconds/spanSeconds + 1 /** prevent index out of bounds **/
var spans = []*Span{}
for i := 0; i < countSpans; i++ {
spans = append(spans, NewSpan())
}
return &Item{
lifeSeconds: int64(lifeSeconds),
spanSeconds: int64(spanSeconds),
spans: spans,
lastUpdateTime: fasttime.Now().Unix(),
}
}
func (this *Item) Increase() uint64 {
var currentTime = fasttime.Now().Unix()
var spanIndex = int(currentTime % this.lifeSeconds / this.spanSeconds)
var span = this.spans[spanIndex]
var roundTime = currentTime / this.spanSeconds * this.spanSeconds
this.lastUpdateTime = currentTime
if span.Timestamp < roundTime {
span.Timestamp = roundTime // update time
span.Count = 0 // reset count
}
span.Count++
return this.Sum()
}
func (this *Item) Sum() uint64 {
var result uint64 = 0
var currentTimestamp = fasttime.Now().Unix()
for _, span := range this.spans {
if span.Timestamp >= currentTimestamp-this.lifeSeconds {
result += span.Count
}
}
return result
}
func (this *Item) Reset() {
for _, span := range this.spans {
span.Count = 0
span.Timestamp = 0
}
}
func (this *Item) IsExpired(currentTime int64) bool {
return this.lastUpdateTime < currentTime-this.lifeSeconds-this.spanSeconds
}

View File

@@ -0,0 +1,56 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package counters_test
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/assert"
timeutil "github.com/iwind/TeaGo/utils/time"
"runtime"
"testing"
"time"
)
func TestItem_Increase(t *testing.T) {
// run only under single testing
if !testutils.IsSingleTesting() {
return
}
var a = assert.NewAssertion(t)
var item = counters.NewItem(20)
for i := 0; i < 100; i++ {
t.Log(item.Increase(), timeutil.Format("i:s"))
time.Sleep(2 * time.Second)
}
item.Reset()
a.IsTrue(item.Sum() == 0)
}
func TestItem_IsExpired(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var currentTime = time.Now().Unix()
var item = counters.NewItem(10)
t.Log(item.IsExpired(currentTime))
time.Sleep(10 * time.Second)
t.Log(item.IsExpired(currentTime))
time.Sleep(2 * time.Second)
t.Log(item.IsExpired(currentTime))
}
func BenchmarkItem_Increase(b *testing.B) {
runtime.GOMAXPROCS(1)
var item = counters.NewItem(60)
for i := 0; i < b.N; i++ {
item.Increase()
}
}

View File

@@ -0,0 +1,12 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package counters
type Span struct {
Timestamp int64
Count uint64
}
func NewSpan() *Span {
return &Span{}
}

View File

@@ -14,26 +14,28 @@ type batchItem struct {
}
type Batch struct {
db *sql.DB
db *DB
n int
enableStat bool
onFail func(err error)
queue chan *batchItem
close chan bool
queue chan *batchItem
closeEvent chan bool
isClosed bool
}
func NewBatch(db *sql.DB, n int) *Batch {
return &Batch{
db: db,
n: n,
queue: make(chan *batchItem),
close: make(chan bool, 1),
func NewBatch(db *DB, n int) *Batch {
var batch = &Batch{
db: db,
n: n,
queue: make(chan *batchItem, 16),
closeEvent: make(chan bool, 1),
}
db.batches = append(db.batches, batch)
return batch
}
func (this *Batch) EnableStat(b bool) {
@@ -45,6 +47,9 @@ func (this *Batch) OnFail(callback func(err error)) {
}
func (this *Batch) Add(query string, args ...any) {
if this.isClosed {
return
}
this.queue <- &batchItem{
query: query,
args: args,
@@ -65,7 +70,7 @@ For:
// closed
if this.isClosed {
if lastTx != nil {
_ = lastTx.Commit()
_ = this.commitTx(lastTx)
lastTx = nil
}
@@ -83,6 +88,9 @@ For:
err := this.execItem(lastTx, item)
if err != nil {
if IsClosedErr(err) {
return
}
this.processErr(item.query, err)
}
@@ -90,9 +98,12 @@ For:
if count == n {
count = 0
err = lastTx.Commit()
err = this.commitTx(lastTx)
lastTx = nil
if err != nil {
if IsClosedErr(err) {
return
}
this.processErr("commit", err)
}
}
@@ -101,16 +112,19 @@ For:
continue For
}
count = 0
err := lastTx.Commit()
err := this.commitTx(lastTx)
lastTx = nil
if err != nil {
if IsClosedErr(err) {
return
}
this.processErr("commit", err)
}
case <-this.close:
case <-this.closeEvent:
// closed
if lastTx != nil {
_ = lastTx.Commit()
_ = this.commitTx(lastTx)
lastTx = nil
}
@@ -119,26 +133,44 @@ For:
}
}
func (this *Batch) Close() {
func (this *Batch) close() {
this.isClosed = true
select {
case this.close <- true:
case this.closeEvent <- true:
default:
}
}
func (this *Batch) beginTx() *sql.Tx {
if !this.db.BeginUpdating() {
return nil
}
tx, err := this.db.Begin()
if err != nil {
this.processErr("begin transaction", err)
this.db.EndUpdating()
return nil
}
return tx
}
func (this *Batch) commitTx(tx *sql.Tx) error {
// always commit without checking database closing status
this.db.EndUpdating()
return tx.Commit()
}
func (this *Batch) execItem(tx *sql.Tx, item *batchItem) error {
// check database status
if this.db.BeginUpdating() {
defer this.db.EndUpdating()
} else {
return errDBIsClosed
}
if this.enableStat {
defer SharedQueryStatManager.AddQuery(item.query).End()
}

View File

@@ -5,19 +5,34 @@ package dbs
import (
"context"
"database/sql"
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fileutils"
_ "github.com/mattn/go-sqlite3"
"net/url"
"strings"
"sync"
"time"
)
var errDBIsClosed = errors.New("the database is closed")
type DB struct {
locker *fileutils.Locker
rawDB *sql.DB
dsn string
statusLocker sync.Mutex
countUpdating int32
isClosing bool
enableStat bool
batches []*Batch
}
func OpenWriter(dsn string) (*DB, error) {
@@ -29,6 +44,10 @@ func OpenReader(dsn string) (*DB, error) {
}
func open(dsn string, lock bool) (*DB, error) {
if teaconst.IsQuiting {
return nil, errors.New("can not open database when process is quiting")
}
// locker
var locker *fileutils.Locker
if lock {
@@ -52,21 +71,22 @@ func open(dsn string, lock bool) (*DB, error) {
return nil, err
}
var db = NewDB(rawDB)
var db = NewDB(rawDB, dsn)
db.locker = locker
return db, nil
}
func NewDB(rawDB *sql.DB) *DB {
func NewDB(rawDB *sql.DB, dsn string) *DB {
var db = &DB{
rawDB: rawDB,
dsn: dsn,
}
events.OnKey(events.EventQuit, fmt.Sprintf("db_%p", db), func() {
_ = rawDB.Close()
_ = db.Close()
})
events.OnKey(events.EventTerminated, fmt.Sprintf("db_%p", db), func() {
_ = rawDB.Close()
_ = db.Close()
})
return db
@@ -81,6 +101,13 @@ func (this *DB) EnableStat(b bool) {
}
func (this *DB) Begin() (*sql.Tx, error) {
// check database status
if this.BeginUpdating() {
defer this.EndUpdating()
} else {
return nil, errDBIsClosed
}
return this.rawDB.Begin()
}
@@ -90,7 +117,7 @@ func (this *DB) Prepare(query string) (*Stmt, error) {
return nil, err
}
var s = NewStmt(stmt, query)
var s = NewStmt(this, stmt, query)
if this.enableStat {
s.EnableStat()
}
@@ -98,13 +125,28 @@ func (this *DB) Prepare(query string) (*Stmt, error) {
}
func (this *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
// check database status
if this.BeginUpdating() {
defer this.EndUpdating()
} else {
return nil, errDBIsClosed
}
if this.enableStat {
defer SharedQueryStatManager.AddQuery(query).End()
}
return this.rawDB.ExecContext(ctx, query, args...)
}
func (this *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
// check database status
if this.BeginUpdating() {
defer this.EndUpdating()
} else {
return nil, errDBIsClosed
}
if this.enableStat {
defer SharedQueryStatManager.AddQuery(query).End()
}
@@ -125,7 +167,32 @@ func (this *DB) QueryRow(query string, args ...interface{}) *sql.Row {
return this.rawDB.QueryRow(query, args...)
}
// Close the database
func (this *DB) Close() error {
// check database status
this.statusLocker.Lock()
if this.isClosing {
this.statusLocker.Unlock()
return nil
}
this.isClosing = true
this.statusLocker.Unlock()
// waiting for updating operations to finish
for {
this.statusLocker.Lock()
var countUpdating = this.countUpdating
this.statusLocker.Unlock()
if countUpdating <= 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
for _, batch := range this.batches {
batch.close()
}
events.Remove(fmt.Sprintf("db_%p", this))
defer func() {
@@ -134,9 +201,35 @@ func (this *DB) Close() error {
}
}()
// print log
if len(this.dsn) > 0 {
u, _ := url.Parse(this.dsn)
if u != nil && len(u.Path) > 0 {
remotelogs.Debug("DB", "close '"+u.Path)
}
}
return this.rawDB.Close()
}
func (this *DB) BeginUpdating() bool {
this.statusLocker.Lock()
defer this.statusLocker.Unlock()
if this.isClosing {
return false
}
this.countUpdating++
return true
}
func (this *DB) EndUpdating() {
this.statusLocker.Lock()
this.countUpdating--
this.statusLocker.Unlock()
}
func (this *DB) RawDB() *sql.DB {
return this.rawDB
}

View File

@@ -0,0 +1,17 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dbs_test
import (
"net/url"
"testing"
)
func TestParseDSN(t *testing.T) {
var dsn = "file:/home/cache/p43/.indexes/db-3.db?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size=88000"
u, err := url.Parse(dsn)
if err != nil {
t.Fatal(err)
}
t.Log(u.Path) // expect: :/home/cache/p43/.indexes/db-3.db
}

View File

@@ -8,14 +8,16 @@ import (
)
type Stmt struct {
db *DB
rawStmt *sql.Stmt
query string
enableStat bool
}
func NewStmt(rawStmt *sql.Stmt, query string) *Stmt {
func NewStmt(db *DB, rawStmt *sql.Stmt, query string) *Stmt {
return &Stmt{
db: db,
rawStmt: rawStmt,
query: query,
}
@@ -26,6 +28,13 @@ func (this *Stmt) EnableStat() {
}
func (this *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) {
// check database status
if this.db.BeginUpdating() {
defer this.db.EndUpdating()
} else {
return nil, errDBIsClosed
}
if this.enableStat {
defer SharedQueryStatManager.AddQuery(this.query).End()
}
@@ -33,6 +42,13 @@ func (this *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Res
}
func (this *Stmt) Exec(args ...interface{}) (sql.Result, error) {
// check database status
if this.db.BeginUpdating() {
defer this.db.EndUpdating()
} else {
return nil, errDBIsClosed
}
if this.enableStat {
defer SharedQueryStatManager.AddQuery(this.query).End()
}

View File

@@ -0,0 +1,7 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dbs
func IsClosedErr(err error) bool {
return err == errDBIsClosed
}

View File

@@ -0,0 +1,59 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build linux
package executils
import (
"golang.org/x/sys/unix"
"io/fs"
"os"
"os/exec"
"syscall"
)
// LookPath customize our LookPath() function, to work in broken $PATH environment variable
func LookPath(file string) (string, error) {
result, err := exec.LookPath(file)
if err == nil && len(result) > 0 {
return result, nil
}
// add common dirs contains executable files these may be excluded in $PATH environment variable
var binPaths = []string{
"/usr/sbin",
"/usr/bin",
"/usr/local/sbin",
"/usr/local/bin",
}
for _, binPath := range binPaths {
var fullPath = binPath + string(os.PathSeparator) + file
stat, err := os.Stat(fullPath)
if err != nil {
continue
}
if stat.IsDir() {
return "", syscall.EISDIR
}
var mode = stat.Mode()
if mode.IsDir() {
return "", syscall.EISDIR
}
err = syscall.Faccessat(unix.AT_FDCWD, fullPath, unix.X_OK, unix.AT_EACCESS)
if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) {
return fullPath, err
}
if mode&0111 != 0 {
return fullPath, nil
}
return "", fs.ErrPermission
}
return "", &exec.Error{
Name: file,
Err: exec.ErrNotFound,
}
}

View File

@@ -0,0 +1,10 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !linux
package executils
import "os/exec"
func LookPath(file string) (string, error) {
return exec.LookPath(file)
}

View File

@@ -24,7 +24,7 @@ func init() {
SharedFreeHoursManager.Start()
})
})
events.On(events.EventQuit, func() {
events.OnClose(func() {
SharedFreeHoursManager.Stop()
})
}

69
internal/utils/fs/disk.go Normal file
View File

@@ -0,0 +1,69 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package fsutils
import (
"bytes"
"os"
"time"
)
// CheckDiskWritingSpeed test disk writing speed
func CheckDiskWritingSpeed() (speedMB float64, err error) {
var tempDir = os.TempDir()
if len(tempDir) == 0 {
tempDir = "/tmp"
}
const filename = "edge-disk-writing-test.data"
var path = tempDir + "/" + filename
_ = os.Remove(path) // always try to delete the file
fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return 0, err
}
var isClosed bool
defer func() {
if !isClosed {
_ = fp.Close()
}
_ = os.Remove(path)
}()
var data = bytes.Repeat([]byte{'A'}, 16<<20)
var before = time.Now()
_, err = fp.Write(data)
if err != nil {
return 0, err
}
err = fp.Sync()
if err != nil {
return 0, err
}
err = fp.Close()
if err != nil {
return 0, err
}
var costSeconds = time.Since(before).Seconds()
speedMB = float64(len(data)) / (1 << 20) / costSeconds
isClosed = true
return
}
// CheckDiskIsFast check disk is 'fast' disk to write
func CheckDiskIsFast() (speedMB float64, isFast bool, err error) {
speedMB, err = CheckDiskWritingSpeed()
if err != nil {
return
}
isFast = speedMB > 120
return
}

View File

@@ -0,0 +1,16 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package fsutils_test
import (
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
"testing"
)
func TestCheckDiskWritingSpeed(t *testing.T) {
t.Log(fsutils.CheckDiskWritingSpeed())
}
func TestCheckDiskIsFast(t *testing.T) {
t.Log(fsutils.CheckDiskIsFast())
}

81
internal/utils/fs/stat.go Normal file
View File

@@ -0,0 +1,81 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package fsutils
import (
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"golang.org/x/sys/unix"
"sync"
)
// Stat device contains the path
func Stat(path string) (*StatResult, error) {
var stat = &unix.Statfs_t{}
err := unix.Statfs(path, stat)
if err != nil {
return nil, err
}
return NewStatResult(stat), nil
}
var locker = &sync.RWMutex{}
var cacheMap = map[string]*StatResult{} // path => StatResult
const cacheLife = 3 // seconds
// StatCache stat device with cache
func StatCache(path string) (*StatResult, error) {
locker.RLock()
stat, ok := cacheMap[path]
if ok && stat.updatedAt >= fasttime.Now().Unix()-cacheLife {
locker.RUnlock()
return stat, nil
}
locker.RUnlock()
locker.Lock()
defer locker.Unlock()
stat, err := Stat(path)
if err != nil {
return nil, err
}
cacheMap[path] = stat
return stat, nil
}
type StatResult struct {
rawStat *unix.Statfs_t
blockSize uint64
updatedAt int64
}
func NewStatResult(rawStat *unix.Statfs_t) *StatResult {
var blockSize = rawStat.Bsize
if blockSize < 0 {
blockSize = 0
}
return &StatResult{
rawStat: rawStat,
blockSize: uint64(blockSize),
updatedAt: fasttime.Now().Unix(),
}
}
func (this *StatResult) FreeSize() uint64 {
return this.rawStat.Bfree * this.blockSize
}
func (this *StatResult) TotalSize() uint64 {
return this.rawStat.Blocks * this.blockSize
}
func (this *StatResult) UsedSize() uint64 {
if this.rawStat.Bfree <= this.rawStat.Blocks {
return (this.rawStat.Blocks - this.rawStat.Bfree) * this.blockSize
}
return 0
}

View File

@@ -0,0 +1,69 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package fsutils_test
import (
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
"sync"
"testing"
"time"
)
func TestStat(t *testing.T) {
stat, err := fsutils.Stat("/usr/local")
if err != nil {
t.Fatal(err)
}
t.Log("free:", stat.FreeSize()/(1<<30), "total:", stat.TotalSize()/(1<<30), "used:", stat.UsedSize()/(1<<30))
}
func TestStatCache(t *testing.T) {
for i := 0; i < 10; i++ {
stat, err := fsutils.StatCache("/usr/local")
if err != nil {
t.Fatal(err)
}
t.Log("free:", stat.FreeSize()/(1<<30), "total:", stat.TotalSize()/(1<<30), "used:", stat.UsedSize()/(1<<30))
}
}
func TestConcurrent(t *testing.T) {
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
var count = 10000
var wg = sync.WaitGroup{}
wg.Add(count)
for i := 0; i < count; i++ {
go func() {
defer wg.Done()
_, _ = fsutils.Stat("/usr/local")
}()
}
wg.Wait()
}
func BenchmarkStat(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := fsutils.Stat("/usr/local")
if err != nil {
b.Fatal(err)
}
}
})
}
func BenchmarkStatCache(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := fsutils.StatCache("/usr/local")
if err != nil {
b.Fatal(err)
}
}
})
}

View File

@@ -25,7 +25,7 @@ func (this *TeeReaderCloser) Read(p []byte) (n int, err error) {
n, err = this.r.Read(p)
if n > 0 {
_, wErr := this.w.Write(p[:n])
if err == nil && wErr != nil {
if (err == nil || err == io.EOF) && wErr != nil {
err = wErr
}
}

View File

@@ -24,7 +24,7 @@ func (this *ServiceManager) Install(exePath string, args []string) error {
return errors.New("only root users can install the service")
}
systemd, err := exec.LookPath("systemctl")
systemd, err := executils.LookPath("systemctl")
if err != nil {
return this.installInitService(exePath, args)
}
@@ -39,7 +39,7 @@ func (this *ServiceManager) Start() error {
}
if files.NewFile(systemdServiceFile).Exists() {
systemd, err := exec.LookPath("systemctl")
systemd, err := executils.LookPath("systemctl")
if err != nil {
return err
}
@@ -56,7 +56,7 @@ func (this *ServiceManager) Uninstall() error {
}
if files.NewFile(systemdServiceFile).Exists() {
systemd, err := exec.LookPath("systemctl")
systemd, err := executils.LookPath("systemctl")
if err != nil {
return err
}
@@ -96,7 +96,7 @@ func (this *ServiceManager) installInitService(exePath string, args []string) er
return err
}
chkCmd, err := exec.LookPath("chkconfig")
chkCmd, err := executils.LookPath("chkconfig")
if err != nil {
return err
}

View File

@@ -13,8 +13,8 @@ type FixedSet struct {
maxSize int
locker sync.RWMutex
m map[interface{}]zero.Zero
keys []interface{}
m map[any]zero.Zero
keys []any
}
func NewFixedSet(maxSize int) *FixedSet {
@@ -23,11 +23,11 @@ func NewFixedSet(maxSize int) *FixedSet {
}
return &FixedSet{
maxSize: maxSize,
m: map[interface{}]zero.Zero{},
m: map[any]zero.Zero{},
}
}
func (this *FixedSet) Push(item interface{}) {
func (this *FixedSet) Push(item any) {
this.locker.Lock()
_, ok := this.m[item]
if !ok {
@@ -44,7 +44,7 @@ func (this *FixedSet) Push(item interface{}) {
this.locker.Unlock()
}
func (this *FixedSet) Has(item interface{}) bool {
func (this *FixedSet) Has(item any) bool {
this.locker.RLock()
defer this.locker.RUnlock()
@@ -60,7 +60,7 @@ func (this *FixedSet) Size() int {
func (this *FixedSet) Reset() {
this.locker.Lock()
this.m = map[interface{}]zero.Zero{}
this.m = map[any]zero.Zero{}
this.keys = nil
this.locker.Unlock()
}

View File

@@ -0,0 +1,136 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package syncutils
import (
"runtime"
"sync"
)
type KType interface {
int | int16 | int32 | int64 | uint | uint16 | uint32 | uint64 | uintptr
}
type VType interface {
any
}
type IntMap[K KType, V VType] struct {
count int
m []map[K]V
lockers []*sync.RWMutex
}
func NewIntMap[K KType, V VType]() *IntMap[K, V] {
var count = runtime.NumCPU() * 8
if count <= 0 {
count = 32
}
var m = []map[K]V{}
var lockers = []*sync.RWMutex{}
for i := 0; i < count; i++ {
m = append(m, map[K]V{})
lockers = append(lockers, &sync.RWMutex{})
}
return &IntMap[K, V]{
count: count,
m: m,
lockers: lockers,
}
}
func (this *IntMap[K, V]) Put(k K, v V) {
var index = this.index(k)
this.lockers[index].Lock()
this.m[index][k] = v
this.lockers[index].Unlock()
}
func (this *IntMap[K, V]) PutCompact(k K, v V, compactFunc func(oldV V, newV V) V) {
var index = this.index(k)
this.lockers[index].Lock()
// 再次检查是否已经存在,如果已经存在则合并
oldV, ok := this.m[index][k]
if ok {
this.m[index][k] = compactFunc(oldV, v)
} else {
this.m[index][k] = v
}
this.lockers[index].Unlock()
}
func (this *IntMap[K, V]) Has(k K) bool {
var index = this.index(k)
this.lockers[index].RLock()
_, ok := this.m[index][k]
this.lockers[index].RUnlock()
return ok
}
func (this *IntMap[K, V]) Get(k K) (value V) {
var index = this.index(k)
this.lockers[index].RLock()
value, _ = this.m[index][k]
this.lockers[index].RUnlock()
return
}
func (this *IntMap[K, V]) GetOk(k K) (value V, ok bool) {
var index = this.index(k)
this.lockers[index].RLock()
value, ok = this.m[index][k]
this.lockers[index].RUnlock()
return
}
func (this *IntMap[K, V]) Delete(k K) {
var index = this.index(k)
this.lockers[index].Lock()
delete(this.m[index], k)
this.lockers[index].Unlock()
}
func (this *IntMap[K, V]) DeleteUnsafe(k K) {
var index = this.index(k)
delete(this.m[index], k)
}
func (this *IntMap[K, V]) Len() int {
var l int
for i := 0; i < this.count; i++ {
this.lockers[i].RLock()
l += len(this.m[i])
this.lockers[i].RUnlock()
}
return l
}
func (this *IntMap[K, V]) ForEachRead(iterator func(k K, v V)) {
for i := 0; i < this.count; i++ {
this.lockers[i].RLock()
for k, v := range this.m[i] {
iterator(k, v)
}
this.lockers[i].RUnlock()
}
}
func (this *IntMap[K, V]) ForEachWrite(iterator func(k K, v V)) {
for i := 0; i < this.count; i++ {
this.lockers[i].Lock()
for k, v := range this.m[i] {
iterator(k, v)
}
this.lockers[i].Unlock()
}
}
func (this *IntMap[K, V]) index(k K) int {
var index = int(k % K(this.count))
if index < 0 {
index = -index
}
return index
}

View File

@@ -0,0 +1,125 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package syncutils_test
import (
"github.com/TeaOSLab/EdgeNode/internal/stats"
syncutils "github.com/TeaOSLab/EdgeNode/internal/utils/sync"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/types"
"sync"
"testing"
)
func TestIntMap(t *testing.T) {
var a = assert.NewAssertion(t)
var m = syncutils.NewIntMap[int, string]()
m.Put(1, "1")
a.IsTrue(m.Has(1))
a.IsFalse(m.Has(2))
m.Put(-1, "-1")
t.Log(m.Get(-1))
t.Log(m.Len(), "values")
{
a.IsTrue(m.Has(-1))
m.Delete(-1)
a.IsFalse(m.Has(-1))
}
t.Log(m.Len(), "values")
}
func TestInt64Map(t *testing.T) {
var a = assert.NewAssertion(t)
var m = syncutils.NewIntMap[int64, string]()
m.Put(1, "1")
a.IsTrue(m.Has(1))
a.IsFalse(m.Has(2))
m.Put(-1, "-1")
t.Log(m.Get(-1))
t.Log(m.Len(), "values")
{
a.IsTrue(m.Has(-1))
m.Delete(-1)
a.IsFalse(m.Has(-1))
}
m.Put(1024000000, "large int")
t.Log(m.Get(1024000000))
t.Log(m.Len(), "values")
}
func TestIntMap_PutCompact(t *testing.T) {
var a = assert.NewAssertion(t)
var m = syncutils.NewIntMap[int, string]()
m.Put(1, "a")
m.Put(1, "b")
a.IsTrue(m.Get(1) == "b")
m.PutCompact(1, "c", func(oldV string, newV string) string {
return oldV + newV
})
a.IsTrue(m.Get(1) == "bc")
}
func TestIntMap_ForEachRead(t *testing.T) {
var m = syncutils.NewIntMap[int, string]()
for i := 0; i < 100; i++ {
m.Put(i, "v"+types.String(i))
}
t.Log(m.Len())
m.ForEachRead(func(k int, v string) {
t.Log(k, v)
})
}
func TestIntMap_ForEachWrite(t *testing.T) {
var m = syncutils.NewIntMap[int, string]()
for i := 0; i < 100; i++ {
m.Put(i, "v"+types.String(i))
}
t.Log(m.Len())
m.ForEachRead(func(k int, v string) {
t.Log(k, v)
m.DeleteUnsafe(k)
})
t.Log(m.Len(), "elements left")
}
func BenchmarkNewIntMap(b *testing.B) {
var m = syncutils.NewIntMap[int, *stats.BandwidthStat]()
b.RunParallel(func(pb *testing.PB) {
var i int
for pb.Next() {
i++
m.Put(i, &stats.BandwidthStat{ServerId: 100})
_ = m.Get(i + 100)
}
})
}
func BenchmarkNewIntMap2(b *testing.B) {
var m = map[int]*stats.BandwidthStat{}
var locker = sync.RWMutex{}
b.RunParallel(func(pb *testing.PB) {
var i int
for pb.Next() {
i++
locker.Lock()
m[i] = &stats.BandwidthStat{ServerId: 100}
locker.Unlock()
locker.RLock()
_ = m[i+100]
locker.RUnlock()
}
})
}

View File

@@ -0,0 +1,56 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package syncutils
import (
"sync"
)
type RWMutex struct {
lockers []*sync.RWMutex
countLockers int
}
func NewRWMutex(count int) *RWMutex {
if count <= 0 {
count = 1
}
var lockers = []*sync.RWMutex{}
for i := 0; i < count; i++ {
lockers = append(lockers, &sync.RWMutex{})
}
return &RWMutex{
lockers: lockers,
countLockers: len(lockers),
}
}
func (this *RWMutex) Lock(index int) {
this.lockers[index%this.countLockers].Lock()
}
func (this *RWMutex) Unlock(index int) {
this.lockers[index%this.countLockers].Unlock()
}
func (this *RWMutex) RLock(index int) {
this.lockers[index%this.countLockers].RLock()
}
func (this *RWMutex) RUnlock(index int) {
this.lockers[index%this.countLockers].RUnlock()
}
func (this *RWMutex) TryLock(index int) bool {
return this.lockers[index%this.countLockers].TryLock()
}
func (this *RWMutex) TryRLock(index int) bool {
return this.lockers[index%this.countLockers].TryRLock()
}
func (this *RWMutex) RWMutex(index int) *sync.RWMutex {
return this.lockers[index%this.countLockers]
}

View File

@@ -0,0 +1,47 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package syncutils_test
import (
syncutils "github.com/TeaOSLab/EdgeNode/internal/utils/sync"
"runtime"
"sync"
"testing"
"time"
)
func TestNewRWMutex(t *testing.T) {
var locker = syncutils.NewRWMutex(runtime.NumCPU())
locker.Lock(1)
t.Log(locker.TryLock(1))
locker.Unlock(1)
t.Log(locker.TryLock(1))
}
func BenchmarkRWMutex_Lock(b *testing.B) {
var locker = syncutils.NewRWMutex(runtime.NumCPU())
b.RunParallel(func(pb *testing.PB) {
var i = 0
for pb.Next() {
i++
locker.Lock(i)
time.Sleep(1 * time.Millisecond)
locker.Unlock(i)
}
})
}
func BenchmarkRWMutex_Lock2(b *testing.B) {
var locker = &sync.Mutex{}
b.RunParallel(func(pb *testing.PB) {
var i = 0
for pb.Next() {
i++
locker.Lock()
time.Sleep(1 * time.Millisecond)
locker.Unlock()
}
})
}

View File

@@ -7,7 +7,6 @@ import (
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
"net/http"
@@ -121,7 +120,7 @@ func (this *JSCookieAction) increaseFails(req requests.Request, policyId int64,
var key = "JS_COOKIE:FAILS:" + req.WAFRemoteIP() + ":" + types.String(req.WAFServerId()) + ":" + req.WAFRaw().URL.String()
var countFails = ttlcache.SharedCache.IncreaseInt64(key, 1, time.Now().Unix()+300, true)
var countFails = SharedCounter.IncreaseKey(key, 300)
if int(countFails) >= maxFails {
SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, true, groupId, setId, "JS_COOKIE验证连续失败超过"+types.String(maxFails)+"次")
return false

View File

@@ -4,7 +4,6 @@ package waf
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/types"
@@ -27,7 +26,7 @@ func CaptchaIncreaseFails(req requests.Request, actionConfig *CaptchaAction, pol
if maxFails <= 3 {
maxFails = 3 // 不能小于3防止意外刷新出现
}
var countFails = ttlcache.SharedCache.IncreaseInt64(CaptchaCacheKey(req, pageCode), 1, time.Now().Unix()+300, true)
var countFails = SharedCounter.IncreaseKey(CaptchaCacheKey(req, pageCode), 300)
if int(countFails) >= maxFails {
SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, true, groupId, setId, "CAPTCHA验证连续失败超过"+types.String(maxFails)+"次")
return false
@@ -38,9 +37,9 @@ func CaptchaIncreaseFails(req requests.Request, actionConfig *CaptchaAction, pol
// CaptchaDeleteCacheKey 清除计数
func CaptchaDeleteCacheKey(req requests.Request) {
ttlcache.SharedCache.Delete(CaptchaCacheKey(req, CaptchaPageCodeInit))
ttlcache.SharedCache.Delete(CaptchaCacheKey(req, CaptchaPageCodeShow))
ttlcache.SharedCache.Delete(CaptchaCacheKey(req, CaptchaPageCodeSubmit))
SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeInit))
SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeShow))
SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeSubmit))
}
// CaptchaCacheKey 获取Captcha缓存Key

View File

@@ -1,22 +1,19 @@
package checkpoints
import (
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp"
"sync"
"time"
)
var ccCounter = counters.NewCounter().WithGC()
// CCCheckpoint ${cc.arg}
// TODO implement more traffic rules
type CCCheckpoint struct {
Checkpoint
cache *ttlcache.Cache
once sync.Once
}
func (this *CCCheckpoint) Init() {
@@ -24,33 +21,25 @@ func (this *CCCheckpoint) Init() {
}
func (this *CCCheckpoint) Start() {
if this.cache != nil {
this.cache.Destroy()
}
this.cache = ttlcache.NewCache()
}
func (this *CCCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value interface{}, hasRequestBody bool, sysErr error, userErr error) {
value = 0
if this.cache == nil {
this.once.Do(func() {
this.Start()
})
if this.cache == nil {
return
}
}
periodString, ok := options["period"]
if !ok {
return
}
period := types.Int64(periodString)
var period = types.Int(periodString)
if period < 1 {
return
}
if period > 7*86400 {
period = 7 * 86400
}
v, _ := options["userType"]
userType := types.String(v)
@@ -114,7 +103,7 @@ func (this *CCCheckpoint) RequestValue(req requests.Request, param string, optio
if len(key) == 0 {
key = req.WAFRemoteIP()
}
value = this.cache.IncreaseInt64(types.String(ruleId)+"@"+key, int64(1), time.Now().Unix()+period, false)
value = ccCounter.IncreaseKey(types.String(ruleId)+"@"+key, types.Int(period))
}
return
@@ -203,8 +192,5 @@ func (this *CCCheckpoint) Options() []OptionInterface {
}
func (this *CCCheckpoint) Stop() {
if this.cache != nil {
this.cache.Destroy()
this.cache = nil
}
}

View File

@@ -4,17 +4,16 @@ package checkpoints
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"path/filepath"
"strings"
"time"
)
var ccCache = ttlcache.NewCache()
var cc2Counter = counters.NewCounter().WithGC()
var commonFileExtensionsMap = map[string]zero.Zero{
".ico": zero.New(),
@@ -26,6 +25,10 @@ var commonFileExtensionsMap = map[string]zero.Zero{
".woff2": zero.New(),
".js": zero.New(),
".css": zero.New(),
".ttf": zero.New(),
".otf": zero.New(),
".fnt": zero.New(),
".svg": zero.New(),
}
// CC2Checkpoint 新的CC
@@ -47,9 +50,11 @@ func (this *CC2Checkpoint) RequestValue(req requests.Request, param string, opti
return
}
var period = options.GetInt64("period")
var period = options.GetInt("period")
if period <= 0 {
period = 60
} else if period > 7*86400 {
period = 7 * 86400
}
var threshold = options.GetInt64("threshold")
@@ -71,9 +76,8 @@ func (this *CC2Checkpoint) RequestValue(req requests.Request, param string, opti
}
}
var expiresAt = time.Now().Unix() + period
var ccKey = "WAF-CC-" + types.String(ruleId) + "-" + strings.Join(keyValues, "@")
value = ccCache.IncreaseInt64(ccKey, 1, expiresAt, false)
value = cc2Counter.IncreaseKey(ccKey, period)
// 基于指纹统计
var enableFingerprint = true
@@ -92,8 +96,8 @@ func (this *CC2Checkpoint) RequestValue(req requests.Request, param string, opti
fpKeyValues = append(fpKeyValues, req.Format(types.String(key)))
}
var fpCCKey = "WAF-CC-" + types.String(ruleId) + "-" + strings.Join(fpKeyValues, "@")
var fpValue = ccCache.IncreaseInt64(fpCCKey, 1, expiresAt, false)
if fpValue > value.(int64) {
var fpValue = cc2Counter.IncreaseKey(fpCCKey, period)
if fpValue > value.(uint64) {
value = fpValue
}
}

7
internal/waf/counter.go Normal file
View File

@@ -0,0 +1,7 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package waf
import "github.com/TeaOSLab/EdgeNode/internal/utils/counters"
var SharedCounter = counters.NewCounter().WithGC()